Merge pull request #8239 from TimeIncOSS/f-aws-r53-zone-force-destroy
provider/aws: Add force_destroy option to aws_route53_zone
This commit is contained in:
commit
523627ba24
|
@ -19,9 +19,10 @@ func TestAccAWSRoute53Zone_importBasic(t *testing.T) {
|
|||
},
|
||||
|
||||
resource.TestStep{
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
ResourceName: resourceName,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
ImportStateVerifyIgnore: []string{"force_destroy"},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -269,13 +269,38 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er
|
|||
log.Printf("[DEBUG] Creating resource records for zone: %s, name: %s\n\n%s",
|
||||
zone, *rec.Name, req)
|
||||
|
||||
respRaw, err := changeRoute53RecordSet(conn, req)
|
||||
|
||||
changeInfo := respRaw.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo
|
||||
|
||||
// Generate an ID
|
||||
vars := []string{
|
||||
zone,
|
||||
strings.ToLower(d.Get("name").(string)),
|
||||
d.Get("type").(string),
|
||||
}
|
||||
if v, ok := d.GetOk("set_identifier"); ok {
|
||||
vars = append(vars, v.(string))
|
||||
}
|
||||
|
||||
d.SetId(strings.Join(vars, "_"))
|
||||
|
||||
err = waitForRoute53RecordSetToSync(conn, cleanChangeID(*changeInfo.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceAwsRoute53RecordRead(d, meta)
|
||||
}
|
||||
|
||||
func changeRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResourceRecordSetsInput) (interface{}, error) {
|
||||
wait := resource.StateChangeConf{
|
||||
Pending: []string{"rejected"},
|
||||
Target: []string{"accepted"},
|
||||
Timeout: 5 * time.Minute,
|
||||
MinTimeout: 1 * time.Second,
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
resp, err := conn.ChangeResourceRecordSets(req)
|
||||
resp, err := conn.ChangeResourceRecordSets(input)
|
||||
if err != nil {
|
||||
if r53err, ok := err.(awserr.Error); ok {
|
||||
if r53err.Code() == "PriorRequestNotComplete" {
|
||||
|
@ -292,26 +317,11 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er
|
|||
},
|
||||
}
|
||||
|
||||
respRaw, err := wait.WaitForState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changeInfo := respRaw.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo
|
||||
return wait.WaitForState()
|
||||
}
|
||||
|
||||
// Generate an ID
|
||||
vars := []string{
|
||||
zone,
|
||||
strings.ToLower(d.Get("name").(string)),
|
||||
d.Get("type").(string),
|
||||
}
|
||||
if v, ok := d.GetOk("set_identifier"); ok {
|
||||
vars = append(vars, v.(string))
|
||||
}
|
||||
|
||||
d.SetId(strings.Join(vars, "_"))
|
||||
|
||||
// Wait until we are done
|
||||
wait = resource.StateChangeConf{
|
||||
func waitForRoute53RecordSetToSync(conn *route53.Route53, requestId string) error {
|
||||
wait := resource.StateChangeConf{
|
||||
Delay: 30 * time.Second,
|
||||
Pending: []string{"PENDING"},
|
||||
Target: []string{"INSYNC"},
|
||||
|
@ -319,17 +329,13 @@ func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) er
|
|||
MinTimeout: 5 * time.Second,
|
||||
Refresh: func() (result interface{}, state string, err error) {
|
||||
changeRequest := &route53.GetChangeInput{
|
||||
Id: aws.String(cleanChangeID(*changeInfo.Id)),
|
||||
Id: aws.String(requestId),
|
||||
}
|
||||
return resourceAwsGoRoute53Wait(conn, changeRequest)
|
||||
},
|
||||
}
|
||||
_, err = wait.WaitForState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceAwsRoute53RecordRead(d, meta)
|
||||
_, err := wait.WaitForState()
|
||||
return err
|
||||
}
|
||||
|
||||
func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) error {
|
||||
|
@ -518,13 +524,18 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er
|
|||
ChangeBatch: changeBatch,
|
||||
}
|
||||
|
||||
_, err = deleteRoute53RecordSet(conn, req)
|
||||
return err
|
||||
}
|
||||
|
||||
func deleteRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResourceRecordSetsInput) (interface{}, error) {
|
||||
wait := resource.StateChangeConf{
|
||||
Pending: []string{"rejected"},
|
||||
Target: []string{"accepted"},
|
||||
Timeout: 5 * time.Minute,
|
||||
MinTimeout: 1 * time.Second,
|
||||
Refresh: func() (interface{}, string, error) {
|
||||
_, err := conn.ChangeResourceRecordSets(req)
|
||||
resp, err := conn.ChangeResourceRecordSets(input)
|
||||
if err != nil {
|
||||
if r53err, ok := err.(awserr.Error); ok {
|
||||
if r53err.Code() == "PriorRequestNotComplete" {
|
||||
|
@ -535,22 +546,18 @@ func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) er
|
|||
|
||||
if r53err.Code() == "InvalidChangeBatch" {
|
||||
// This means that the record is already gone.
|
||||
return 42, "accepted", nil
|
||||
return resp, "accepted", nil
|
||||
}
|
||||
}
|
||||
|
||||
return 42, "failure", err
|
||||
}
|
||||
|
||||
return 42, "accepted", nil
|
||||
return resp, "accepted", nil
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := wait.WaitForState(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return wait.WaitForState()
|
||||
}
|
||||
|
||||
func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData, zoneName string) (*route53.ResourceRecordSet, error) {
|
||||
|
|
|
@ -71,6 +71,12 @@ func resourceAwsRoute53Zone() *schema.Resource {
|
|||
},
|
||||
|
||||
"tags": tagsSchema(),
|
||||
|
||||
"force_destroy": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -258,6 +264,10 @@ func resourceAwsRoute53ZoneUpdate(d *schema.ResourceData, meta interface{}) erro
|
|||
func resourceAwsRoute53ZoneDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
r53 := meta.(*AWSClient).r53conn
|
||||
|
||||
if d.Get("force_destroy").(bool) {
|
||||
deleteAllRecordsInHostedZoneId(d.Id(), d.Get("name").(string), r53)
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Deleting Route53 hosted zone: %s (ID: %s)",
|
||||
d.Get("name").(string), d.Id())
|
||||
_, err := r53.DeleteHostedZone(&route53.DeleteHostedZoneInput{Id: aws.String(d.Id())})
|
||||
|
@ -273,6 +283,59 @@ func resourceAwsRoute53ZoneDelete(d *schema.ResourceData, meta interface{}) erro
|
|||
return nil
|
||||
}
|
||||
|
||||
func deleteAllRecordsInHostedZoneId(hostedZoneId, hostedZoneName string, conn *route53.Route53) error {
|
||||
input := &route53.ListResourceRecordSetsInput{
|
||||
HostedZoneId: aws.String(hostedZoneId),
|
||||
}
|
||||
|
||||
var lastDeleteErr, lastErrorFromWaiter error
|
||||
var pageNum = 0
|
||||
err := conn.ListResourceRecordSetsPages(input, func(page *route53.ListResourceRecordSetsOutput, isLastPage bool) bool {
|
||||
sets := page.ResourceRecordSets
|
||||
pageNum += 1
|
||||
|
||||
changes := make([]*route53.Change, 0)
|
||||
// 100 items per page returned by default
|
||||
for _, set := range sets {
|
||||
if *set.Name == hostedZoneName+"." && (*set.Type == "NS" || *set.Type == "SOA") {
|
||||
// Zone NS & SOA records cannot be deleted
|
||||
continue
|
||||
}
|
||||
changes = append(changes, &route53.Change{
|
||||
Action: aws.String("DELETE"),
|
||||
ResourceRecordSet: set,
|
||||
})
|
||||
}
|
||||
log.Printf("[DEBUG] Deleting %d records (page %d) from %s",
|
||||
len(changes), pageNum, hostedZoneId)
|
||||
|
||||
req := &route53.ChangeResourceRecordSetsInput{
|
||||
HostedZoneId: aws.String(hostedZoneId),
|
||||
ChangeBatch: &route53.ChangeBatch{
|
||||
Comment: aws.String("Deleted by Terraform"),
|
||||
Changes: changes,
|
||||
},
|
||||
}
|
||||
|
||||
var resp interface{}
|
||||
resp, lastDeleteErr = deleteRoute53RecordSet(conn, req)
|
||||
if out, ok := resp.(*route53.ChangeResourceRecordSetsOutput); ok {
|
||||
log.Printf("[DEBUG] Waiting for change batch to become INSYNC: %#v", out)
|
||||
lastErrorFromWaiter = waitForRoute53RecordSetToSync(conn, cleanChangeID(*out.ChangeInfo.Id))
|
||||
} else {
|
||||
log.Printf("[DEBUG] Unable to wait for change batch because of an error: %s", lastDeleteErr)
|
||||
}
|
||||
|
||||
return !isLastPage
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed listing/deleting record sets: %s\nLast error from deletion: %s\nLast error from waiter: %s",
|
||||
err, lastDeleteErr, lastErrorFromWaiter)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceAwsGoRoute53Wait(r53 *route53.Route53, ref *route53.GetChangeInput) (result interface{}, state string, err error) {
|
||||
|
||||
status, err := r53.GetChange(ref)
|
||||
|
|
|
@ -2,9 +2,11 @@ package aws
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/acctest"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
@ -86,6 +88,39 @@ func TestAccAWSRoute53Zone_basic(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestAccAWSRoute53Zone_forceDestroy(t *testing.T) {
|
||||
var zone route53.GetHostedZoneOutput
|
||||
|
||||
// record the initialized providers so that we can use them to
|
||||
// check for the instances in each region
|
||||
var providers []*schema.Provider
|
||||
providerFactories := map[string]terraform.ResourceProviderFactory{
|
||||
"aws": func() (terraform.ResourceProvider, error) {
|
||||
p := Provider()
|
||||
providers = append(providers, p.(*schema.Provider))
|
||||
return p, nil
|
||||
},
|
||||
}
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
IDRefreshName: "aws_route53_zone.destroyable",
|
||||
ProviderFactories: providerFactories,
|
||||
CheckDestroy: testAccCheckRoute53ZoneDestroyWithProviders(&providers),
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccRoute53ZoneConfig_forceDestroy,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckRoute53ZoneExistsWithProviders("aws_route53_zone.destroyable", &zone, &providers),
|
||||
// Add >100 records to verify pagination works ok
|
||||
testAccCreateRandomRoute53RecordsInZoneIdWithProviders(&providers, &zone, 100),
|
||||
testAccCreateRandomRoute53RecordsInZoneIdWithProviders(&providers, &zone, 5),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccAWSRoute53Zone_updateComment(t *testing.T) {
|
||||
var zone route53.GetHostedZoneOutput
|
||||
var td route53.ResourceTagSet
|
||||
|
@ -204,6 +239,59 @@ func testAccCheckRoute53ZoneDestroyWithProvider(s *terraform.State, provider *sc
|
|||
return nil
|
||||
}
|
||||
|
||||
func testAccCreateRandomRoute53RecordsInZoneIdWithProviders(providers *[]*schema.Provider,
|
||||
zone *route53.GetHostedZoneOutput, recordsCount int) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
for _, provider := range *providers {
|
||||
if provider.Meta() == nil {
|
||||
continue
|
||||
}
|
||||
if err := testAccCreateRandomRoute53RecordsInZoneId(provider, zone, recordsCount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCreateRandomRoute53RecordsInZoneId(provider *schema.Provider, zone *route53.GetHostedZoneOutput, recordsCount int) error {
|
||||
conn := provider.Meta().(*AWSClient).r53conn
|
||||
|
||||
var changes []*route53.Change
|
||||
if recordsCount > 100 {
|
||||
return fmt.Errorf("Route53 API only allows 100 record sets in a single batch")
|
||||
}
|
||||
for i := 0; i < recordsCount; i++ {
|
||||
changes = append(changes, &route53.Change{
|
||||
Action: aws.String("UPSERT"),
|
||||
ResourceRecordSet: &route53.ResourceRecordSet{
|
||||
Name: aws.String(fmt.Sprintf("%d-tf-acc-random.%s", acctest.RandInt(), *zone.HostedZone.Name)),
|
||||
Type: aws.String("CNAME"),
|
||||
ResourceRecords: []*route53.ResourceRecord{
|
||||
&route53.ResourceRecord{Value: aws.String(fmt.Sprintf("random.%s", *zone.HostedZone.Name))},
|
||||
},
|
||||
TTL: aws.Int64(int64(30)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
req := &route53.ChangeResourceRecordSetsInput{
|
||||
HostedZoneId: zone.HostedZone.Id,
|
||||
ChangeBatch: &route53.ChangeBatch{
|
||||
Comment: aws.String("Generated by Terraform"),
|
||||
Changes: changes,
|
||||
},
|
||||
}
|
||||
log.Printf("[DEBUG] Change set: %s\n", *req)
|
||||
resp, err := changeRoute53RecordSet(conn, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changeInfo := resp.(*route53.ChangeResourceRecordSetsOutput).ChangeInfo
|
||||
err = waitForRoute53RecordSetToSync(conn, cleanChangeID(*changeInfo.Id))
|
||||
return err
|
||||
}
|
||||
|
||||
func testAccCheckRoute53ZoneExists(n string, zone *route53.GetHostedZoneOutput) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
return testAccCheckRoute53ZoneExistsWithProvider(s, n, zone, testAccProvider)
|
||||
|
@ -324,6 +412,13 @@ resource "aws_route53_zone" "main" {
|
|||
}
|
||||
`
|
||||
|
||||
const testAccRoute53ZoneConfig_forceDestroy = `
|
||||
resource "aws_route53_zone" "destroyable" {
|
||||
name = "terraform.io"
|
||||
force_destroy = true
|
||||
}
|
||||
`
|
||||
|
||||
const testAccRoute53ZoneConfigUpdateComment = `
|
||||
resource "aws_route53_zone" "main" {
|
||||
name = "hashicorp.com."
|
||||
|
|
|
@ -61,6 +61,8 @@ The following arguments are supported:
|
|||
* `vpc_region` - (Optional) The VPC's region. Defaults to the region of the AWS provider.
|
||||
* `delegation_set_id` - (Optional) The ID of the reusable delgation set whose NS records you want to assign to the hosted zone.
|
||||
Conflicts w/ `vpc_id` as delegation sets can only be used for public zones.
|
||||
* `force_destroy` - (Optional) Whether to destroy all records (possibly managed outside of Terraform)
|
||||
in the zone when destroying the zone.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
|
|
Loading…
Reference in New Issue