dnscontrol/providers/google/google.go
Craig Peterson 12f006441b Refactoring diff package interface (#22)
* initial refactoring of diffing

* making cloudflare and others compile

* gandi and gcloud. no idea if gandi works anymore.

* r53

* namedotcom wasn't working.
2017-01-11 12:38:07 -07:00

237 lines
5.4 KiB
Go

package google
import (
"context"
"encoding/json"
"fmt"
"strings"
gauth "golang.org/x/oauth2/google"
"google.golang.org/api/dns/v1"
"github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/providers"
"github.com/StackExchange/dnscontrol/providers/diff"
)
func init() {
providers.RegisterDomainServiceProviderType("GCLOUD", New)
}
type gcloud struct {
client *dns.Service
project string
zones map[string]*dns.ManagedZone
}
// New creates a new gcloud provider
func New(cfg map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
raw, err := json.Marshal(cfg)
if err != nil {
return nil, err
}
config, err := gauth.JWTConfigFromJSON(raw, "https://www.googleapis.com/auth/ndev.clouddns.readwrite")
if err != nil {
return nil, err
}
ctx := context.Background()
hc := config.Client(ctx)
dcli, err := dns.New(hc)
if err != nil {
return nil, err
}
return &gcloud{
client: dcli,
project: cfg["project_id"],
}, nil
}
type errNoExist struct {
domain string
}
func (e errNoExist) Error() string {
return fmt.Sprintf("Domain %s not found in gcloud account", e.domain)
}
func (g *gcloud) getZone(domain string) (*dns.ManagedZone, error) {
if g.zones == nil {
g.zones = map[string]*dns.ManagedZone{}
pageToken := ""
for {
resp, err := g.client.ManagedZones.List(g.project).PageToken(pageToken).Do()
if err != nil {
return nil, err
}
for _, z := range resp.ManagedZones {
g.zones[z.DnsName] = z
}
if pageToken = resp.NextPageToken; pageToken == "" {
break
}
}
}
if g.zones[domain+"."] == nil {
return nil, errNoExist{domain}
}
return g.zones[domain+"."], nil
}
func (g *gcloud) GetNameservers(domain string) ([]*models.Nameserver, error) {
zone, err := g.getZone(domain)
if err != nil {
return nil, err
}
return models.StringsToNameservers(zone.NameServers), nil
}
type key struct {
Type string
Name string
}
func keyFor(r *dns.ResourceRecordSet) key {
return key{Type: r.Type, Name: r.Name}
}
func keyForRec(r *models.RecordConfig) key {
return key{Type: r.Type, Name: r.NameFQDN + "."}
}
func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
rrs, zoneName, err := g.getRecords(dc.Name)
if err != nil {
return nil, err
}
//convert to dnscontrol RecordConfig format
existingRecords := []*models.RecordConfig{}
oldRRs := map[key]*dns.ResourceRecordSet{}
for _, set := range rrs {
nameWithoutDot := set.Name
if strings.HasSuffix(nameWithoutDot, ".") {
nameWithoutDot = nameWithoutDot[:len(nameWithoutDot)-1]
}
oldRRs[keyFor(set)] = set
for _, rec := range set.Rrdatas {
r := &models.RecordConfig{
NameFQDN: nameWithoutDot,
Type: set.Type,
Target: rec,
TTL: uint32(set.Ttl),
}
existingRecords = append(existingRecords, r)
}
}
for _, want := range dc.Records {
if want.Type == "MX" {
want.Target = fmt.Sprintf("%d %s", want.Priority, want.Target)
want.Priority = 0
} else if want.Type == "TXT" {
//add quotes to txts
want.Target = fmt.Sprintf(`"%s"`, want.Target)
}
}
// first collect keys that have changed
differ := diff.New(dc)
_, create, delete, modify := differ.IncrementalDiff(existingRecords)
changedKeys := map[key]bool{}
desc := ""
for _, c := range create {
desc += fmt.Sprintln(c)
changedKeys[keyForRec(c.Desired)] = true
}
for _, d := range delete {
desc += fmt.Sprintln(d)
changedKeys[keyForRec(d.Existing)] = true
}
for _, m := range modify {
desc += fmt.Sprintln(m)
changedKeys[keyForRec(m.Existing)] = true
}
if len(changedKeys) == 0 {
return nil, nil
}
chg := &dns.Change{Kind: "dns#change"}
for ck := range changedKeys {
// remove old version (if present)
if old, ok := oldRRs[ck]; ok {
chg.Deletions = append(chg.Deletions, old)
}
//collect records to replace with
newRRs := &dns.ResourceRecordSet{
Name: ck.Name,
Type: ck.Type,
Kind: "dns#resourceRecordSet",
}
for _, r := range dc.Records {
if keyForRec(r) == ck {
newRRs.Rrdatas = append(newRRs.Rrdatas, r.Target)
newRRs.Ttl = int64(r.TTL)
}
}
if len(newRRs.Rrdatas) > 0 {
chg.Additions = append(chg.Additions, newRRs)
}
}
runChange := func() error {
_, err := g.client.Changes.Create(g.project, zoneName, chg).Do()
return err
}
return []*models.Correction{{
Msg: desc,
F: runChange,
}}, nil
}
func (g *gcloud) getRecords(domain string) ([]*dns.ResourceRecordSet, string, error) {
zone, err := g.getZone(domain)
if err != nil {
return nil, "", err
}
pageToken := ""
sets := []*dns.ResourceRecordSet{}
for {
call := g.client.ResourceRecordSets.List(g.project, zone.Name)
if pageToken != "" {
call = call.PageToken(pageToken)
}
resp, err := call.Do()
if err != nil {
return nil, "", err
}
for _, rrs := range resp.Rrsets {
if rrs.Type == "SOA" {
continue
}
sets = append(sets, rrs)
}
if pageToken = resp.NextPageToken; pageToken == "" {
break
}
}
return sets, zone.Name, nil
}
func (g *gcloud) EnsureDomainExists(domain string) error {
z, err := g.getZone(domain)
if err != nil {
if _, ok := err.(errNoExist); !ok {
return err
}
}
if z != nil {
return nil
}
fmt.Printf("Adding zone for %s to gcloud account\n", domain)
mz := &dns.ManagedZone{
DnsName: domain + ".",
Name: strings.Replace(domain, ".", "-", -1),
Description: "zone added by dnscontrol",
}
g.zones = nil //reset cache
_, err = g.client.ManagedZones.Create(g.project, mz).Do()
return err
}