github.com/rmenn/terraform@v0.3.8-0.20150225065417-fc84b3a78802/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  
    13  	"github.com/awslabs/aws-sdk-go/aws"
    14  	"github.com/awslabs/aws-sdk-go/gen/route53"
    15  )
    16  
    17  func resourceAwsRoute53Record() *schema.Resource {
    18  	return &schema.Resource{
    19  		Create: resourceAwsRoute53RecordCreate,
    20  		Read:   resourceAwsRoute53RecordRead,
    21  		Delete: resourceAwsRoute53RecordDelete,
    22  
    23  		Schema: map[string]*schema.Schema{
    24  			"name": &schema.Schema{
    25  				Type:     schema.TypeString,
    26  				Required: true,
    27  				ForceNew: true,
    28  			},
    29  
    30  			"type": &schema.Schema{
    31  				Type:     schema.TypeString,
    32  				Required: true,
    33  				ForceNew: true,
    34  			},
    35  
    36  			"zone_id": &schema.Schema{
    37  				Type:     schema.TypeString,
    38  				Required: true,
    39  				ForceNew: true,
    40  			},
    41  
    42  			"ttl": &schema.Schema{
    43  				Type:     schema.TypeInt,
    44  				Required: true,
    45  				ForceNew: true,
    46  			},
    47  
    48  			"records": &schema.Schema{
    49  				Type:     schema.TypeSet,
    50  				Elem:     &schema.Schema{Type: schema.TypeString},
    51  				Required: true,
    52  				ForceNew: true,
    53  				Set: func(v interface{}) int {
    54  					return hashcode.String(v.(string))
    55  				},
    56  			},
    57  		},
    58  	}
    59  }
    60  
    61  func resourceAwsRoute53RecordCreate(d *schema.ResourceData, meta interface{}) error {
    62  	conn := meta.(*AWSClient).r53conn
    63  	zone := d.Get("zone_id").(string)
    64  
    65  	zoneRecord, err := conn.GetHostedZone(&route53.GetHostedZoneRequest{ID: aws.String(zone)})
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	// Check if the current record name contains the zone suffix.
    71  	// If it does not, add the zone name to form a fully qualified name
    72  	// and keep AWS happy.
    73  	recordName := d.Get("name").(string)
    74  	zoneName := strings.Trim(*zoneRecord.HostedZone.Name, ".")
    75  	if !strings.HasSuffix(recordName, zoneName) {
    76  		d.Set("name", strings.Join([]string{recordName, zoneName}, "."))
    77  	}
    78  
    79  	// Get the record
    80  	rec, err := resourceAwsRoute53RecordBuildSet(d)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	// Create the new records. We abuse StateChangeConf for this to
    86  	// retry for us since Route53 sometimes returns errors about another
    87  	// operation happening at the same time.
    88  	changeBatch := &route53.ChangeBatch{
    89  		Comment: aws.String("Managed by Terraform"),
    90  		Changes: []route53.Change{
    91  			route53.Change{
    92  				Action:            aws.String("UPSERT"),
    93  				ResourceRecordSet: rec,
    94  			},
    95  		},
    96  	}
    97  
    98  	req := &route53.ChangeResourceRecordSetsRequest{
    99  		HostedZoneID: aws.String(cleanZoneID(*zoneRecord.HostedZone.ID)),
   100  		ChangeBatch:  changeBatch,
   101  	}
   102  
   103  	log.Printf("[DEBUG] Creating resource records for zone: %s, name: %s",
   104  		zone, d.Get("name").(string))
   105  
   106  	wait := resource.StateChangeConf{
   107  		Pending:    []string{"rejected"},
   108  		Target:     "accepted",
   109  		Timeout:    5 * time.Minute,
   110  		MinTimeout: 1 * time.Second,
   111  		Refresh: func() (interface{}, string, error) {
   112  			resp, err := conn.ChangeResourceRecordSets(req)
   113  			if err != nil {
   114  				if strings.Contains(err.Error(), "PriorRequestNotComplete") {
   115  					// There is some pending operation, so just retry
   116  					// in a bit.
   117  					return nil, "rejected", nil
   118  				}
   119  
   120  				return nil, "failure", err
   121  			}
   122  
   123  			return resp, "accepted", nil
   124  		},
   125  	}
   126  
   127  	respRaw, err := wait.WaitForState()
   128  	if err != nil {
   129  		return err
   130  	}
   131  	changeInfo := respRaw.(*route53.ChangeResourceRecordSetsResponse).ChangeInfo
   132  
   133  	// Generate an ID
   134  	d.SetId(fmt.Sprintf("%s_%s_%s", zone, d.Get("name").(string), d.Get("type").(string)))
   135  
   136  	// Wait until we are done
   137  	wait = resource.StateChangeConf{
   138  		Delay:      30 * time.Second,
   139  		Pending:    []string{"PENDING"},
   140  		Target:     "INSYNC",
   141  		Timeout:    10 * time.Minute,
   142  		MinTimeout: 5 * time.Second,
   143  		Refresh: func() (result interface{}, state string, err error) {
   144  			changeRequest := &route53.GetChangeRequest{
   145  				ID: aws.String(cleanChangeID(*changeInfo.ID)),
   146  			}
   147  			return resourceAwsGoRoute53Wait(conn, changeRequest)
   148  		},
   149  	}
   150  	_, err = wait.WaitForState()
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  func resourceAwsRoute53RecordRead(d *schema.ResourceData, meta interface{}) error {
   159  	conn := meta.(*AWSClient).r53conn
   160  
   161  	zone := d.Get("zone_id").(string)
   162  	lopts := &route53.ListResourceRecordSetsRequest{
   163  		HostedZoneID:    aws.String(cleanZoneID(zone)),
   164  		StartRecordName: aws.String(d.Get("name").(string)),
   165  		StartRecordType: aws.String(d.Get("type").(string)),
   166  	}
   167  
   168  	resp, err := conn.ListResourceRecordSets(lopts)
   169  	if err != nil {
   170  		return err
   171  	}
   172  
   173  	// Scan for a matching record
   174  	found := false
   175  	for _, record := range resp.ResourceRecordSets {
   176  		if FQDN(*record.Name) != FQDN(*lopts.StartRecordName) {
   177  			continue
   178  		}
   179  		if strings.ToUpper(*record.Type) != strings.ToUpper(*lopts.StartRecordType) {
   180  			continue
   181  		}
   182  
   183  		found = true
   184  
   185  		d.Set("records", record.ResourceRecords)
   186  		d.Set("ttl", record.TTL)
   187  
   188  		break
   189  	}
   190  
   191  	if !found {
   192  		d.SetId("")
   193  	}
   194  
   195  	return nil
   196  }
   197  
   198  func resourceAwsRoute53RecordDelete(d *schema.ResourceData, meta interface{}) error {
   199  	conn := meta.(*AWSClient).r53conn
   200  
   201  	zone := d.Get("zone_id").(string)
   202  	log.Printf("[DEBUG] Deleting resource records for zone: %s, name: %s",
   203  		zone, d.Get("name").(string))
   204  
   205  	// Get the records
   206  	rec, err := resourceAwsRoute53RecordBuildSet(d)
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	// Create the new records
   212  	changeBatch := &route53.ChangeBatch{
   213  		Comment: aws.String("Deleted by Terraform"),
   214  		Changes: []route53.Change{
   215  			route53.Change{
   216  				Action:            aws.String("DELETE"),
   217  				ResourceRecordSet: rec,
   218  			},
   219  		},
   220  	}
   221  
   222  	req := &route53.ChangeResourceRecordSetsRequest{
   223  		HostedZoneID: aws.String(cleanZoneID(zone)),
   224  		ChangeBatch:  changeBatch,
   225  	}
   226  
   227  	wait := resource.StateChangeConf{
   228  		Pending:    []string{"rejected"},
   229  		Target:     "accepted",
   230  		Timeout:    5 * time.Minute,
   231  		MinTimeout: 1 * time.Second,
   232  		Refresh: func() (interface{}, string, error) {
   233  			_, err := conn.ChangeResourceRecordSets(req)
   234  			if err != nil {
   235  				if strings.Contains(err.Error(), "PriorRequestNotComplete") {
   236  					// There is some pending operation, so just retry
   237  					// in a bit.
   238  					return 42, "rejected", nil
   239  				}
   240  
   241  				if strings.Contains(err.Error(), "InvalidChangeBatch") {
   242  					// This means that the record is already gone.
   243  					return 42, "accepted", nil
   244  				}
   245  
   246  				return 42, "failure", err
   247  			}
   248  
   249  			return 42, "accepted", nil
   250  		},
   251  	}
   252  
   253  	if _, err := wait.WaitForState(); err != nil {
   254  		return err
   255  	}
   256  
   257  	return nil
   258  }
   259  
   260  func resourceAwsRoute53RecordBuildSet(d *schema.ResourceData) (*route53.ResourceRecordSet, error) {
   261  	recs := d.Get("records").(*schema.Set).List()
   262  	records := make([]route53.ResourceRecord, 0, len(recs))
   263  
   264  	for _, r := range recs {
   265  		records = append(records, route53.ResourceRecord{Value: aws.String(r.(string))})
   266  	}
   267  
   268  	rec := &route53.ResourceRecordSet{
   269  		Name:            aws.String(d.Get("name").(string)),
   270  		Type:            aws.String(d.Get("type").(string)),
   271  		TTL:             aws.Long(int64(d.Get("ttl").(int))),
   272  		ResourceRecords: records,
   273  	}
   274  	return rec, nil
   275  }
   276  
   277  func FQDN(name string) string {
   278  	n := len(name)
   279  	if n == 0 || name[n-1] == '.' {
   280  		return name
   281  	} else {
   282  		return name + "."
   283  	}
   284  }