/
provider.go
144 lines (123 loc) · 4 KB
/
provider.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
138
139
140
141
142
143
144
package gcp
import (
"context"
"fmt"
"net/http"
"strings"
"google.golang.org/api/googleapi"
configv1 "github.com/openshift/api/config/v1"
iov1 "github.com/openshift/api/operatoringress/v1"
"github.com/openshift/cluster-ingress-operator/pkg/dns"
logf "github.com/openshift/cluster-ingress-operator/pkg/log"
gdnsv1 "google.golang.org/api/dns/v1"
"google.golang.org/api/option"
)
var (
_ dns.Provider = &Provider{}
log = logf.Logger.WithName("dns")
)
type Provider struct {
// config is required input.
config Config
// dnsService provides DNS API access.
dnsService *gdnsv1.Service
}
type Config struct {
Project string
UserAgent string
CredentialsJSON []byte
}
func New(config Config) (*Provider, error) {
dnsService, err := gdnsv1.NewService(context.TODO(), option.WithCredentialsJSON(config.CredentialsJSON), option.WithUserAgent(config.UserAgent))
if err != nil {
return nil, err
}
provider := &Provider{
config: config,
dnsService: dnsService,
}
return provider, nil
}
// ParseZone will parse two different string formatted zones. The first is the short name where only the
// zone id is provided. The second is the long name where the zone and project are both available in the string
// in the format provided by GCP projects/{projectID}/managedZones/{zoneID}.
func ParseZone(defaultProject, zoneID string) (string, string, error) {
parts := strings.Split(zoneID, "/")
switch {
case len(parts) == 1:
return defaultProject, zoneID, nil
case len(parts) == 4 && parts[0] == "projects" && parts[2] == "managedZones":
return parts[1], parts[3], nil
}
return "", "", fmt.Errorf("invalid managedZone: %s", zoneID)
}
func (p *Provider) parseZone(zone configv1.DNSZone) (string, string, error) {
// parse the zone that was provided
project, zoneID, err := ParseZone(p.config.Project, zone.ID)
if err != nil {
return "", "", err
}
return project, zoneID, nil
}
func (p *Provider) Ensure(record *iov1.DNSRecord, zone configv1.DNSZone) error {
change := &gdnsv1.Change{Additions: []*gdnsv1.ResourceRecordSet{resourceRecordSet(record)}}
project, zoneID, err := p.parseZone(zone)
if err != nil {
return err
}
call := p.dnsService.Changes.Create(project, zoneID, change)
_, err = call.Do()
// Since we don't yet handle updates, assume that existing records are correct.
if ae, ok := err.(*googleapi.Error); ok && ae.Code == http.StatusConflict {
return nil
}
return err
}
func (p *Provider) Replace(record *iov1.DNSRecord, zone configv1.DNSZone) error {
ctx := context.Background()
project, zoneID, err := p.parseZone(zone)
if err != nil {
return err
}
oldRecord := p.dnsService.ResourceRecordSets.List(project, zoneID).Name(record.Spec.DNSName)
if err := oldRecord.Pages(ctx, func(page *gdnsv1.ResourceRecordSetsListResponse) error {
for _, resourceRecordSet := range page.Rrsets {
log.Info("found old DNS resource record set", "resourceRecordSet", resourceRecordSet)
change := &gdnsv1.Change{Deletions: []*gdnsv1.ResourceRecordSet{resourceRecordSet}}
call := p.dnsService.Changes.Create(project, zoneID, change)
_, err := call.Do()
if ae, ok := err.(*googleapi.Error); ok && ae.Code == http.StatusNotFound {
return nil
}
return err
}
return nil
}); err != nil {
return err
}
if err := p.Ensure(record, zone); err != nil {
return err
}
return nil
}
func (p *Provider) Delete(record *iov1.DNSRecord, zone configv1.DNSZone) error {
change := &gdnsv1.Change{Deletions: []*gdnsv1.ResourceRecordSet{resourceRecordSet(record)}}
project, zoneID, err := p.parseZone(zone)
if err != nil {
return err
}
call := p.dnsService.Changes.Create(project, zoneID, change)
_, err = call.Do()
if ae, ok := err.(*googleapi.Error); ok && ae.Code == http.StatusNotFound {
return nil
}
return err
}
func resourceRecordSet(record *iov1.DNSRecord) *gdnsv1.ResourceRecordSet {
return &gdnsv1.ResourceRecordSet{
Name: record.Spec.DNSName,
Rrdatas: record.Spec.Targets,
Type: string(record.Spec.RecordType),
Ttl: record.Spec.RecordTTL,
}
}