Skip to content

Commit

Permalink
Add force_destroy field to aws_athena_database (#2363)
Browse files Browse the repository at this point in the history
* Add force_destroy field to aws_athena_database.

Fixes #2362.

* Remove unnecessary import.

* Code review feedback
  • Loading branch information
betabandido authored and radeksimko committed Dec 11, 2017
1 parent 881c998 commit 90347f6
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 4 deletions.
27 changes: 25 additions & 2 deletions aws/resource_aws_athena_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ func resourceAwsAthenaDatabase() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAthenaDatabaseCreate,
Read: resourceAwsAthenaDatabaseRead,
Update: resourceAwsAthenaDatabaseUpdate,
Delete: resourceAwsAthenaDatabaseDelete,

Schema: map[string]*schema.Schema{
Expand All @@ -28,6 +29,11 @@ func resourceAwsAthenaDatabase() *schema.Resource {
Required: true,
ForceNew: true,
},
"force_destroy": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
Expand Down Expand Up @@ -76,12 +82,24 @@ func resourceAwsAthenaDatabaseRead(d *schema.ResourceData, meta interface{}) err
return nil
}

func resourceAwsAthenaDatabaseUpdate(d *schema.ResourceData, meta interface{}) error {
return resourceAwsAthenaDatabaseRead(d, meta)
}

func resourceAwsAthenaDatabaseDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).athenaconn

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

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

input := &athena.StartQueryExecutionInput{
QueryString: aws.String(fmt.Sprintf("drop database %s;", d.Get("name").(string))),
QueryString: aws.String(queryString),
ResultConfiguration: &athena.ResultConfiguration{
OutputLocation: aws.String("s3://" + bucket),
},
Expand Down Expand Up @@ -168,7 +186,12 @@ func queryExecutionStateRefreshFunc(qeid string, conn *athena.Athena) resource.S
if err != nil {
return nil, "failed", err
}
return out, *out.QueryExecution.Status.State, nil
status := out.QueryExecution.Status
if *status.State == athena.QueryExecutionStateFailed &&
status.StateChangeReason != nil {
err = fmt.Errorf("reason: %s", *status.StateChangeReason)
}
return out, *out.QueryExecution.Status.State, err
}
}

Expand Down
174 changes: 172 additions & 2 deletions aws/resource_aws_athena_database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,48 @@ func TestAccAWSAthenaDatabase_basic(t *testing.T) {
})
}

func TestAccAWSAthenaDatabase_destroyFailsIfTablesExist(t *testing.T) {
rInt := acctest.RandInt()
dbName := acctest.RandString(8)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAthenaDatabaseDestroy,
Steps: []resource.TestStep{
{
Config: testAccAthenaDatabaseConfig(rInt, dbName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAthenaDatabaseExists("aws_athena_database.hoge"),
testAccAWSAthenaDatabaseCreateTables(dbName),
testAccCheckAWSAthenaDatabaseDropFails(dbName),
testAccAWSAthenaDatabaseDestroyTables(dbName),
),
},
},
})
}

func TestAccAWSAthenaDatabase_forceDestroyAlwaysSucceeds(t *testing.T) {
rInt := acctest.RandInt()
dbName := acctest.RandString(8)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAthenaDatabaseDestroy,
Steps: []resource.TestStep{
{
Config: testAccAthenaDatabaseConfigForceDestroy(rInt, dbName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSAthenaDatabaseExists("aws_athena_database.hoge"),
testAccAWSAthenaDatabaseCreateTables(dbName),
),
},
},
})
}

// StartQueryExecution requires OutputLocation but terraform destroy deleted S3 bucket as well.
// So temporary S3 bucket as OutputLocation is created to confirm whether the database is acctually deleted.
// So temporary S3 bucket as OutputLocation is created to confirm whether the database is actually deleted.
func testAccCheckAWSAthenaDatabaseDestroy(s *terraform.State) error {
athenaconn := testAccProvider.Meta().(*AWSClient).athenaconn
s3conn := testAccProvider.Meta().(*AWSClient).s3conn
Expand Down Expand Up @@ -122,12 +162,127 @@ func testAccCheckAWSAthenaDatabaseExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
_, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s, %v", name, s.RootModule().Resources)
return fmt.Errorf("not found: %s, %v", name, s.RootModule().Resources)
}
return nil
}
}

