mirror of https://github.com/kubernetes/kops.git
Merge pull request #2864 from andrewsykim/digitalocean
digitalocean support -- dns provider
This commit is contained in:
commit
3139e89fae
|
@ -71,6 +71,8 @@ k8s.io/kops/pkg/model/iam
|
|||
k8s.io/kops/pkg/model/resources
|
||||
k8s.io/kops/pkg/model/vspheremodel
|
||||
k8s.io/kops/pkg/resources
|
||||
k8s.io/kops/pkg/resources/digitalocean
|
||||
k8s.io/kops/pkg/resources/digitalocean/dns
|
||||
k8s.io/kops/pkg/systemd
|
||||
k8s.io/kops/pkg/templates
|
||||
k8s.io/kops/pkg/testutils
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"golang.org/x/oauth2"
|
||||
"k8s.io/kops/pkg/resources/digitalocean/dns"
|
||||
"k8s.io/kubernetes/federation/pkg/dnsprovider"
|
||||
)
|
||||
|
||||
// TokenSource implements oauth2.TokenSource
|
||||
type TokenSource struct {
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
// Token() returns oauth2.Token
|
||||
func (t *TokenSource) Token() (*oauth2.Token, error) {
|
||||
token := &oauth2.Token{
|
||||
AccessToken: t.AccessToken,
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Cloud exposes all the interfaces required to operate on DigitalOcean resources
|
||||
type Cloud struct {
|
||||
client *godo.Client
|
||||
|
||||
Region string
|
||||
tags map[string]string
|
||||
}
|
||||
|
||||
// NewCloud returns a Cloud, expecting the env var DO_ACCESS_TOKEN
|
||||
// NewCloud will return an err if DO_ACCESS_TOKEN is not defined
|
||||
func NewCloud() (*Cloud, error) {
|
||||
accessToken := os.Getenv("DO_ACCESS_TOKEN")
|
||||
if accessToken == "" {
|
||||
return nil, errors.New("DO_ACCESS_TOKEN is required")
|
||||
}
|
||||
|
||||
tokenSource := &TokenSource{
|
||||
AccessToken: accessToken,
|
||||
}
|
||||
|
||||
oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
|
||||
client := godo.NewClient(oauthClient)
|
||||
|
||||
return &Cloud{
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DNS returns a DO implementation for dnsprovider.Interface
|
||||
func (c *Cloud) DNS() (dnsprovider.Interface, error) {
|
||||
provider := dns.NewProvider(c.client)
|
||||
return provider, nil
|
||||
}
|
|
@ -0,0 +1,485 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/digitalocean/godo/context"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/federation/pkg/dnsprovider"
|
||||
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
|
||||
)
|
||||
|
||||
const ipPlaceholder = "203.0.113.123"
|
||||
|
||||
// DNS implements dnsprovider.Interface
|
||||
type DNS struct {
|
||||
client *godo.Client
|
||||
}
|
||||
|
||||
// NewProvider returns an implementation of dnsprovider.Interface
|
||||
func NewProvider(client *godo.Client) dnsprovider.Interface {
|
||||
return &DNS{client: client}
|
||||
}
|
||||
|
||||
// Zones returns an implementation of dnsprovider.Zones
|
||||
func (d *DNS) Zones() (dnsprovider.Zones, bool) {
|
||||
return &zones{
|
||||
client: d.client,
|
||||
}, true
|
||||
}
|
||||
|
||||
// zones is an implementation of dnsprovider.Zones
|
||||
type zones struct {
|
||||
client *godo.Client
|
||||
}
|
||||
|
||||
// List returns a list of all dns zones
|
||||
func (z *zones) List() ([]dnsprovider.Zone, error) {
|
||||
domains, err := listDomains(z.client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var newZone *zone
|
||||
var zones []dnsprovider.Zone
|
||||
for _, domain := range domains {
|
||||
newZone = &zone{
|
||||
name: domain.Name,
|
||||
client: z.client,
|
||||
}
|
||||
zones = append(zones, newZone)
|
||||
}
|
||||
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
// Add adds a new DNS zone
|
||||
func (z *zones) Add(newZone dnsprovider.Zone) (dnsprovider.Zone, error) {
|
||||
domainCreateRequest := &godo.DomainCreateRequest{
|
||||
Name: newZone.Name(),
|
||||
IPAddress: ipPlaceholder,
|
||||
}
|
||||
|
||||
domain, err := createDomain(z.client, domainCreateRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &zone{
|
||||
name: domain.Name,
|
||||
client: z.client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Remove deletes a zone
|
||||
func (z *zones) Remove(zone dnsprovider.Zone) error {
|
||||
return deleteDomain(z.client, zone.Name())
|
||||
}
|
||||
|
||||
// New returns a new implementation of dnsprovider.Zone
|
||||
func (z *zones) New(name string) (dnsprovider.Zone, error) {
|
||||
return &zone{
|
||||
name: name,
|
||||
client: z.client,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
// zone implements dnsprovider.Zone
|
||||
type zone struct {
|
||||
name string
|
||||
client *godo.Client
|
||||
}
|
||||
|
||||
// Name returns the Name of a dns zone
|
||||
func (z *zone) Name() string {
|
||||
return z.name
|
||||
}
|
||||
|
||||
// ID returns the name of a dns zone, in DO the ID is the name
|
||||
func (z *zone) ID() string {
|
||||
return z.name
|
||||
}
|
||||
|
||||
// ResourceRecordSet returns an implementation of dnsprovider.ResourceRecordSets
|
||||
func (z *zone) ResourceRecordSets() (dnsprovider.ResourceRecordSets, bool) {
|
||||
return &resourceRecordSets{zone: z, client: z.client}, true
|
||||
}
|
||||
|
||||
// resourceRecordSets implements dnsprovider.ResourceRecordSet
|
||||
type resourceRecordSets struct {
|
||||
zone *zone
|
||||
client *godo.Client
|
||||
}
|
||||
|
||||
// List returns a list of dnsprovider.ResourceRecordSet
|
||||
func (r *resourceRecordSets) List() ([]dnsprovider.ResourceRecordSet, error) {
|
||||
records, err := getRecords(r.client, r.zone.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rrset *resourceRecordSet
|
||||
var rrsets []dnsprovider.ResourceRecordSet
|
||||
for _, record := range records {
|
||||
rrset = &resourceRecordSet{
|
||||
name: record.Name,
|
||||
data: record.Data,
|
||||
ttl: record.TTL,
|
||||
recordType: rrstype.RrsType(record.Type),
|
||||
}
|
||||
|
||||
rrsets = append(rrsets, rrset)
|
||||
}
|
||||
|
||||
return rrsets, nil
|
||||
|
||||
}
|
||||
|
||||
// Get returns a list of dnsprovider.ResourceRecordSet that matches the name parameter
|
||||
func (r *resourceRecordSets) Get(name string) ([]dnsprovider.ResourceRecordSet, error) {
|
||||
records, err := r.List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var recordSets []dnsprovider.ResourceRecordSet
|
||||
for _, record := range records {
|
||||
if record.Name() == name {
|
||||
recordSets = append(recordSets, record)
|
||||
}
|
||||
}
|
||||
|
||||
return recordSets, nil
|
||||
}
|
||||
|
||||
// New returns an implementation of dnsprovider.ResourceRecordSet
|
||||
func (r *resourceRecordSets) New(name string, rrdatas []string, ttl int64, rrstype rrstype.RrsType) dnsprovider.ResourceRecordSet {
|
||||
if len(rrdatas) > 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &resourceRecordSet{
|
||||
name: name,
|
||||
data: rrdatas[0],
|
||||
ttl: int(ttl),
|
||||
recordType: rrstype,
|
||||
}
|
||||
}
|
||||
|
||||
// StartChangeset returns an implementation of dnsprovider.ResourceRecordChangeset
|
||||
func (r *resourceRecordSets) StartChangeset() dnsprovider.ResourceRecordChangeset {
|
||||
return &resourceRecordChangeset{
|
||||
client: r.client,
|
||||
zone: r.zone,
|
||||
rrsets: r,
|
||||
additions: []dnsprovider.ResourceRecordSet{},
|
||||
removals: []dnsprovider.ResourceRecordSet{},
|
||||
upserts: []dnsprovider.ResourceRecordSet{},
|
||||
}
|
||||
}
|
||||
|
||||
// Zone returns the associated implementation of dnsprovider.Zone
|
||||
func (r *resourceRecordSets) Zone() dnsprovider.Zone {
|
||||
return r.zone
|
||||
}
|
||||
|
||||
// recordRecordSet implements dnsprovider.ResourceRecordSet which represents
|
||||
// a single record associated with a zone
|
||||
type resourceRecordSet struct {
|
||||
name string
|
||||
data string
|
||||
ttl int
|
||||
recordType rrstype.RrsType
|
||||
}
|
||||
|
||||
// Name returns the name of a resource record set
|
||||
func (r *resourceRecordSet) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
// Rrdatas returns a list of data associated with a resource record set
|
||||
// in DO this is almost always the IP of a record
|
||||
func (r *resourceRecordSet) Rrdatas() []string {
|
||||
return []string{r.data}
|
||||
}
|
||||
|
||||
// Ttl returns the time-to-live of a record
|
||||
func (r *resourceRecordSet) Ttl() int64 {
|
||||
return int64(r.ttl)
|
||||
}
|
||||
|
||||
// Type returns the type of record a resource record set is
|
||||
func (r *resourceRecordSet) Type() rrstype.RrsType {
|
||||
return r.recordType
|
||||
}
|
||||
|
||||
// resourceRecordChangeset implements dnsprovider.ResourceRecordChangeset
|
||||
type resourceRecordChangeset struct {
|
||||
client *godo.Client
|
||||
zone *zone
|
||||
rrsets dnsprovider.ResourceRecordSets
|
||||
|
||||
additions []dnsprovider.ResourceRecordSet
|
||||
removals []dnsprovider.ResourceRecordSet
|
||||
upserts []dnsprovider.ResourceRecordSet
|
||||
}
|
||||
|
||||
// Add adds a new resource record set to the list of additions to apply
|
||||
func (r *resourceRecordChangeset) Add(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
|
||||
r.additions = append(r.additions, rrset)
|
||||
return r
|
||||
}
|
||||
|
||||
// Remove adds a new resource record set to the list of removals to apply
|
||||
func (r *resourceRecordChangeset) Remove(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
|
||||
r.removals = append(r.removals, rrset)
|
||||
return r
|
||||
}
|
||||
|
||||
// Upsert adds a new resource record set to the list of upesrts to apply
|
||||
func (r *resourceRecordChangeset) Upsert(rrset dnsprovider.ResourceRecordSet) dnsprovider.ResourceRecordChangeset {
|
||||
r.upserts = append(r.upserts, rrset)
|
||||
return r
|
||||
}
|
||||
|
||||
// Apply adds new records stored in r.additions, updates records stored
|
||||
// in r.upserts and deletes records stored in r.removals
|
||||
func (r *resourceRecordChangeset) Apply() error {
|
||||
glog.V(2).Infof("applying changes in record change set")
|
||||
if r.IsEmpty() {
|
||||
glog.V(2).Infof("record change set is empty")
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(r.additions) > 0 {
|
||||
for _, record := range r.additions {
|
||||
recordCreateRequest := &godo.DomainRecordEditRequest{
|
||||
Name: record.Name(),
|
||||
Data: record.Rrdatas()[0],
|
||||
TTL: int(record.Ttl()),
|
||||
Type: string(record.Type()),
|
||||
}
|
||||
err := createRecord(r.client, r.zone.Name(), recordCreateRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create record: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(2).Infof("record change set additions complete")
|
||||
}
|
||||
|
||||
if len(r.removals) > 0 {
|
||||
records, err := getRecords(r.client, r.zone.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range r.removals {
|
||||
var desiredRecord godo.DomainRecord
|
||||
found := false
|
||||
for _, domainRecord := range records {
|
||||
if domainRecord.Name == record.Name() {
|
||||
desiredRecord = domainRecord
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("could not find desired record to remove")
|
||||
}
|
||||
|
||||
err := deleteRecord(r.client, r.zone.Name(), desiredRecord.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(2).Infof("record change set removals complete")
|
||||
}
|
||||
|
||||
if len(r.upserts) > 0 {
|
||||
records, err := getRecords(r.client, r.zone.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range r.upserts {
|
||||
var desiredRecord godo.DomainRecord
|
||||
found := false
|
||||
for _, domainRecord := range records {
|
||||
if domainRecord.Name == record.Name() {
|
||||
desiredRecord = domainRecord
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf("could not find desired record to upsert")
|
||||
}
|
||||
|
||||
domainEditRequest := &godo.DomainRecordEditRequest{
|
||||
Name: record.Name(),
|
||||
Data: record.Rrdatas()[0],
|
||||
TTL: int(record.Ttl()),
|
||||
Type: string(record.Type()),
|
||||
}
|
||||
err := editRecord(r.client, r.zone.Name(), desiredRecord.ID, domainEditRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
glog.V(2).Infof("record change set upserts complete")
|
||||
}
|
||||
|
||||
glog.V(2).Infof("record change sets successfully applied")
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEmpty returns true if a changeset is empty, false otherwise
|
||||
func (r *resourceRecordChangeset) IsEmpty() bool {
|
||||
if len(r.additions) == 0 && len(r.removals) == 0 && len(r.upserts) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ResourceRecordSet returns the associated resourceRecordSets of a changset
|
||||
func (r *resourceRecordChangeset) ResourceRecordSets() dnsprovider.ResourceRecordSets {
|
||||
return r.rrsets
|
||||
}
|
||||
|
||||
// listDomains returns a list of godo.Domain
|
||||
func listDomains(c *godo.Client) ([]godo.Domain, error) {
|
||||
// TODO (andrewsykim): pagination in ListOptions
|
||||
domains, resp, err := c.Domains.List(context.TODO(), &godo.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list domains: %v", err)
|
||||
}
|
||||
|
||||
if err = handleResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domains, err
|
||||
}
|
||||
|
||||
// createDomain creates a domain provided godo.DomainCreateRequest
|
||||
func createDomain(c *godo.Client, createRequest *godo.DomainCreateRequest) (*godo.Domain, error) {
|
||||
domain, resp, err := c.Domains.Create(context.TODO(), createRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = handleResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// deleteDomain deletes a domain given its name
|
||||
func deleteDomain(c *godo.Client, name string) error {
|
||||
resp, err := c.Domains.Delete(context.TODO(), name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = handleResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRecords returns a list of godo.DomainRecord given a zone name
|
||||
func getRecords(c *godo.Client, zoneName string) ([]godo.DomainRecord, error) {
|
||||
records, resp, err := c.Domains.Records(context.TODO(), zoneName, &godo.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = handleResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// createRecord creates a record given an assoicated zone and a godo.DomainRecordEditRequest
|
||||
func createRecord(c *godo.Client, zoneName string, createRequest *godo.DomainRecordEditRequest) error {
|
||||
_, resp, err := c.Domains.CreateRecord(context.TODO(), zoneName, createRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying changeset: %v", err)
|
||||
}
|
||||
|
||||
if err = handleResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// editRecord edits a record given an associated ozone and a godo.DomainRecordEditRequest
|
||||
func editRecord(c *godo.Client, zoneName string, recordID int, editRequest *godo.DomainRecordEditRequest) error {
|
||||
_, resp, err := c.Domains.EditRecord(context.TODO(), zoneName, recordID, editRequest)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying changeset: %v", err)
|
||||
}
|
||||
|
||||
if err = handleResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteRecord deletes a record given an associated zone and a record ID
|
||||
func deleteRecord(c *godo.Client, zoneName string, recordID int) error {
|
||||
resp, err := c.Domains.DeleteRecord(context.TODO(), zoneName, recordID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error applying changeset: %v", err)
|
||||
}
|
||||
|
||||
if err = handleResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleResponse(resp *godo.Response) error {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
respData, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading response body: %v", err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("received non 200 status code: %d from api: %v",
|
||||
resp.StatusCode, string(respData))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,489 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/digitalocean/godo/context"
|
||||
|
||||
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
|
||||
)
|
||||
|
||||
type fakeDomainService struct {
|
||||
listFunc func(ctx context.Context, listOpt *godo.ListOptions) ([]godo.Domain, *godo.Response, error)
|
||||
getFunc func(ctx context.Context, name string) (*godo.Domain, *godo.Response, error)
|
||||
createFunc func(ctx context.Context, domainCreateRequest *godo.DomainCreateRequest) (*godo.Domain, *godo.Response, error)
|
||||
deleteFunc func(ctx context.Context, name string) (*godo.Response, error)
|
||||
recordsFunc func(ctx context.Context, domain string, listOpts *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error)
|
||||
recordFunc func(ctx context.Context, domain string, id int) (*godo.DomainRecord, *godo.Response, error)
|
||||
deleteRecordFunc func(ctx context.Context, domain string, id int) (*godo.Response, error)
|
||||
editRecordFunc func(ctx context.Context, domain string, id int, editRequest *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error)
|
||||
createRecordFunc func(ctx context.Context, domain string, createRequest *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) List(ctx context.Context, listOpt *godo.ListOptions) ([]godo.Domain, *godo.Response, error) {
|
||||
return f.listFunc(ctx, listOpt)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) Get(ctx context.Context, name string) (*godo.Domain, *godo.Response, error) {
|
||||
return f.getFunc(ctx, name)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) Create(ctx context.Context, domainCreateRequest *godo.DomainCreateRequest) (*godo.Domain, *godo.Response, error) {
|
||||
return f.createFunc(ctx, domainCreateRequest)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) Delete(ctx context.Context, name string) (*godo.Response, error) {
|
||||
return f.deleteFunc(ctx, name)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) Records(ctx context.Context, domain string, listOpts *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) {
|
||||
return f.recordsFunc(ctx, domain, listOpts)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) Record(ctx context.Context, domain string, id int) (*godo.DomainRecord, *godo.Response, error) {
|
||||
return f.recordFunc(ctx, domain, id)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) DeleteRecord(ctx context.Context, domain string, id int) (*godo.Response, error) {
|
||||
return f.deleteRecordFunc(ctx, domain, id)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) EditRecord(ctx context.Context, domain string,
|
||||
id int, editRequest *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) {
|
||||
return f.editRecordFunc(ctx, domain, id, editRequest)
|
||||
}
|
||||
|
||||
func (f *fakeDomainService) CreateRecord(ctx context.Context, domain string,
|
||||
createRequest *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) {
|
||||
return f.createRecordFunc(ctx, domain, createRequest)
|
||||
}
|
||||
|
||||
func TestZonesList(t *testing.T) {
|
||||
client := godo.NewClient(nil)
|
||||
fake := &fakeDomainService{}
|
||||
|
||||
// happy path
|
||||
fake.listFunc = func(ctx context.Context, listOpts *godo.ListOptions) ([]godo.Domain, *godo.Response, error) {
|
||||
domains := []godo.Domain{
|
||||
{
|
||||
Name: "example.com",
|
||||
},
|
||||
}
|
||||
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
return domains, resp, nil
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
z := &zones{client}
|
||||
zoneList, err := z.List()
|
||||
if err != nil {
|
||||
t.Errorf("error listing zones: %v", err)
|
||||
}
|
||||
|
||||
if len(zoneList) != 1 {
|
||||
t.Errorf("expected only 1 zone, got %d", len(zoneList))
|
||||
}
|
||||
|
||||
zone := zoneList[0]
|
||||
if zone.Name() != "example.com" {
|
||||
t.Errorf("expected example.com as zone name, got: %s", zone.Name())
|
||||
}
|
||||
|
||||
// bad response path
|
||||
fake.listFunc = func(ctx context.Context, listOpts *godo.ListOptions) ([]godo.Domain, *godo.Response, error) {
|
||||
domains := []godo.Domain{
|
||||
{
|
||||
Name: "example.com",
|
||||
},
|
||||
}
|
||||
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusInternalServerError
|
||||
resp.Body = ioutil.NopCloser(bytes.NewBufferString("error!"))
|
||||
return domains, resp, nil
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
z = &zones{client}
|
||||
zoneList, err = z.List()
|
||||
if err == nil {
|
||||
t.Errorf("expected non-nil err")
|
||||
}
|
||||
|
||||
if zoneList != nil {
|
||||
t.Errorf("expected nil zone, got %v", zoneList)
|
||||
}
|
||||
|
||||
// godo client returned error path
|
||||
fake.listFunc = func(ctx context.Context, listOpts *godo.ListOptions) ([]godo.Domain, *godo.Response, error) {
|
||||
return nil, nil, errors.New("error!")
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
z = &zones{client}
|
||||
zoneList, err = z.List()
|
||||
if err == nil {
|
||||
t.Errorf("expected non-nil err")
|
||||
}
|
||||
|
||||
if zoneList != nil {
|
||||
t.Errorf("expected nil zone, got %v", zoneList)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
client := godo.NewClient(nil)
|
||||
fake := &fakeDomainService{}
|
||||
|
||||
// happy path
|
||||
fake.createFunc = func(ctx context.Context, domainCreateRequest *godo.DomainCreateRequest) (*godo.Domain, *godo.Response, error) {
|
||||
domain := &godo.Domain{Name: domainCreateRequest.Name}
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
|
||||
return domain, resp, nil
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
dnsProvider := NewProvider(client)
|
||||
zs, _ := dnsProvider.Zones()
|
||||
inZone := &zone{name: "test", client: client}
|
||||
|
||||
outZone, err := zs.Add(inZone)
|
||||
|
||||
if outZone.Name() != "test" {
|
||||
t.Errorf("unexpected zone name: %s", outZone.Name())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
// bad status code
|
||||
fake.createFunc = func(ctx context.Context, domainCreateRequest *godo.DomainCreateRequest) (*godo.Domain, *godo.Response, error) {
|
||||
domain := &godo.Domain{Name: domainCreateRequest.Name}
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusInternalServerError
|
||||
resp.Body = ioutil.NopCloser(bytes.NewBufferString("error!"))
|
||||
|
||||
return domain, resp, nil
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
dnsProvider = NewProvider(client)
|
||||
zs, _ = dnsProvider.Zones()
|
||||
inZone = &zone{name: "test", client: client}
|
||||
|
||||
outZone, err = zs.Add(inZone)
|
||||
|
||||
if outZone != nil {
|
||||
t.Errorf("expected zone to be nil, got :%v", outZone)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("expected non-nil err: %v", err)
|
||||
}
|
||||
|
||||
// godo returns error
|
||||
fake.createFunc = func(ctx context.Context, domainCreateRequest *godo.DomainCreateRequest) (*godo.Domain, *godo.Response, error) {
|
||||
domain := &godo.Domain{Name: domainCreateRequest.Name}
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
|
||||
return domain, resp, errors.New("error!")
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
dnsProvider = NewProvider(client)
|
||||
zs, _ = dnsProvider.Zones()
|
||||
inZone = &zone{name: "test", client: client}
|
||||
|
||||
outZone, err = zs.Add(inZone)
|
||||
|
||||
if outZone != nil {
|
||||
t.Errorf("expected zone to be nil, got :%v", outZone)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("expected non-nil err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
client := godo.NewClient(nil)
|
||||
fake := &fakeDomainService{}
|
||||
|
||||
// happy path
|
||||
fake.deleteFunc = func(ctx context.Context, name string) (*godo.Response, error) {
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
return resp, nil
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
dnsProvider := NewProvider(client)
|
||||
zs, _ := dnsProvider.Zones()
|
||||
inZone := &zone{name: "test", client: client}
|
||||
|
||||
err := zs.Remove(inZone)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected err: %v", err)
|
||||
}
|
||||
|
||||
// bad status code
|
||||
fake.deleteFunc = func(ctx context.Context, name string) (*godo.Response, error) {
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusInternalServerError
|
||||
resp.Body = ioutil.NopCloser(bytes.NewBufferString("error!"))
|
||||
return resp, nil
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
dnsProvider = NewProvider(client)
|
||||
zs, _ = dnsProvider.Zones()
|
||||
inZone = &zone{name: "test", client: client}
|
||||
|
||||
err = zs.Remove(inZone)
|
||||
if err == nil {
|
||||
t.Errorf("expected non-nil err: %v", err)
|
||||
}
|
||||
|
||||
// godo returns error
|
||||
fake.deleteFunc = func(ctx context.Context, name string) (*godo.Response, error) {
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
resp.Body = ioutil.NopCloser(bytes.NewBufferString("error!"))
|
||||
return resp, errors.New("error!")
|
||||
}
|
||||
client.Domains = fake
|
||||
|
||||
dnsProvider = NewProvider(client)
|
||||
zs, _ = dnsProvider.Zones()
|
||||
inZone = &zone{name: "test", client: client}
|
||||
|
||||
err = zs.Remove(inZone)
|
||||
if err == nil {
|
||||
t.Errorf("expected non-nil err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewZone(t *testing.T) {
|
||||
client := godo.NewClient(nil)
|
||||
dnsprovider := NewProvider(client)
|
||||
zs, _ := dnsprovider.Zones()
|
||||
|
||||
zone, err := zs.New("test")
|
||||
if err != nil {
|
||||
t.Errorf("error creating zone: %v", err)
|
||||
}
|
||||
|
||||
if zone.Name() != "test" {
|
||||
t.Errorf("unexpected zone name: %v", zone.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewResourceRecordSet(t *testing.T) {
|
||||
fake := &fakeDomainService{}
|
||||
fake.recordsFunc = func(ctx context.Context, domain string, listOpts *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) {
|
||||
domainRecords := []godo.DomainRecord{
|
||||
{
|
||||
Name: "test",
|
||||
Data: "127.0.0.1",
|
||||
TTL: 3600,
|
||||
Type: "A",
|
||||
},
|
||||
}
|
||||
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
|
||||
return domainRecords, resp, nil
|
||||
}
|
||||
client := godo.NewClient(nil)
|
||||
client.Domains = fake
|
||||
|
||||
dnsprovider := NewProvider(client)
|
||||
zs, _ := dnsprovider.Zones()
|
||||
|
||||
zone, err := zs.New("example.com")
|
||||
if err != nil {
|
||||
t.Errorf("error creating zone: %v", err)
|
||||
}
|
||||
|
||||
if zone.Name() != "example.com" {
|
||||
t.Errorf("unexpected zone name: %v", zone.Name())
|
||||
}
|
||||
|
||||
rrset, _ := zone.ResourceRecordSets()
|
||||
rrsets, err := rrset.List()
|
||||
if err != nil {
|
||||
t.Errorf("error listing resource record sets: %v", err)
|
||||
}
|
||||
|
||||
if len(rrsets) != 1 {
|
||||
t.Errorf("unexpected number of records: %d", len(rrsets))
|
||||
}
|
||||
|
||||
records, err := rrset.Get("test")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error getting resource record set: %v", err)
|
||||
}
|
||||
|
||||
if len(records) != 1 {
|
||||
t.Errorf("unexpected records from resource record set: %d, expected 1 record", len(records))
|
||||
}
|
||||
|
||||
if records[0].Name() != "test" {
|
||||
t.Errorf("unexpected record name: %s, expected 'test'", records[0].Name())
|
||||
}
|
||||
|
||||
if len(records[0].Rrdatas()) != 1 {
|
||||
t.Errorf("unexpected number of resource record data: %d", len(records[0].Rrdatas()))
|
||||
}
|
||||
|
||||
if records[0].Rrdatas()[0] != "127.0.0.1" {
|
||||
t.Errorf("unexpected resource record data: %s", records[0].Rrdatas()[0])
|
||||
}
|
||||
|
||||
if records[0].Ttl() != 3600 {
|
||||
t.Errorf("unexpected record TTL: %d, expected 3600", records[0].Ttl())
|
||||
}
|
||||
|
||||
if records[0].Type() != rrstype.A {
|
||||
t.Errorf("unexpected resource record type: %s, expected %s", records[0].Type(), rrstype.A)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceRecordChangeset(t *testing.T) {
|
||||
fake := &fakeDomainService{}
|
||||
fake.recordsFunc = func(ctx context.Context, domain string, listOpts *godo.ListOptions) ([]godo.DomainRecord, *godo.Response, error) {
|
||||
domainRecords := []godo.DomainRecord{
|
||||
{
|
||||
Name: "test",
|
||||
Data: "127.0.0.1",
|
||||
TTL: 3600,
|
||||
Type: "A",
|
||||
},
|
||||
{
|
||||
Name: "to-remove",
|
||||
Data: "127.0.0.1",
|
||||
TTL: 3600,
|
||||
Type: "A",
|
||||
},
|
||||
{
|
||||
Name: "to-upsert",
|
||||
Data: "127.0.0.1",
|
||||
TTL: 3600,
|
||||
Type: "A",
|
||||
},
|
||||
}
|
||||
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
|
||||
return domainRecords, resp, nil
|
||||
}
|
||||
|
||||
fake.createRecordFunc = func(ctx context.Context, domain string, createRequest *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) {
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
return &godo.DomainRecord{}, resp, nil
|
||||
}
|
||||
|
||||
fake.deleteRecordFunc = func(ctx context.Context, domain string, id int) (*godo.Response, error) {
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
fake.editRecordFunc = func(ctx context.Context, domain string, id int, editRequest *godo.DomainRecordEditRequest) (*godo.DomainRecord, *godo.Response, error) {
|
||||
resp := &godo.Response{
|
||||
Response: &http.Response{},
|
||||
}
|
||||
resp.StatusCode = http.StatusOK
|
||||
return &godo.DomainRecord{}, resp, nil
|
||||
}
|
||||
|
||||
client := godo.NewClient(nil)
|
||||
client.Domains = fake
|
||||
|
||||
dnsprovider := NewProvider(client)
|
||||
zs, _ := dnsprovider.Zones()
|
||||
|
||||
zone, err := zs.New("example.com")
|
||||
if err != nil {
|
||||
t.Errorf("error creating zone: %v", err)
|
||||
}
|
||||
|
||||
if zone.Name() != "example.com" {
|
||||
t.Errorf("unexpected zone name: %v", zone.Name())
|
||||
}
|
||||
|
||||
rrset, _ := zone.ResourceRecordSets()
|
||||
|
||||
changeset := rrset.StartChangeset()
|
||||
|
||||
if !changeset.IsEmpty() {
|
||||
t.Error("expected empty changeset")
|
||||
}
|
||||
|
||||
record := rrset.New("to-add", []string{"127.0.0.1"}, 3600, rrstype.A)
|
||||
changeset.Add(record)
|
||||
|
||||
record = rrset.New("to-remove", []string{"127.0.0.1"}, 3600, rrstype.A)
|
||||
changeset.Remove(record)
|
||||
|
||||
record = rrset.New("to-upsert", []string{"127.0.0.1"}, 3600, rrstype.A)
|
||||
changeset.Upsert(record)
|
||||
|
||||
err = changeset.Apply()
|
||||
if err != nil {
|
||||
t.Errorf("error applying changeset: %v", err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue