github.com/anfernee/terraform@v0.6.16-0.20160430000239-06e5085a92f2/builtin/providers/aws/resource_aws_route53_zone.go (about) 1 package aws 2 3 import ( 4 "fmt" 5 "log" 6 "sort" 7 "strings" 8 "time" 9 10 "github.com/hashicorp/terraform/helper/resource" 11 "github.com/hashicorp/terraform/helper/schema" 12 13 "github.com/aws/aws-sdk-go/aws" 14 "github.com/aws/aws-sdk-go/aws/awserr" 15 "github.com/aws/aws-sdk-go/service/route53" 16 ) 17 18 func resourceAwsRoute53Zone() *schema.Resource { 19 return &schema.Resource{ 20 Create: resourceAwsRoute53ZoneCreate, 21 Read: resourceAwsRoute53ZoneRead, 22 Update: resourceAwsRoute53ZoneUpdate, 23 Delete: resourceAwsRoute53ZoneDelete, 24 25 Schema: map[string]*schema.Schema{ 26 "name": &schema.Schema{ 27 Type: schema.TypeString, 28 Required: true, 29 ForceNew: true, 30 }, 31 32 "comment": &schema.Schema{ 33 Type: schema.TypeString, 34 Optional: true, 35 Default: "Managed by Terraform", 36 }, 37 38 "vpc_id": &schema.Schema{ 39 Type: schema.TypeString, 40 Optional: true, 41 ForceNew: true, 42 }, 43 44 "vpc_region": &schema.Schema{ 45 Type: schema.TypeString, 46 Optional: true, 47 ForceNew: true, 48 Computed: true, 49 }, 50 51 "zone_id": &schema.Schema{ 52 Type: schema.TypeString, 53 Computed: true, 54 }, 55 56 "delegation_set_id": &schema.Schema{ 57 Type: schema.TypeString, 58 Optional: true, 59 ForceNew: true, 60 }, 61 62 "name_servers": &schema.Schema{ 63 Type: schema.TypeList, 64 Elem: &schema.Schema{Type: schema.TypeString}, 65 Computed: true, 66 }, 67 68 "tags": tagsSchema(), 69 }, 70 } 71 } 72 73 func resourceAwsRoute53ZoneCreate(d *schema.ResourceData, meta interface{}) error { 74 r53 := meta.(*AWSClient).r53conn 75 76 req := &route53.CreateHostedZoneInput{ 77 Name: aws.String(d.Get("name").(string)), 78 HostedZoneConfig: &route53.HostedZoneConfig{Comment: aws.String(d.Get("comment").(string))}, 79 CallerReference: aws.String(time.Now().Format(time.RFC3339Nano)), 80 } 81 if v := d.Get("vpc_id"); v != "" { 82 req.VPC = &route53.VPC{ 83 VPCId: aws.String(v.(string)), 84 VPCRegion: aws.String(meta.(*AWSClient).region), 85 } 86 if w := d.Get("vpc_region"); w != "" { 87 req.VPC.VPCRegion = aws.String(w.(string)) 88 } 89 d.Set("vpc_region", req.VPC.VPCRegion) 90 } 91 92 if v, ok := d.GetOk("delegation_set_id"); ok { 93 req.DelegationSetId = aws.String(v.(string)) 94 } 95 96 log.Printf("[DEBUG] Creating Route53 hosted zone: %s", *req.Name) 97 var err error 98 resp, err := r53.CreateHostedZone(req) 99 if err != nil { 100 return err 101 } 102 103 // Store the zone_id 104 zone := cleanZoneID(*resp.HostedZone.Id) 105 d.Set("zone_id", zone) 106 d.SetId(zone) 107 108 // Wait until we are done initializing 109 wait := resource.StateChangeConf{ 110 Delay: 30 * time.Second, 111 Pending: []string{"PENDING"}, 112 Target: []string{"INSYNC"}, 113 Timeout: 10 * time.Minute, 114 MinTimeout: 2 * time.Second, 115 Refresh: func() (result interface{}, state string, err error) { 116 changeRequest := &route53.GetChangeInput{ 117 Id: aws.String(cleanChangeID(*resp.ChangeInfo.Id)), 118 } 119 return resourceAwsGoRoute53Wait(r53, changeRequest) 120 }, 121 } 122 _, err = wait.WaitForState() 123 if err != nil { 124 return err 125 } 126 return resourceAwsRoute53ZoneUpdate(d, meta) 127 } 128 129 func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error { 130 r53 := meta.(*AWSClient).r53conn 131 zone, err := r53.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(d.Id())}) 132 if err != nil { 133 // Handle a deleted zone 134 if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" { 135 d.SetId("") 136 return nil 137 } 138 return err 139 } 140 141 // In the import case this will be empty 142 if _, ok := d.GetOk("zone_id"); !ok { 143 d.Set("zone_id", d.Id()) 144 } 145 if _, ok := d.GetOk("name"); !ok { 146 d.Set("name", zone.HostedZone.Name) 147 } 148 149 if !*zone.HostedZone.Config.PrivateZone { 150 ns := make([]string, len(zone.DelegationSet.NameServers)) 151 for i := range zone.DelegationSet.NameServers { 152 ns[i] = *zone.DelegationSet.NameServers[i] 153 } 154 sort.Strings(ns) 155 if err := d.Set("name_servers", ns); err != nil { 156 return fmt.Errorf("[DEBUG] Error setting name servers for: %s, error: %#v", d.Id(), err) 157 } 158 } else { 159 ns, err := getNameServers(d.Id(), d.Get("name").(string), r53) 160 if err != nil { 161 return err 162 } 163 if err := d.Set("name_servers", ns); err != nil { 164 return fmt.Errorf("[DEBUG] Error setting name servers for: %s, error: %#v", d.Id(), err) 165 } 166 167 // In the import case we just associate it with the first VPC 168 if _, ok := d.GetOk("vpc_id"); !ok { 169 if len(zone.VPCs) > 1 { 170 return fmt.Errorf( 171 "Can't import a route53_zone with more than one VPC attachment") 172 } 173 174 if len(zone.VPCs) > 0 { 175 d.Set("vpc_id", zone.VPCs[0].VPCId) 176 d.Set("vpc_region", zone.VPCs[0].VPCRegion) 177 } 178 } 179 180 var associatedVPC *route53.VPC 181 for _, vpc := range zone.VPCs { 182 if *vpc.VPCId == d.Get("vpc_id") { 183 associatedVPC = vpc 184 break 185 } 186 } 187 if associatedVPC == nil { 188 return fmt.Errorf("[DEBUG] VPC: %v is not associated with Zone: %v", d.Get("vpc_id"), d.Id()) 189 } 190 } 191 192 if zone.DelegationSet != nil && zone.DelegationSet.Id != nil { 193 d.Set("delegation_set_id", cleanDelegationSetId(*zone.DelegationSet.Id)) 194 } 195 196 if zone.HostedZone != nil && zone.HostedZone.Config != nil && zone.HostedZone.Config.Comment != nil { 197 d.Set("comment", zone.HostedZone.Config.Comment) 198 } 199 200 // get tags 201 req := &route53.ListTagsForResourceInput{ 202 ResourceId: aws.String(d.Id()), 203 ResourceType: aws.String("hostedzone"), 204 } 205 206 resp, err := r53.ListTagsForResource(req) 207 if err != nil { 208 return err 209 } 210 211 var tags []*route53.Tag 212 if resp.ResourceTagSet != nil { 213 tags = resp.ResourceTagSet.Tags 214 } 215 216 if err := d.Set("tags", tagsToMapR53(tags)); err != nil { 217 return err 218 } 219 220 return nil 221 } 222 223 func resourceAwsRoute53ZoneUpdate(d *schema.ResourceData, meta interface{}) error { 224 conn := meta.(*AWSClient).r53conn 225 226 d.Partial(true) 227 228 if d.HasChange("comment") { 229 zoneInput := route53.UpdateHostedZoneCommentInput{ 230 Id: aws.String(d.Id()), 231 Comment: aws.String(d.Get("comment").(string)), 232 } 233 234 _, err := conn.UpdateHostedZoneComment(&zoneInput) 235 if err != nil { 236 return err 237 } else { 238 d.SetPartial("comment") 239 } 240 } 241 242 if err := setTagsR53(conn, d, "hostedzone"); err != nil { 243 return err 244 } else { 245 d.SetPartial("tags") 246 } 247 248 d.Partial(false) 249 250 return resourceAwsRoute53ZoneRead(d, meta) 251 } 252 253 func resourceAwsRoute53ZoneDelete(d *schema.ResourceData, meta interface{}) error { 254 r53 := meta.(*AWSClient).r53conn 255 256 log.Printf("[DEBUG] Deleting Route53 hosted zone: %s (ID: %s)", 257 d.Get("name").(string), d.Id()) 258 _, err := r53.DeleteHostedZone(&route53.DeleteHostedZoneInput{Id: aws.String(d.Id())}) 259 if err != nil { 260 if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" { 261 log.Printf("[DEBUG] No matching Route 53 Zone found for: %s, removing from state file", d.Id()) 262 d.SetId("") 263 return nil 264 } 265 return err 266 } 267 268 return nil 269 } 270 271 func resourceAwsGoRoute53Wait(r53 *route53.Route53, ref *route53.GetChangeInput) (result interface{}, state string, err error) { 272 273 status, err := r53.GetChange(ref) 274 if err != nil { 275 return nil, "UNKNOWN", err 276 } 277 return true, *status.ChangeInfo.Status, nil 278 } 279 280 // cleanChangeID is used to remove the leading /change/ 281 func cleanChangeID(ID string) string { 282 return cleanPrefix(ID, "/change/") 283 } 284 285 // cleanZoneID is used to remove the leading /hostedzone/ 286 func cleanZoneID(ID string) string { 287 return cleanPrefix(ID, "/hostedzone/") 288 } 289 290 // cleanPrefix removes a string prefix from an ID 291 func cleanPrefix(ID, prefix string) string { 292 if strings.HasPrefix(ID, prefix) { 293 ID = strings.TrimPrefix(ID, prefix) 294 } 295 return ID 296 } 297 298 func getNameServers(zoneId string, zoneName string, r53 *route53.Route53) ([]string, error) { 299 resp, err := r53.ListResourceRecordSets(&route53.ListResourceRecordSetsInput{ 300 HostedZoneId: aws.String(zoneId), 301 StartRecordName: aws.String(zoneName), 302 StartRecordType: aws.String("NS"), 303 }) 304 if err != nil { 305 return nil, err 306 } 307 if len(resp.ResourceRecordSets) == 0 { 308 return nil, nil 309 } 310 ns := make([]string, len(resp.ResourceRecordSets[0].ResourceRecords)) 311 for i := range resp.ResourceRecordSets[0].ResourceRecords { 312 ns[i] = *resp.ResourceRecordSets[0].ResourceRecords[i].Value 313 } 314 sort.Strings(ns) 315 return ns, nil 316 }