/
register.go
137 lines (121 loc) · 3.61 KB
/
register.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package dnsx
import (
"context"
"errors"
"log"
"github.com/m-lab/autojoin/internal/dnsx/dnsiface"
"google.golang.org/api/dns/v1"
"google.golang.org/api/googleapi"
)
var (
// ErrBadIPFormat is returned when registering a hostname with a malformed IP.
ErrBadIPFormat = errors.New("bad ip format")
recordTypeA = "A"
recordTypeAAAA = "AAAA"
)
// Manager contains state needed for managing DNS recors.
type Manager struct {
Project string
Zone string
Service dnsiface.Service
}
// NewManager creates a new Manager instance.
func NewManager(s dnsiface.Service, project, zone string) *Manager {
return &Manager{
Project: project,
Zone: zone,
Service: s,
}
}
func appendDeletions(chg *dns.Change, rr *dns.ResourceRecordSet, hostname string) {
chg.Deletions = append(chg.Deletions,
&dns.ResourceRecordSet{
Name: hostname,
Type: rr.Type,
Ttl: rr.Ttl,
Rrdatas: rr.Rrdatas,
},
)
}
func appendAdditions(chg *dns.Change, hostname, ip, rtype string) {
chg.Additions = append(chg.Additions,
&dns.ResourceRecordSet{
Name: hostname,
Type: rtype,
Ttl: 300,
Rrdatas: []string{ip},
},
)
}
// Register creates a new resource record for hostname with the given ipv4 and ipv6 adresses.
func (d *Manager) Register(ctx context.Context, hostname, ipv4, ipv6 string) (*dns.Change, error) {
chg := &dns.Change{}
var err error
var rr *dns.ResourceRecordSet
// IPv4 is required. An empty ipv4 value will generate an error.
rr, err = d.get(ctx, hostname, recordTypeA)
if isNotFound(err) {
appendAdditions(chg, hostname, ipv4, recordTypeA)
}
if rr != nil {
// Record matches given parameters, so we do not need to add or delete it.
matches := (len(rr.Rrdatas) == 1 && rr.Rrdatas[0] == ipv4)
if !matches {
// We found an existing resource record that doesn't match the given address.
// Remove the old one and add a new one.
appendDeletions(chg, rr, hostname)
appendAdditions(chg, hostname, ipv4, recordTypeA)
}
}
// IPv6 remains optional for now.
if ipv6 != "" {
rr, err = d.get(ctx, hostname, recordTypeAAAA)
if isNotFound(err) {
appendAdditions(chg, hostname, ipv6, recordTypeAAAA)
}
if rr != nil {
matches := (len(rr.Rrdatas) == 1 && rr.Rrdatas[0] == ipv6)
if !matches {
appendDeletions(chg, rr, hostname)
appendAdditions(chg, hostname, ipv6, recordTypeAAAA)
}
}
}
if chg.Additions == nil && chg.Deletions == nil {
// Without any actions, the ChangeCreate will fail.
return nil, err
}
return d.Service.ChangeCreate(ctx, d.Project, d.Zone, chg)
}
// Delete removes all resource records associated with the given hostname.
func (d *Manager) Delete(ctx context.Context, hostname string) (*dns.Change, error) {
chg := &dns.Change{}
for _, rtype := range []string{recordTypeA, recordTypeAAAA} {
rr, err := d.get(ctx, hostname, rtype)
if err != nil && !isNotFound(err) {
// A different error occured. The host record may or may not exist.
return nil, err
}
if rr != nil {
// Remove the record we found.
appendDeletions(chg, rr, hostname)
}
}
return d.Service.ChangeCreate(ctx, d.Project, d.Zone, chg)
}
// get retrieves a resource record for the given hostname and rtype.
func (d *Manager) get(ctx context.Context, hostname, rtype string) (*dns.ResourceRecordSet, error) {
return d.Service.ResourceRecordSetsGet(ctx, d.Project, d.Zone, hostname, rtype)
}
// checks whether this is a googleapi.Error for "not found".
func isNotFound(err error) bool {
if err == nil {
return false
}
var gerr *googleapi.Error
if errors.As(err, &gerr) {
log.Printf("googleapi.Error: %#v", gerr)
return gerr.Code == 404
}
return false
}