github.com/nalum/terraform@v0.3.2-0.20141223102918-aa2c22ffeff6/builtin/providers/aws/resource_aws_route53_record.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/helper/hashcode"
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  	"github.com/mitchellh/goamz/route53"
    13  )
    14  
    15  func resourceAwsRoute53Record() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceAwsRoute53RecordCreate,
    18  		Read:   resourceAwsRoute53RecordRead,
    19  		Delete: resourceAwsRoute53RecordDelete,
    20  
    21  		Schema: map[string]*schema.Schema{
    22  			"name": &schema.Schema{
    23  				Type:     schema.TypeString,
    24  				Required: true,
    25  				ForceNew: true,
    26  			},
    27  
    28  			"type": &schema.Schema{
    29  				Type:     schema.TypeString,
    30  				Required: true,
    31  				ForceNew: true,
    32  			},
    33  
    34  			"zone_id": &schema.Schema{
    35  				Type:     schema.TypeString,
    36  				Required: true,
    37  				ForceNew: true,
    38  			},
    39  
    40  			"ttl": &schema.Schema{
    41  				Type:     schema.TypeInt,
    42  				Required: true,
    43  				ForceNew: true,
    44  			},
    45  
    46  			"records": &schema.Schema{
    47  				Type:     schema.TypeSet,
    48  				Elem:     &schema.Schema{Type: schema.TypeString},
    49  				Required: true,
    50  				ForceNew: true,
    51  				Set: func(v interface{}) int {
    52  					return hashcode.String(v.(string))
    53  				},
    54  			},
    55  		},
    56  	}
    57  }
    58  
    59  func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) error {
    60  	conn := meta.(*AWSClient).route53
    61  
    62  	// Get the record
    63  	rec, err := resourceAwsRoute53RecordBuildSet(d)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	// Create the new records. We abuse StateChangeConf for this to
    69  	// retry for us since Route53 sometimes returns errors about another
    70  	// operation happening at the same time.
    71  	req := &route53.ChangeResourceRecordSetsRequest{
    72  		Comment: "Managed by Terraform",
    73  		Changes: []route53.Change{
    74  			route53.Change{
    75  				Action: "UPSERT",
    76  				Record: *rec,
    77  			},
    78  		},
    79  	}
    80  	zone := d.Get("zone_id").(string)
    81  	log.Printf("[DEBUG] Creating resource records for zone: %s, name: %s",
    82  		zone, d.Get("name").(string))
    83  
    84  	wait := resource.StateChangeConf{
    85  		Pending:    []string{"rejected"},
    86  		Target:     "accepted",
    87  		Timeout:    5 * time.Minute,
    88  		MinTimeout: 1 * time.Second,
    89  		Refresh: func() (interface{}, string, error) {
    90  			resp, err := conn.ChangeResourceRecordSets(zone, req)
    91  			if err != nil {
    92  				if strings.Contains(err.Error(), "PriorRequestNotComplete") {
    93  					// There is some pending operation, so just retry
    94  					// in a bit.
    95  					return nil, "rejected", nil
    96  				}
    97  
    98  				return nil, "failure", err
    99  			}
   100  
   101  			return resp.ChangeInfo, "accepted", nil
   102  		},
   103  	}
   104  
   105  	respRaw, err := wait.WaitForState()
   106  	if err != nil {
   107  		return err
   108  	}
   109  	changeInfo := respRaw.(route53.ChangeInfo)
   110  
   111  	// Generate an ID
   112  	d.SetId(fmt.Sprintf("%s_%s_%s", zone, d.Get("name").(string), d.Get("type").(string)))
   113  
   114  	// Wait until we are done
   115  	wait = resource.StateChangeConf{
   116  		Delay:      30 * time.Second,
   117  		Pending:    []string{"PENDING"},
   118  		Target:     "INSYNC",
   119  		Timeout:    10 * time.Minute,
   120  		MinTimeout: 5 * time.Second,
   121  		Refresh: func() (result interface{}, state string, err error) {
   122  			return resourceAwsRoute53Wait(conn, changeInfo.ID)
   123  		},
   124  	}
   125  	_, err = wait.WaitForState()
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	return nil
   131  }
   132  
   133  func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) error {
   134  	conn := meta.(*AWSClient).route53
   135  
   136  	zone := d.Get("zone_id").(string)
   137  	lopts := &route53.ListOpts{
   138  		Name: d.Get("name").(string),
   139  		Type: d.Get("type").(string),
   140  	}
   141  	resp, err := conn.ListResourceRecordSets(zone, lopts)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	// Scan for a matching record
   147  	found := false
   148  	for _, record := range resp.Records {
   149  		if route53.FQDN(record.Name) != route53.FQDN(lopts.Name) {
   150  			continue
   151  		}
   152  		if strings.ToUpper(record.Type) != strings.ToUpper(lopts.Type) {
   153  			continue
   154  		}
   155  
   156  		found = true
   157  
   158  		d.Set("records", record.Records)
   159  		d.Set("ttl", record.TTL)
   160  
   161  		break
   162  	}
   163  
   164  	if !found {
   165  		d.SetId("")
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) error {
   172  	conn := meta.(*AWSClient).route53
   173  
   174  	// Get the records
   175  	rec, err := resourceAwsRoute53RecordBuildSet(d)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	// Create the new records
   181  	req := &route53.ChangeResourceRecordSetsRequest{
   182  		Comment: "Deleted by Terraform",
   183  		Changes: []route53.Change{
   184  			route53.Change{
   185  				Action: "DELETE",
   186  				Record: *rec,
   187  			},
   188  		},
   189  	}
   190  	zone := d.Get("zone_id").(string)
   191  	log.Printf("[DEBUG] Deleting resource records for zone: %s, name: %s",
   192  		zone, d.Get("name").(string))
   193  
   194  	wait := resource.StateChangeConf{
   195  		Pending:    []string{"rejected"},
   196  		Target:     "accepted",
   197  		Timeout:    5 * time.Minute,
   198  		MinTimeout: 1 * time.Second,
   199  		Refresh: func() (interface{}, string, error) {
   200  			_, err := conn.ChangeResourceRecordSets(zone, req)
   201  			if err != nil {
   202  				if strings.Contains(err.Error(), "PriorRequestNotComplete") {
   203  					// There is some pending operation, so just retry
   204  					// in a bit.
   205  					return 42, "rejected", nil
   206  				}
   207  
   208  				if strings.Contains(err.Error(), "InvalidChangeBatch") {
   209  					// This means that the record is already gone.
   210  					return 42, "accepted", nil
   211  				}
   212  
   213  				return 42, "failure", err
   214  			}
   215  
   216  			return 42, "accepted", nil
   217  		},
   218  	}
   219  
   220  	if _, err := wait.WaitForState(); err != nil {
   221  		return err
   222  	}
   223  
   224  	return nil
   225  }
   226  
   227  func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData) (*route53.ResourceRecordSet, error) {
   228  	recs := d.Get("records").(*schema.Set).List()
   229  	records := make([]string, 0, len(recs))
   230  
   231  	for _, r := range recs {
   232  		records = append(records, r.(string))
   233  	}
   234  
   235  	rec := &route53.ResourceRecordSet{
   236  		Name:    d.Get("name").(string),
   237  		Type:    d.Get("type").(string),
   238  		TTL:     d.Get("ttl").(int),
   239  		Records: records,
   240  	}
   241  	return rec, nil
   242  }