func testAccAWSAthenaDatabaseCreateTables(dbName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
bucketName, err := testAccAthenaDatabaseFindBucketName(s, dbName)
if err != nil {
return err
}

athenaconn := testAccProvider.Meta().(*AWSClient).athenaconn

input := &athena.StartQueryExecutionInput{
QueryExecutionContext: &athena.QueryExecutionContext{
Database: aws.String(dbName),
},
QueryString: aws.String(fmt.Sprintf(
"create external table foo (bar int) location 's3://%s/';", bucketName)),
ResultConfiguration: &athena.ResultConfiguration{
OutputLocation: aws.String("s3://" + bucketName),
},
}

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

_, err = queryExecutionResult(*resp.QueryExecutionId, athenaconn)
if err != nil {
return err
}

return nil
}
}

func testAccAWSAthenaDatabaseDestroyTables(dbName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
bucketName, err := testAccAthenaDatabaseFindBucketName(s, dbName)
if err != nil {
return err
}

athenaconn := testAccProvider.Meta().(*AWSClient).athenaconn

input := &athena.StartQueryExecutionInput{
QueryExecutionContext: &athena.QueryExecutionContext{
Database: aws.String(dbName),
},
QueryString: aws.String("drop table foo;"),
ResultConfiguration: &athena.ResultConfiguration{
OutputLocation: aws.String("s3://" + bucketName),
},
}

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

_, err = queryExecutionResult(*resp.QueryExecutionId, athenaconn)
if err != nil {
return err
}

return nil
}
}

func testAccCheckAWSAthenaDatabaseDropFails(dbName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
bucketName, err := testAccAthenaDatabaseFindBucketName(s, dbName)
if err != nil {
return err
}

athenaconn := testAccProvider.Meta().(*AWSClient).athenaconn

input := &athena.StartQueryExecutionInput{
QueryExecutionContext: &athena.QueryExecutionContext{
Database: aws.String(dbName),
},
QueryString: aws.String(fmt.Sprintf("drop database %s;", dbName)),
ResultConfiguration: &athena.ResultConfiguration{
OutputLocation: aws.String("s3://" + bucketName),
},
}

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

_, err = queryExecutionResult(*resp.QueryExecutionId, athenaconn)
if err == nil {
return fmt.Errorf("drop database unexpectedly succeeded for a database with tables")
}

return nil
}
}

func testAccAthenaDatabaseFindBucketName(s *terraform.State, dbName string) (bucket string, err error) {
for _, rs := range s.RootModule().Resources {
if rs.Type == "aws_athena_database" && rs.Primary.Attributes["name"] == dbName {
bucket = rs.Primary.Attributes["bucket"]
break
}
}

if bucket == "" {
err = fmt.Errorf("cannot find database %s", dbName)
}

return bucket, err
}

func testAccAthenaDatabaseConfig(randInt int, dbName string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "hoge" {
Expand All @@ -141,3 +296,18 @@ func testAccAthenaDatabaseConfig(randInt int, dbName string) string {
}
`, dbName, randInt, dbName)
}

func testAccAthenaDatabaseConfigForceDestroy(randInt int, dbName string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "hoge" {
bucket = "tf-athena-db-%s-%d"
force_destroy = true
}
resource "aws_athena_database" "hoge" {
name = "%s"
bucket = "${aws_s3_bucket.hoge.bucket}"
force_destroy = true
}
`, dbName, randInt, dbName)
}
3 changes: 3 additions & 0 deletions website/docs/r/athena_database.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ The following arguments are supported:

* `name` - (Required) Name of the database to create.
* `bucket` - (Required) Name of s3 bucket to save the results of the query execution.
* `force_destroy` - (Optional, Default: false) A boolean that indicates all tables should be deleted from the database so that the database can be destroyed without error. The tables are *not* recoverable.

~> **NOTE:** When Athena queries are executed, result files may be created in the specified bucket. Consider using `force_destroy` on the bucket too in order to avoid any problems when destroying the bucket.

## Attributes Reference

Expand Down

0 comments on commit 90347f6

Please sign in to comment.