github.com/arvindram03/terraform@v0.3.7-0.20150212015210-408f838db36d/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  	zone := d.Get("zone_id").(string)
    63  
    64  	zoneRecord, err := conn.GetHostedZone(zone)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	// Check if the current record name contains the zone suffix.
    70  	// If it does not, add the zone name to form a fully qualified name
    71  	// and keep AWS happy.
    72  	recordName := d.Get("name").(string)
    73  	zoneName := strings.Trim(zoneRecord.HostedZone.Name, ".")
    74  	if !strings.HasSuffix(recordName, zoneName) {
    75  		d.Set("name", strings.Join([]string{recordName, zoneName}, "."))
    76  	}
    77  
    78  	// Get the record
    79  	rec, err := resourceAwsRoute53RecordBuildSet(d)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	// Create the new records. We abuse StateChangeConf for this to
    85  	// retry for us since Route53 sometimes returns errors about another
    86  	// operation happening at the same time.
    87  	req := &route53.ChangeResourceRecordSetsRequest{
    88  		Comment: "Managed by Terraform",
    89  		Changes: []route53.Change{
    90  			route53.Change{
    91  				Action: "UPSERT",
    92  				Record: *rec,
    93  			},
    94  		},
    95  	}
    96  
    97  	log.Printf("[DEBUG] Creating resource records for zone: %s, name: %s",
    98  		zone, d.Get("name").(string))
    99  
   100  	wait := resource.StateChangeConf{
   101  		Pending:    []string{"rejected"},
   102  		Target:     "accepted",
   103  		Timeout:    5 * time.Minute,
   104  		MinTimeout: 1 * time.Second,
   105  		Refresh: func() (interface{}, string, error) {
   106  			resp, err := conn.ChangeResourceRecordSets(zone, req)
   107  			if err != nil {
   108  				if strings.Contains(err.Error(), "PriorRequestNotComplete") {
   109  					// There is some pending operation, so just retry
   110  					// in a bit.
   111  					return nil, "rejected", nil
   112  				}
   113  
   114  				return nil, "failure", err
   115  			}
   116  
   117  			return resp.ChangeInfo, "accepted", nil
   118  		},
   119  	}
   120  
   121  	respRaw, err := wait.WaitForState()
   122  	if err != nil {
   123  		return err
   124  	}
   125  	changeInfo := respRaw.(route53.ChangeInfo)
   126  
   127  	// Generate an ID
   128  	d.SetId(fmt.Sprintf("%s_%s_%s", zone, d.Get("name").(string), d.Get("type").(string)))
   129  
   130  	// Wait until we are done
   131  	wait = resource.StateChangeConf{
   132  		Delay:      30 * time.Second,
   133  		Pending:    []string{"PENDING"},
   134  		Target:     "INSYNC",
   135  		Timeout:    10 * time.Minute,
   136  		MinTimeout: 5 * time.Second,
   137  		Refresh: func() (result interface{}, state string, err error) {
   138  			return resourceAwsRoute53Wait(conn, changeInfo.ID)
   139  		},
   140  	}
   141  	_, err = wait.WaitForState()
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	return nil
   147  }
   148  
   149  func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) error {
   150  	conn := meta.(*AWSClient).route53
   151  
   152  	zone := d.Get("zone_id").(string)
   153  	lopts := &route53.ListOpts{
   154  		Name: d.Get("name").(string),
   155  		Type: d.Get("type").(string),
   156  	}
   157  	resp, err := conn.ListResourceRecordSets(zone, lopts)
   158  	if err != nil {
   159  		return err
   160  	}
   161  
   162  	// Scan for a matching record
   163  	found := false
   164  	for _, record := range resp.Records {
   165  		if route53.FQDN(record.Name) != route53.FQDN(lopts.Name) {
   166  			continue
   167  		}
   168  		if strings.ToUpper(record.Type) != strings.ToUpper(lopts.Type) {
   169  			continue
   170  		}
   171  
   172  		found = true
   173  
   174  		d.Set("records", record.Records)
   175  		d.Set("ttl", record.TTL)
   176  
   177  		break
   178  	}
   179  
   180  	if !found {
   181  		d.SetId("")
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) error {
   188  	conn := meta.(*AWSClient).route53
   189  
   190  	// Get the records
   191  	rec, err := resourceAwsRoute53RecordBuildSet(d)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	// Create the new records
   197  	req := &route53.ChangeResourceRecordSetsRequest{
   198  		Comment: "Deleted by Terraform",
   199  		Changes: []route53.Change{
   200  			route53.Change{
   201  				Action: "DELETE",
   202  				Record: *rec,
   203  			},
   204  		},
   205  	}
   206  	zone := d.Get("zone_id").(string)
   207  	log.Printf("[DEBUG] Deleting resource records for zone: %s, name: %s",
   208  		zone, d.Get("name").(string))
   209  
   210  	wait := resource.StateChangeConf{
   211  		Pending:    []string{"rejected"},
   212  		Target:     "accepted",
   213  		Timeout:    5 * time.Minute,
   214  		MinTimeout: 1 * time.Second,
   215  		Refresh: func() (interface{}, string, error) {
   216  			_, err := conn.ChangeResourceRecordSets(zone, req)
   217  			if err != nil {
   218  				if strings.Contains(err.Error(), "PriorRequestNotComplete") {
   219  					// There is some pending operation, so just retry
   220  					// in a bit.
   221  					return 42, "rejected", nil
   222  				}
   223  
   224  				if strings.Contains(err.Error(), "InvalidChangeBatch") {
   225  					// This means that the record is already gone.
   226  					return 42, "accepted", nil
   227  				}
   228  
   229  				return 42, "failure", err
   230  			}
   231  
   232  			return 42, "accepted", nil
   233  		},
   234  	}
   235  
   236  	if _, err := wait.WaitForState(); err != nil {
   237  		return err
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData) (*route53.ResourceRecordSet, error) {
   244  	recs := d.Get("records").(*schema.Set).List()
   245  	records := make([]string, 0, len(recs))
   246  
   247  	for _, r := range recs {
   248  		records = append(records, r.(string))
   249  	}
   250  
   251  	rec := &route53.ResourceRecordSet{
   252  		Name:    d.Get("name").(string),
   253  		Type:    d.Get("type").(string),
   254  		TTL:     d.Get("ttl").(int),
   255  		Records: records,
   256  	}
   257  	return rec, nil
   258  }