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