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 }