Skip to content

Commit

Permalink
Merge pull request #11 from calebdoxsey/zoneIDs
Browse files Browse the repository at this point in the history
support .co.uk domains
  • Loading branch information
calebdoxsey committed Mar 21, 2019
2 parents 06c77ad + d842044 commit 2fc66c7
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 94 deletions.
8 changes: 4 additions & 4 deletions Dockerfile
@@ -1,4 +1,4 @@
FROM golang:1.11.2-alpine as build
FROM golang:1.12-alpine as build
ENV GO111MODULE=on

RUN apk add --update --no-cache build-base git
Expand All @@ -12,10 +12,10 @@ WORKDIR /src



COPY go.mod .
COPY go.mod ./
RUN go mod download

COPY *.go .
COPY *.go ./
RUN CGO_ENABLED=0 go build -o /bin/kubernetes-cloudflare-sync .

FROM scratch
Expand All @@ -28,7 +28,7 @@ COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# practice.
COPY --from=0 /etc_passwd /etc/passwd

COPY --from=build /bin/kubernetes-cloudflare-sync /bin/kubernetes-cloudflare-sync
COPY --from=build /bin/kubernetes-cloudflare-sync /bin/kubernetes-cloudflare-sync

USER nobody

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -19,7 +19,7 @@ require (
github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.2 // indirect
github.com/stretchr/testify v1.2.2 // indirect
github.com/stretchr/testify v1.2.2
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 // indirect
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd // indirect
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 // indirect
Expand Down
2 changes: 1 addition & 1 deletion go.sum
Expand Up @@ -65,7 +65,7 @@ k8s.io/api v0.0.0-20181221193117-173ce66c1e39 h1:iGq7zEPXFb0IeXAQK5RiYT1SVKX/af9
k8s.io/api v0.0.0-20181221193117-173ce66c1e39/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apimachinery v0.0.0-20181108192626-90473842928c h1:cWEIJoXaA/PLmos29XffPIPWmxUcZKn1VhEEpZbp9p0=
k8s.io/apimachinery v0.0.0-20181108192626-90473842928c/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/client-go v9.0.0+incompatible h1:2kqW3X2xQ9SbFvWZjGEHBLlWc1LG9JIJNXWkuqwdZ3A=
k8s.io/client-go v9.0.0+incompatible h1:/PdJjifJTjMFe0G4ESclZDcwF1+bFePTJ2xf+MXjcvs=
k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
88 changes: 0 additions & 88 deletions main.go
Expand Up @@ -9,8 +9,6 @@ import (
"strings"
"time"

cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/pkg/errors"
core_v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/informers"
Expand Down Expand Up @@ -171,89 +169,3 @@ func nodeIsReady(node *core_v1.Node) bool {

return false
}

func sync(ips []string, dnsNames []string, cloudflareTTL int, cloudflareProxy bool) error {
api, err := cloudflare.New(options.CloudflareAPIKey, options.CloudflareAPIEmail)
if err != nil {
return errors.Wrap(err, "failed to access cloudflare api")
}

root := dnsNames[0]
for strings.Count(root, ".") > 1 {
root = root[strings.Index(root, ".")+1:]
}

zoneID, err := api.ZoneIDByName(root)
if err != nil {
return errors.Wrapf(err, "failed to find zone id for zone-name:=%s",
root)
}

known := map[string]bool{}
for _, ip := range ips {
known[ip] = true
}

for _, dnsName := range dnsNames {
records, err := api.DNSRecords(zoneID, cloudflare.DNSRecord{Type: "A", Name: dnsName})
if err != nil {
return errors.Wrapf(err, "failed to list dns records for zone-id=%s name=%s",
zoneID, dnsName)
}

seen := map[string]bool{}

for _, record := range records {
log.Printf("found existing record name=%s ip=%s\n",
record.Name, record.Content)
if _, ok := known[record.Content]; ok {
seen[record.Content] = true

if record.Proxied != cloudflareProxy || record.TTL != cloudflareTTL {
log.Printf("updating dns record name=%s ip=%s\n",
record.Name, record.Content)
err := api.UpdateDNSRecord(zoneID, record.ID, cloudflare.DNSRecord{
Type: record.Type,
Name: record.Name,
Content: record.Content,
TTL: cloudflareTTL,
Proxied: cloudflareProxy,
})
if err != nil {
return errors.Wrapf(err, "failed to update dns record zone-id=%s record-id=%s name=%s ip=%s",
zoneID, record.ID, record.Name, record.Content)
}
}
} else {
log.Printf("removing dns record name=%s ip=%s\n",
record.Name, record.Content)
err := api.DeleteDNSRecord(zoneID, record.ID)
if err != nil {
return errors.Wrapf(err, "failed to delete dns record zone-id=%s record-id=%s name=%s ip=%s",
zoneID, record.ID, record.Name, record.Content)
}
}
}

for ip := range known {
if _, ok := seen[ip]; ok {
continue
}
log.Printf("adding dns record name=%s ip=%s\n",
dnsName, ip)
_, err := api.CreateDNSRecord(zoneID, cloudflare.DNSRecord{
Type: "A",
Name: dnsName,
Content: ip,
TTL: cloudflareTTL,
Proxied: cloudflareProxy,
})
if err != nil {
return errors.Wrapf(err, "failed to create dns record zone-id=%s name=%s ip=%s",
zoneID, dnsName, ip)
}
}
}

return nil
}
109 changes: 109 additions & 0 deletions sync.go
@@ -0,0 +1,109 @@
package main

import (
"log"
"strings"

cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/pkg/errors"
)

func sync(ips []string, dnsNames []string, cloudflareTTL int, cloudflareProxy bool) error {
api, err := cloudflare.New(options.CloudflareAPIKey, options.CloudflareAPIEmail)
if err != nil {
return errors.Wrap(err, "failed to access cloudflare api")
}

root := dnsNames[0]
zoneID, err := findZoneID(api, root)
if err != nil {
return errors.Wrapf(err, "failed to find zone id for dns-name:=%s",
root)
}

known := map[string]bool{}
for _, ip := range ips {
known[ip] = true
}

for _, dnsName := range dnsNames {
records, err := api.DNSRecords(zoneID, cloudflare.DNSRecord{Type: "A", Name: dnsName})
if err != nil {
return errors.Wrapf(err, "failed to list dns records for zone-id=%s name=%s",
zoneID, dnsName)
}

seen := map[string]bool{}

for _, record := range records {
log.Printf("found existing record name=%s ip=%s\n",
record.Name, record.Content)
if _, ok := known[record.Content]; ok {
seen[record.Content] = true

if record.Proxied != cloudflareProxy || record.TTL != cloudflareTTL {
log.Printf("updating dns record name=%s ip=%s\n",
record.Name, record.Content)
err := api.UpdateDNSRecord(zoneID, record.ID, cloudflare.DNSRecord{
Type: record.Type,
Name: record.Name,
Content: record.Content,
TTL: cloudflareTTL,
Proxied: cloudflareProxy,
})
if err != nil {
return errors.Wrapf(err, "failed to update dns record zone-id=%s record-id=%s name=%s ip=%s",
zoneID, record.ID, record.Name, record.Content)
}
}
} else {
log.Printf("removing dns record name=%s ip=%s\n",
record.Name, record.Content)
err := api.DeleteDNSRecord(zoneID, record.ID)
if err != nil {
return errors.Wrapf(err, "failed to delete dns record zone-id=%s record-id=%s name=%s ip=%s",
zoneID, record.ID, record.Name, record.Content)
}
}
}

for ip := range known {
if _, ok := seen[ip]; ok {
continue
}
log.Printf("adding dns record name=%s ip=%s\n",
dnsName, ip)
_, err := api.CreateDNSRecord(zoneID, cloudflare.DNSRecord{
Type: "A",
Name: dnsName,
Content: ip,
TTL: cloudflareTTL,
Proxied: cloudflareProxy,
})
if err != nil {
return errors.Wrapf(err, "failed to create dns record zone-id=%s name=%s ip=%s",
zoneID, dnsName, ip)
}
}
}

return nil
}

// findZoneID finds a zone id for the given dns record
func findZoneID(api interface {
ListZones(z ...string) ([]cloudflare.Zone, error)
}, dnsName string) (string, error) {
zones, err := api.ListZones()
if err != nil {
return "", err
}

for _, zone := range zones {
if zone.Name == dnsName || strings.HasSuffix(dnsName, "."+zone.Name) {
return zone.ID, nil
}
}

return "", errors.New("zone id not found")
}
64 changes: 64 additions & 0 deletions sync_test.go
@@ -0,0 +1,64 @@
package main

import (
"testing"

cloudflare "github.com/cloudflare/cloudflare-go"
"github.com/stretchr/testify/assert"
)

type mockAPI struct {
listZones func(z ...string) ([]cloudflare.Zone, error)
}

func (m mockAPI) ListZones(z ...string) ([]cloudflare.Zone, error) {
return m.listZones(z...)
}

func TestFindZoneID(t *testing.T) {
t.Run("subdomain", func(t *testing.T) {
zoneID, err := findZoneID(mockAPI{
listZones: func(z ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{
{ID: "1", Name: "example.com"},
}, nil
},
}, "kubernetes.example.com")
assert.Nil(t, err)
assert.Equal(t, "1", zoneID)
})
t.Run("domain", func(t *testing.T) {
zoneID, err := findZoneID(mockAPI{
listZones: func(z ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{
{ID: "1", Name: "example.com"},
}, nil
},
}, "example.com")
assert.Nil(t, err)
assert.Equal(t, "1", zoneID)
})
t.Run("partial domain", func(t *testing.T) {
zoneID, err := findZoneID(mockAPI{
listZones: func(z ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{
{ID: "1", Name: "example.com"}, // a bare suffix match would inadvertently match this domain
{ID: "2", Name: "anotherexample.com"},
}, nil
},
}, "anotherexample.com")
assert.Nil(t, err)
assert.Equal(t, "2", zoneID)
})
t.Run(".co.uk", func(t *testing.T) {
zoneID, err := findZoneID(mockAPI{
listZones: func(z ...string) ([]cloudflare.Zone, error) {
return []cloudflare.Zone{
{ID: "1", Name: "example.co.uk"},
}, nil
},
}, "subdomain.example.co.uk")
assert.Nil(t, err)
assert.Equal(t, "1", zoneID)
})
}

0 comments on commit 2fc66c7

Please sign in to comment.