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