github.com/bengesoff/terraform@v0.3.1-0.20141018223233-b25a53629922/builtin/providers/aws/resource_aws_route53_record.go (about)

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