| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | // Copyright 2019 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of go-ethereum. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  | // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/aws/aws-sdk-go/aws" | 
					
						
							|  |  |  | 	"github.com/aws/aws-sdk-go/aws/credentials" | 
					
						
							|  |  |  | 	"github.com/aws/aws-sdk-go/aws/session" | 
					
						
							|  |  |  | 	"github.com/aws/aws-sdk-go/service/route53" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/log" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/p2p/dnsdisc" | 
					
						
							|  |  |  | 	"gopkg.in/urfave/cli.v1" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-26 23:55:33 +01:00
										 |  |  | const ( | 
					
						
							|  |  |  | 	// Route53 limits change sets to 32k of 'RDATA size'. Change sets are also limited to | 
					
						
							|  |  |  | 	// 1000 items. UPSERTs count double. | 
					
						
							|  |  |  | 	// https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-changeresourcerecordsets | 
					
						
							|  |  |  | 	route53ChangeSizeLimit  = 32000 | 
					
						
							|  |  |  | 	route53ChangeCountLimit = 1000 | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | var ( | 
					
						
							|  |  |  | 	route53AccessKeyFlag = cli.StringFlag{ | 
					
						
							|  |  |  | 		Name:   "access-key-id", | 
					
						
							|  |  |  | 		Usage:  "AWS Access Key ID", | 
					
						
							|  |  |  | 		EnvVar: "AWS_ACCESS_KEY_ID", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	route53AccessSecretFlag = cli.StringFlag{ | 
					
						
							|  |  |  | 		Name:   "access-key-secret", | 
					
						
							|  |  |  | 		Usage:  "AWS Access Key Secret", | 
					
						
							|  |  |  | 		EnvVar: "AWS_SECRET_ACCESS_KEY", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	route53ZoneIDFlag = cli.StringFlag{ | 
					
						
							|  |  |  | 		Name:  "zone-id", | 
					
						
							|  |  |  | 		Usage: "Route53 Zone ID", | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type route53Client struct { | 
					
						
							|  |  |  | 	api    *route53.Route53 | 
					
						
							|  |  |  | 	zoneID string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | type recordSet struct { | 
					
						
							|  |  |  | 	values []string | 
					
						
							|  |  |  | 	ttl    int64 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | // newRoute53Client sets up a Route53 API client from command line flags. | 
					
						
							|  |  |  | func newRoute53Client(ctx *cli.Context) *route53Client { | 
					
						
							|  |  |  | 	akey := ctx.String(route53AccessKeyFlag.Name) | 
					
						
							|  |  |  | 	asec := ctx.String(route53AccessSecretFlag.Name) | 
					
						
							|  |  |  | 	if akey == "" || asec == "" { | 
					
						
							|  |  |  | 		exit(fmt.Errorf("need Route53 Access Key ID and secret proceed")) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	config := &aws.Config{Credentials: credentials.NewStaticCredentials(akey, asec, "")} | 
					
						
							|  |  |  | 	session, err := session.NewSession(config) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		exit(fmt.Errorf("can't create AWS session: %v", err)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return &route53Client{ | 
					
						
							|  |  |  | 		api:    route53.New(session), | 
					
						
							|  |  |  | 		zoneID: ctx.String(route53ZoneIDFlag.Name), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // deploy uploads the given tree to Route53. | 
					
						
							|  |  |  | func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { | 
					
						
							|  |  |  | 	if err := c.checkZone(name); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Compute DNS changes. | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	existing, err := c.collectRecords(name) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	log.Info(fmt.Sprintf("Found %d TXT records", len(existing))) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	records := t.ToTXT(name) | 
					
						
							|  |  |  | 	changes := c.computeChanges(name, records, existing) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	if len(changes) == 0 { | 
					
						
							|  |  |  | 		log.Info("No DNS changes needed") | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	// Submit change batches. | 
					
						
							| 
									
										
										
										
											2020-03-26 23:55:33 +01:00
										 |  |  | 	batches := splitChanges(changes, route53ChangeSizeLimit, route53ChangeCountLimit) | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	for i, changes := range batches { | 
					
						
							|  |  |  | 		log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes))) | 
					
						
							|  |  |  | 		batch := new(route53.ChangeBatch) | 
					
						
							|  |  |  | 		batch.SetChanges(changes) | 
					
						
							|  |  |  | 		batch.SetComment(fmt.Sprintf("enrtree update %d/%d of %s at seq %d", i+1, len(batches), name, t.Seq())) | 
					
						
							|  |  |  | 		req := &route53.ChangeResourceRecordSetsInput{HostedZoneId: &c.zoneID, ChangeBatch: batch} | 
					
						
							|  |  |  | 		resp, err := c.api.ChangeResourceRecordSets(req) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 		log.Info(fmt.Sprintf("Waiting for change request %s", *resp.ChangeInfo.Id)) | 
					
						
							|  |  |  | 		wreq := &route53.GetChangeInput{Id: resp.ChangeInfo.Id} | 
					
						
							|  |  |  | 		if err := c.api.WaitUntilResourceRecordSetsChanged(wreq); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // checkZone verifies zone information for the given domain. | 
					
						
							|  |  |  | func (c *route53Client) checkZone(name string) (err error) { | 
					
						
							|  |  |  | 	if c.zoneID == "" { | 
					
						
							|  |  |  | 		c.zoneID, err = c.findZoneID(name) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // findZoneID searches for the Zone ID containing the given domain. | 
					
						
							|  |  |  | func (c *route53Client) findZoneID(name string) (string, error) { | 
					
						
							|  |  |  | 	log.Info(fmt.Sprintf("Finding Route53 Zone ID for %s", name)) | 
					
						
							|  |  |  | 	var req route53.ListHostedZonesByNameInput | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		resp, err := c.api.ListHostedZonesByName(&req) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return "", err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, zone := range resp.HostedZones { | 
					
						
							|  |  |  | 			if isSubdomain(name, *zone.Name) { | 
					
						
							|  |  |  | 				return *zone.Id, nil | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if !*resp.IsTruncated { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		req.DNSName = resp.NextDNSName | 
					
						
							|  |  |  | 		req.HostedZoneId = resp.NextHostedZoneId | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return "", errors.New("can't find zone ID for " + name) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // computeChanges creates DNS changes for the given record. | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | func (c *route53Client) computeChanges(name string, records map[string]string, existing map[string]recordSet) []*route53.Change { | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	// Convert all names to lowercase. | 
					
						
							|  |  |  | 	lrecords := make(map[string]string, len(records)) | 
					
						
							|  |  |  | 	for name, r := range records { | 
					
						
							|  |  |  | 		lrecords[strings.ToLower(name)] = r | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	records = lrecords | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var changes []*route53.Change | 
					
						
							|  |  |  | 	for path, val := range records { | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 		ttl := int64(rootTTL) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 		if path != name { | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 			ttl = int64(treeNodeTTL) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		prevRecords, exists := existing[path] | 
					
						
							| 
									
										
										
										
											2020-02-05 15:29:59 +01:00
										 |  |  | 		prevValue := strings.Join(prevRecords.values, "") | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 		if !exists { | 
					
						
							|  |  |  | 			// Entry is unknown, push a new one | 
					
						
							|  |  |  | 			log.Info(fmt.Sprintf("Creating %s = %q", path, val)) | 
					
						
							|  |  |  | 			changes = append(changes, newTXTChange("CREATE", path, ttl, splitTXT(val))) | 
					
						
							| 
									
										
										
										
											2020-03-26 23:55:33 +01:00
										 |  |  | 		} else if prevValue != val || prevRecords.ttl != ttl { | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 			// Entry already exists, only change its content. | 
					
						
							|  |  |  | 			log.Info(fmt.Sprintf("Updating %s from %q to %q", path, prevValue, val)) | 
					
						
							|  |  |  | 			changes = append(changes, newTXTChange("UPSERT", path, ttl, splitTXT(val))) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			log.Info(fmt.Sprintf("Skipping %s = %q", path, val)) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Iterate over the old records and delete anything stale. | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	for path, set := range existing { | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 		if _, ok := records[path]; ok { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Stale entry, nuke it. | 
					
						
							| 
									
										
										
										
											2020-02-05 15:29:59 +01:00
										 |  |  | 		log.Info(fmt.Sprintf("Deleting %s = %q", path, strings.Join(set.values, ""))) | 
					
						
							|  |  |  | 		changes = append(changes, newTXTChange("DELETE", path, set.ttl, set.values...)) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	sortChanges(changes) | 
					
						
							|  |  |  | 	return changes | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // sortChanges ensures DNS changes are in leaf-added -> root-changed -> leaf-deleted order. | 
					
						
							|  |  |  | func sortChanges(changes []*route53.Change) { | 
					
						
							|  |  |  | 	score := map[string]int{"CREATE": 1, "UPSERT": 2, "DELETE": 3} | 
					
						
							|  |  |  | 	sort.Slice(changes, func(i, j int) bool { | 
					
						
							|  |  |  | 		if *changes[i].Action == *changes[j].Action { | 
					
						
							|  |  |  | 			return *changes[i].ResourceRecordSet.Name < *changes[j].ResourceRecordSet.Name | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return score[*changes[i].Action] < score[*changes[j].Action] | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // splitChanges splits up DNS changes such that each change batch | 
					
						
							|  |  |  | // is smaller than the given RDATA limit. | 
					
						
							| 
									
										
										
										
											2020-03-26 23:55:33 +01:00
										 |  |  | func splitChanges(changes []*route53.Change, sizeLimit, countLimit int) [][]*route53.Change { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		batches    [][]*route53.Change | 
					
						
							|  |  |  | 		batchSize  int | 
					
						
							|  |  |  | 		batchCount int | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	for _, ch := range changes { | 
					
						
							|  |  |  | 		// Start new batch if this change pushes the current one over the limit. | 
					
						
							| 
									
										
										
										
											2020-03-26 23:55:33 +01:00
										 |  |  | 		count := changeCount(ch) | 
					
						
							|  |  |  | 		size := changeSize(ch) * count | 
					
						
							|  |  |  | 		overSize := batchSize+size > sizeLimit | 
					
						
							|  |  |  | 		overCount := batchCount+count > countLimit | 
					
						
							|  |  |  | 		if len(batches) == 0 || overSize || overCount { | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 			batches = append(batches, nil) | 
					
						
							|  |  |  | 			batchSize = 0 | 
					
						
							| 
									
										
										
										
											2020-03-26 23:55:33 +01:00
										 |  |  | 			batchCount = 0 | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		batches[len(batches)-1] = append(batches[len(batches)-1], ch) | 
					
						
							|  |  |  | 		batchSize += size | 
					
						
							| 
									
										
										
										
											2020-03-26 23:55:33 +01:00
										 |  |  | 		batchCount += count | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return batches | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // changeSize returns the RDATA size of a DNS change. | 
					
						
							|  |  |  | func changeSize(ch *route53.Change) int { | 
					
						
							|  |  |  | 	size := 0 | 
					
						
							|  |  |  | 	for _, rr := range ch.ResourceRecordSet.ResourceRecords { | 
					
						
							|  |  |  | 		if rr.Value != nil { | 
					
						
							|  |  |  | 			size += len(*rr.Value) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return size | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-26 23:55:33 +01:00
										 |  |  | func changeCount(ch *route53.Change) int { | 
					
						
							|  |  |  | 	if *ch.Action == "UPSERT" { | 
					
						
							|  |  |  | 		return 2 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 1 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | // collectRecords collects all TXT records below the given name. | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | func (c *route53Client) collectRecords(name string) (map[string]recordSet, error) { | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	log.Info(fmt.Sprintf("Retrieving existing TXT records on %s (%s)", name, c.zoneID)) | 
					
						
							|  |  |  | 	var req route53.ListResourceRecordSetsInput | 
					
						
							|  |  |  | 	req.SetHostedZoneId(c.zoneID) | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	existing := make(map[string]recordSet) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	err := c.api.ListResourceRecordSetsPages(&req, func(resp *route53.ListResourceRecordSetsOutput, last bool) bool { | 
					
						
							|  |  |  | 		for _, set := range resp.ResourceRecordSets { | 
					
						
							|  |  |  | 			if !isSubdomain(*set.Name, name) || *set.Type != "TXT" { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 			s := recordSet{ttl: *set.TTL} | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 			for _, rec := range set.ResourceRecords { | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 				s.values = append(s.values, *rec.Value) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 			} | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 			name := strings.TrimSuffix(*set.Name, ".") | 
					
						
							|  |  |  | 			existing[name] = s | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	return existing, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // newTXTChange creates a change to a TXT record. | 
					
						
							| 
									
										
										
										
											2020-02-05 15:29:59 +01:00
										 |  |  | func newTXTChange(action, name string, ttl int64, values ...string) *route53.Change { | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	var c route53.Change | 
					
						
							|  |  |  | 	var r route53.ResourceRecordSet | 
					
						
							|  |  |  | 	var rrs []*route53.ResourceRecord | 
					
						
							|  |  |  | 	for _, val := range values { | 
					
						
							|  |  |  | 		rr := new(route53.ResourceRecord) | 
					
						
							|  |  |  | 		rr.SetValue(val) | 
					
						
							|  |  |  | 		rrs = append(rrs, rr) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	r.SetType("TXT") | 
					
						
							|  |  |  | 	r.SetName(name) | 
					
						
							| 
									
										
										
										
											2020-01-17 11:32:29 +01:00
										 |  |  | 	r.SetTTL(ttl) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	r.SetResourceRecords(rrs) | 
					
						
							|  |  |  | 	c.SetAction(action) | 
					
						
							|  |  |  | 	c.SetResourceRecordSet(&r) | 
					
						
							|  |  |  | 	return &c | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // isSubdomain returns true if name is a subdomain of domain. | 
					
						
							|  |  |  | func isSubdomain(name, domain string) bool { | 
					
						
							|  |  |  | 	domain = strings.TrimSuffix(domain, ".") | 
					
						
							|  |  |  | 	name = strings.TrimSuffix(name, ".") | 
					
						
							|  |  |  | 	return strings.HasSuffix("."+name, "."+domain) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // splitTXT splits value into a list of quoted 255-character strings. | 
					
						
							| 
									
										
										
										
											2020-02-05 15:29:59 +01:00
										 |  |  | func splitTXT(value string) string { | 
					
						
							|  |  |  | 	var result strings.Builder | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 	for len(value) > 0 { | 
					
						
							|  |  |  | 		rlen := len(value) | 
					
						
							|  |  |  | 		if rlen > 253 { | 
					
						
							|  |  |  | 			rlen = 253 | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-02-05 15:29:59 +01:00
										 |  |  | 		result.WriteString(strconv.Quote(value[:rlen])) | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | 		value = value[rlen:] | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2020-02-05 15:29:59 +01:00
										 |  |  | 	return result.String() | 
					
						
							| 
									
										
										
										
											2019-12-12 22:25:12 +01:00
										 |  |  | } |