// setTags is a helper to set the tags for a resource. It expects the // tags field to be named "tags" func setTagsR53(conn *route53.Route53, d *schema.ResourceData, resourceType string) error { if d.HasChange("tags") { oraw, nraw := d.GetChange("tags") o := oraw.(map[string]interface{}) n := nraw.(map[string]interface{}) create, remove := diffTagsR53(tagsFromMapR53(o), tagsFromMapR53(n)) // Set tags r := make([]*string, len(remove)) for i, t := range remove { r[i] = t.Key } log.Printf("[DEBUG] Changing tags: \n\tadding: %#v\n\tremoving:%#v", create, remove) req := &route53.ChangeTagsForResourceInput{ ResourceId: aws.String(d.Id()), ResourceType: aws.String(resourceType), } if len(create) > 0 { req.AddTags = create } if len(r) > 0 { req.RemoveTagKeys = r } _, err := conn.ChangeTagsForResource(req) if err != nil { return err } } return nil }
func cleanupDomain(r53 *route53.Route53, id string) { // delete all non-default SOA/NS records rrsets, err := cli53.ListAllRecordSets(r53, id) fatalIfErr(err) changes := []*route53.Change{} for _, rrset := range rrsets { if *rrset.Type != "NS" && *rrset.Type != "SOA" { change := &route53.Change{ Action: aws.String("DELETE"), ResourceRecordSet: rrset, } changes = append(changes, change) } } if len(changes) > 0 { req2 := route53.ChangeResourceRecordSetsInput{ HostedZoneId: &id, ChangeBatch: &route53.ChangeBatch{ Changes: changes, }, } _, err = r53.ChangeResourceRecordSets(&req2) if err != nil { fmt.Printf("Warning: cleanup failed - %s\n", err) } } req3 := route53.DeleteHostedZoneInput{Id: &id} _, err = r53.DeleteHostedZone(&req3) if err != nil { fmt.Printf("Warning: cleanup failed - %s\n", err) } }
func deleteRoute53RecordSet(conn *route53.Route53, input *route53.ChangeResourceRecordSetsInput) (interface{}, error) { wait := resource.StateChangeConf{ Pending: []string{"rejected"}, Target: []string{"accepted"}, Timeout: 5 * time.Minute, MinTimeout: 1 * time.Second, Refresh: func() (interface{}, string, error) { resp, err := conn.ChangeResourceRecordSets(input) if err != nil { if r53err, ok := err.(awserr.Error); ok { if r53err.Code() == "PriorRequestNotComplete" { // There is some pending operation, so just retry // in a bit. return 42, "rejected", nil } if r53err.Code() == "InvalidChangeBatch" { // This means that the record is already gone. return resp, "accepted", nil } } return 42, "failure", err } return resp, "accepted", nil }, } return wait.WaitForState() }
// Paginate request to get all record sets. func ListAllRecordSets(r53 *route53.Route53, id string) (rrsets []*route53.ResourceRecordSet, err error) { req := route53.ListResourceRecordSetsInput{ HostedZoneId: &id, } for { var resp *route53.ListResourceRecordSetsOutput resp, err = r53.ListResourceRecordSets(&req) if err != nil { return } else { rrsets = append(rrsets, resp.ResourceRecordSets...) if *resp.IsTruncated { req.StartRecordName = resp.NextRecordName req.StartRecordType = resp.NextRecordType req.StartRecordIdentifier = resp.NextRecordIdentifier } else { break } } } // unescape wildcards for _, rrset := range rrsets { rrset.Name = aws.String(unescaper.Replace(*rrset.Name)) } return }
func cleanupReusableDelegationSet(r53 *route53.Route53, id string) { req := route53.DeleteReusableDelegationSetInput{Id: &id} _, err := r53.DeleteReusableDelegationSet(&req) if err != nil { fmt.Printf("Warning: cleanup failed - %s\n", err) } }
func resourceAwsGoRoute53Wait(r53 *route53.Route53, ref *route53.GetChangeInput) (result interface{}, state string, err error) { status, err := r53.GetChange(ref) if err != nil { return nil, "UNKNOWN", err } return true, *status.ChangeInfo.Status, nil }
func deleteAllRecordsInHostedZoneId(hostedZoneId, hostedZoneName string, conn *route53.Route53) error { input := &route53.ListResourceRecordSetsInput{ HostedZoneId: aws.String(hostedZoneId), } var lastDeleteErr, lastErrorFromWaiter error var pageNum = 0 err := conn.ListResourceRecordSetsPages(input, func(page *route53.ListResourceRecordSetsOutput, isLastPage bool) bool { sets := page.ResourceRecordSets pageNum += 1 changes := make([]*route53.Change, 0) // 100 items per page returned by default for _, set := range sets { if *set.Name == hostedZoneName+"." && (*set.Type == "NS" || *set.Type == "SOA") { // Zone NS & SOA records cannot be deleted continue } changes = append(changes, &route53.Change{ Action: aws.String("DELETE"), ResourceRecordSet: set, }) } log.Printf("[DEBUG] Deleting %d records (page %d) from %s", len(changes), pageNum, hostedZoneId) req := &route53.ChangeResourceRecordSetsInput{ HostedZoneId: aws.String(hostedZoneId), ChangeBatch: &route53.ChangeBatch{ Comment: aws.String("Deleted by Terraform"), Changes: changes, }, } var resp interface{} resp, lastDeleteErr = deleteRoute53RecordSet(conn, req) if out, ok := resp.(*route53.ChangeResourceRecordSetsOutput); ok { log.Printf("[DEBUG] Waiting for change batch to become INSYNC: %#v", out) if out.ChangeInfo != nil && out.ChangeInfo.Id != nil { lastErrorFromWaiter = waitForRoute53RecordSetToSync(conn, cleanChangeID(*out.ChangeInfo.Id)) } else { log.Printf("[DEBUG] Change info was empty") } } else { log.Printf("[DEBUG] Unable to wait for change batch because of an error: %s", lastDeleteErr) } return !isLastPage }) if err != nil { return fmt.Errorf("Failed listing/deleting record sets: %s\nLast error from deletion: %s\nLast error from waiter: %s", err, lastDeleteErr, lastErrorFromWaiter) } return nil }
func createRoute53Delete(r53Api *route53.Route53, s serviceStatus) (*route53.ChangeResourceRecordSetsInput, error) { domainParts := strings.Split(s.dnsName, ".") segments := len(domainParts) tld := strings.Join(domainParts[segments-2:], ".") subdomain := strings.Join(domainParts[:segments-2], ".") listHostedZoneInput := route53.ListHostedZonesByNameInput{ DNSName: &tld, } hzOut, err := r53Api.ListHostedZonesByName(&listHostedZoneInput) if err != nil { glog.Warningf("No zone found for %s: %v", tld, err) return nil, err } zones := hzOut.HostedZones if len(zones) < 1 { glog.Warningf("No zone found for %s", tld) return nil, err } // The AWS API may return more than one zone, the first zone should be the relevant one tldWithDot := fmt.Sprint(tld, ".") if *zones[0].Name != tldWithDot { glog.Warningf("Zone found %s does not match tld given %s", *zones[0].Name, tld) return nil, err } zoneId := *zones[0].ID zoneParts := strings.Split(zoneId, "/") zoneId = zoneParts[len(zoneParts)-1] at := route53.AliasTarget{ DNSName: &s.lb.Hostname, EvaluateTargetHealth: aws.Boolean(false), HostedZoneID: &s.hzId, } rrs := route53.ResourceRecordSet{ AliasTarget: &at, Name: &s.dnsName, Type: aws.String("A"), } change := route53.Change{ Action: aws.String("DELETE"), ResourceRecordSet: &rrs, } batch := route53.ChangeBatch{ Changes: []*route53.Change{&change}, Comment: aws.String("Kubernetes Update to Service"), } crrsInput := route53.ChangeResourceRecordSetsInput{ ChangeBatch: &batch, HostedZoneID: &zoneId, } glog.Infof("Created dns delete record set: tld=%s, subdomain=%s, zoneId=%s", tld, subdomain, zoneId) return &crrsInput, nil }
func getHostedZone(service *route53.Route53, domain string) (*route53.HostedZone, error) { params := &route53.ListHostedZonesByNameInput{ DNSName: aws.String(domain), MaxItems: aws.String("1"), } resp, err := service.ListHostedZonesByName(params) if err != nil { return nil, err } zone := resp.HostedZones[0] return zone, nil }
func getHostedZoneID(fqdn string, client *route53.Route53) (string, error) { authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers) if err != nil { return "", err } // .DNSName should not have a trailing dot reqParams := &route53.ListHostedZonesByNameInput{ DNSName: aws.String(acme.UnFqdn(authZone)), } resp, err := client.ListHostedZonesByName(reqParams) if err != nil { return "", err } var hostedZoneID string for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot if !*hostedZone.Config.PrivateZone && *hostedZone.Name == authZone { hostedZoneID = *hostedZone.Id break } } if len(hostedZoneID) == 0 { return "", fmt.Errorf("Zone %s not found in Route 53 for domain %s", authZone, fqdn) } if strings.HasPrefix(hostedZoneID, "/hostedzone/") { hostedZoneID = strings.TrimPrefix(hostedZoneID, "/hostedzone/") } return hostedZoneID, nil }
func getNameServers(zoneId string, zoneName string, r53 *route53.Route53) ([]string, error) { resp, err := r53.ListResourceRecordSets(&route53.ListResourceRecordSetsInput{ HostedZoneId: aws.String(zoneId), StartRecordName: aws.String(zoneName), StartRecordType: aws.String("NS"), }) if err != nil { return nil, err } if len(resp.ResourceRecordSets) == 0 { return nil, nil } ns := make([]string, len(resp.ResourceRecordSets[0].ResourceRecords)) for i := range resp.ResourceRecordSets[0].ResourceRecords { ns[i] = *resp.ResourceRecordSets[0].ResourceRecords[i].Value } sort.Strings(ns) return ns, nil }
// Paginate request to get all record sets. func ListAllRecordSets(r53 *route53.Route53, id string) (rrsets []*route53.ResourceRecordSet, err error) { req := route53.ListResourceRecordSetsInput{ HostedZoneId: &id, } for { var resp *route53.ListResourceRecordSetsOutput resp, err = r53.ListResourceRecordSets(&req) if err != nil { return } else { rrsets = append(rrsets, resp.ResourceRecordSets...) if *resp.IsTruncated { req.StartRecordName = resp.NextRecordName req.StartRecordType = resp.NextRecordType req.StartRecordIdentifier = resp.NextRecordIdentifier } else { break } } } return }
func upsertRecordOnRoute53(ipAddress string, fqdn string, svc *route53.Route53, verbose bool) { // Extract the domain from the fully qualified domain name r, _ := regexp.Compile("^([^\x2E]*)\x2E(.*)$") toks := r.FindStringSubmatch(fqdn) domain := toks[2] // http://docs.aws.amazon.com/sdk-for-go/api/service/route53/Route53.html#ListHostedZonesByName-instance_method resources, err := svc.ListHostedZonesByName(&route53.ListHostedZonesByNameInput{ DNSName: aws.String(domain + "."), MaxItems: aws.String("1"), }) if err != nil { fmt.Println("ERR: err.Error()", err) os.Exit(1) } if len(resources.HostedZones) != 1 { fmt.Printf("ERR: Could not find the domain %s\n", domain) os.Exit(1) } if *resources.DNSName != domain+"." { fmt.Printf("ERR: Could not find the domain %s - %s \n", domain, *resources.DNSName) os.Exit(1) } zoneIDToks := strings.Split(*resources.HostedZones[0].Id, "/") zoneID := zoneIDToks[len(zoneIDToks)-1] resp, err := svc.ListResourceRecordSets(&route53.ListResourceRecordSetsInput{ StartRecordName: aws.String(fqdn), StartRecordType: aws.String("A"), HostedZoneId: aws.String(zoneID), MaxItems: aws.String("1"), }) if err != nil { fmt.Println(err.Error()) os.Exit(1) } var foundResource bool if len(resp.ResourceRecordSets) != 1 { foundResource = false } else { foundResource = *resp.ResourceRecordSets[0].Name == fqdn+"." if foundResource { for _, record := range resp.ResourceRecordSets[0].ResourceRecords { if *record.Value == ipAddress { if verbose { fmt.Printf("INFO: %s already registered in route53 as %s\n", ipAddress, fqdn) } return } } } } // Make an A-record: resourceRecordSet := &route53.ResourceRecordSet{ Name: aws.String(fqdn + "."), Type: aws.String("A"), ResourceRecords: []*route53.ResourceRecord{ &route53.ResourceRecord{ Value: aws.String(ipAddress), }, }, TTL: aws.Int64(300), } // Wrap it as an UPSERT upsert := []*route53.Change{&route53.Change{ Action: aws.String("UPSERT"), ResourceRecordSet: resourceRecordSet, }} // Put it into a pretty envelope with a stamp for route53#zoneId and change ticket params := route53.ChangeResourceRecordSetsInput{ ChangeBatch: &route53.ChangeBatch{ Changes: upsert, }, HostedZoneId: aws.String(zoneID), } // Post it changeResp, err := svc.ChangeResourceRecordSets(¶ms) if err != nil { fmt.Println("ERR: ", err.Error(), changeResp) os.Exit(1) } // Done if verbose { fmt.Printf("INFO: Submitted change for zoneId %s to register %s as %s\n", zoneID, ipAddress, fqdn) } }
func createRoute53Upsert(r53Api *route53.Route53, elbApi *elb.ELB, domain string, hn string) (*route53.ChangeResourceRecordSetsInput, error) { domainParts := strings.Split(domain, ".") segments := len(domainParts) tld := strings.Join(domainParts[segments-2:], ".") // subdomain := strings.Join(domainParts[:segments-2], ".") elbName := strings.Split(hn, "-")[0] lbInput := &elb.DescribeLoadBalancersInput{ LoadBalancerNames: []*string{ &elbName, }, } resp, err := elbApi.DescribeLoadBalancers(lbInput) if err != nil { glog.Warningf("Could not describe load balancer: %v", err) return nil, err } descs := resp.LoadBalancerDescriptions if len(descs) < 1 { glog.Warningf("No lb found for %s: %v", tld, err) return nil, err } if len(descs) > 1 { glog.Warningf("Multiple lbs found for %s: %v", tld, err) return nil, err } hzId := descs[0].CanonicalHostedZoneNameID listHostedZoneInput := route53.ListHostedZonesByNameInput{ DNSName: &tld, } hzOut, err := r53Api.ListHostedZonesByName(&listHostedZoneInput) if err != nil { glog.Warningf("No zone found for %s: %v", tld, err) return nil, err } zones := hzOut.HostedZones if len(zones) < 1 { glog.Warningf("No zone found for %s", tld) return nil, err } // The AWS API may return more than one zone, the first zone should be the relevant one tldWithDot := fmt.Sprint(tld, ".") if *zones[0].Name != tldWithDot { glog.Warningf("Zone found %s does not match tld given %s", *zones[0].Name, tld) return nil, err } zoneId := *zones[0].ID zoneParts := strings.Split(zoneId, "/") zoneId = zoneParts[len(zoneParts)-1] at := route53.AliasTarget{ DNSName: &hn, EvaluateTargetHealth: aws.Boolean(false), HostedZoneID: hzId, } rrs := route53.ResourceRecordSet{ AliasTarget: &at, Name: &domain, Type: aws.String("A"), } change := route53.Change{ Action: aws.String("UPSERT"), ResourceRecordSet: &rrs, } batch := route53.ChangeBatch{ Changes: []*route53.Change{&change}, Comment: aws.String("Kubernetes Update to Service"), } crrsInput := route53.ChangeResourceRecordSetsInput{ ChangeBatch: &batch, HostedZoneID: &zoneId, } //glog.Infof("Created dns record set: tld=%s, subdomain=%s, zoneId=%s", tld, subdomain, zoneId) return &crrsInput, nil }