From ef310eee3a60d5c986c9eba753588cc76ea5593f Mon Sep 17 00:00:00 2001 From: Nathan LaFreniere Date: Sat, 21 Nov 2015 15:14:43 -0800 Subject: [PATCH 1/2] add dnsimple provider --- Godeps/Godeps.json | 6 +- .../src/github.com/weppos/go-dnsimple/LICENSE | 20 ++ .../weppos/go-dnsimple/dnsimple/contacts.go | 121 +++++++++++ .../weppos/go-dnsimple/dnsimple/dnsimple.go | 192 ++++++++++++++++++ .../weppos/go-dnsimple/dnsimple/domains.go | 121 +++++++++++ .../go-dnsimple/dnsimple/domains_records.go | 136 +++++++++++++ .../go-dnsimple/dnsimple/domains_zones.go | 24 +++ .../weppos/go-dnsimple/dnsimple/registrar.go | 131 ++++++++++++ .../weppos/go-dnsimple/dnsimple/users.go | 35 ++++ providers/dnsimple.go | 171 ++++++++++++++++ providers/route53.go | 15 ++ 11 files changed, 971 insertions(+), 1 deletion(-) create mode 100644 Godeps/_workspace/src/github.com/weppos/go-dnsimple/LICENSE create mode 100644 Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/contacts.go create mode 100644 Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/dnsimple.go create mode 100644 Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains.go create mode 100644 Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains_records.go create mode 100644 Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains_zones.go create mode 100644 Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/registrar.go create mode 100644 Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/users.go create mode 100644 providers/dnsimple.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 47949a5..678f908 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/rancher/external-dns", - "GoVersion": "go1.4", + "GoVersion": "go1.5.1", "Deps": [ { "ImportPath": "github.com/Sirupsen/logrus", @@ -39,6 +39,10 @@ { "ImportPath": "github.com/vaughan0/go-ini", "Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1" + }, + { + "ImportPath": "github.com/weppos/go-dnsimple/dnsimple", + "Rev": "c82ccf106204c401644a24e8b45d16cf81e9cccb" } ] } diff --git a/Godeps/_workspace/src/github.com/weppos/go-dnsimple/LICENSE b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/LICENSE new file mode 100644 index 0000000..5377f6d --- /dev/null +++ b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Scott Barron + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/contacts.go b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/contacts.go new file mode 100644 index 0000000..6a53be7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/contacts.go @@ -0,0 +1,121 @@ +package dnsimple + +import ( + "fmt" + "time" +) + +// ContactsService handles communication with the contact related +// methods of the DNSimple API. +// +// DNSimple API docs: http://developer.dnsimple.com/contacts/ +type ContactsService struct { + client *Client +} + +type Contact struct { + Id int `json:"id,omitempty"` + Label string `json:"label,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + JobTitle string `json:"job_title,omitempty"` + Organization string `json:"organization_name,omitempty"` + Email string `json:"email_address,omitempty"` + Phone string `json:"phone,omitempty"` + Fax string `json:"fax,omitempty"` + Address1 string `json:"address1,omitempty"` + Address2 string `json:"address2,omitempty"` + City string `json:"city,omitempty"` + Zip string `json:"postal_code,omitempty"` + Country string `json:"country,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +type contactWrapper struct { + Contact Contact `json:"contact"` +} + +// contactPath generates the resource path for given contact. +func contactPath(contact interface{}) string { + if contact != nil { + return fmt.Sprintf("contacts/%d", contact) + } + return "contacts" +} + +// List the contacts. +// +// DNSimple API docs: http://developer.dnsimple.com/contacts/#list +func (s *ContactsService) List() ([]Contact, *Response, error) { + path := contactPath(nil) + wrappedContacts := []contactWrapper{} + + res, err := s.client.get(path, &wrappedContacts) + if err != nil { + return []Contact{}, res, err + } + + contacts := []Contact{} + for _, contact := range wrappedContacts { + contacts = append(contacts, contact.Contact) + } + + return contacts, res, nil +} + +// Create a new contact. +// +// DNSimple API docs: http://developer.dnsimple.com/contacts/#create +func (s *ContactsService) Create(contactAttributes Contact) (Contact, *Response, error) { + path := contactPath(nil) + wrappedContact := contactWrapper{Contact: contactAttributes} + returnedContact := contactWrapper{} + + res, err := s.client.post(path, wrappedContact, &returnedContact) + if err != nil { + return Contact{}, res, err + } + + return returnedContact.Contact, res, nil +} + +// Get fetches a contact. +// +// DNSimple API docs: http://developer.dnsimple.com/contacts/#get +func (s *ContactsService) Get(contactID int) (Contact, *Response, error) { + path := contactPath(contactID) + wrappedContact := contactWrapper{} + + res, err := s.client.get(path, &wrappedContact) + if err != nil { + return Contact{}, res, err + } + + return wrappedContact.Contact, res, nil +} + +// Update a contact. +// +// DNSimple API docs: http://developer.dnsimple.com/contacts/#update +func (s *ContactsService) Update(contactID int, contactAttributes Contact) (Contact, *Response, error) { + path := contactPath(contactID) + wrappedContact := contactWrapper{Contact: contactAttributes} + returnedContact := contactWrapper{} + + res, err := s.client.put(path, wrappedContact, &returnedContact) + if err != nil { + return Contact{}, res, err + } + + return returnedContact.Contact, res, nil +} + +// Delete a contact. +// +// DNSimple API docs: http://developer.dnsimple.com/contacts/#delete +func (s *ContactsService) Delete(contactID int) (*Response, error) { + path := contactPath(contactID) + + return s.client.delete(path, nil) +} diff --git a/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/dnsimple.go b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/dnsimple.go new file mode 100644 index 0000000..3d40664 --- /dev/null +++ b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/dnsimple.go @@ -0,0 +1,192 @@ +// Package dnsimple implements a client for the DNSimple API. +// +// In order to use this package you will need a DNSimple account and your API Token. +package dnsimple + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +const ( + libraryVersion = "0.1" + baseURL = "https://api.dnsimple.com/" + userAgent = "dnsimple-go/" + libraryVersion + + apiVersion = "v1" +) + +type Client struct { + // HTTP client used to communicate with the API. + HttpClient *http.Client + + // API Token for the DNSimple account you want to use. + ApiToken string + + // Email associated with the provided DNSimple API Token. + Email string + + // Domain Token to be used for authentication + // as an alternative to the DNSimple API Token for some domain-scoped operations. + DomainToken string + + // Base URL for API requests. + // Defaults to the public DNSimple API, but can be set to a different endpoint (e.g. the sandbox). + // BaseURL should always be specified with a trailing slash. + BaseURL string + + // User agent used when communicating with the DNSimple API. + UserAgent string + + // Services used for talking to different parts of the DNSimple API. + Contacts *ContactsService + Domains *DomainsService + Registrar *RegistrarService + Users *UsersService +} + +// NewClient returns a new DNSimple API client. +func NewClient(apiToken, email string) *Client { + c := &Client{ApiToken: apiToken, Email: email, HttpClient: &http.Client{}, BaseURL: baseURL, UserAgent: userAgent} + c.Contacts = &ContactsService{client: c} + c.Domains = &DomainsService{client: c} + c.Registrar = &RegistrarService{client: c} + c.Users = &UsersService{client: c} + return c +} + +// NewRequest creates an API request. +// The path is expected to be a relative path and will be resolved +// according to the BaseURL of the Client. Paths should always be specified without a preceding slash. +func (client *Client) NewRequest(method, path string, payload interface{}) (*http.Request, error) { + url := client.BaseURL + fmt.Sprintf("%s/%s", apiVersion, path) + + body := new(bytes.Buffer) + if payload != nil { + err := json.NewEncoder(body).Encode(payload) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + req.Header.Add("User-Agent", client.UserAgent) + req.Header.Add("X-DNSimple-Token", fmt.Sprintf("%s:%s", client.Email, client.ApiToken)) + + return req, nil +} + +func (c *Client) get(path string, v interface{}) (*Response, error) { + return c.Do("GET", path, nil, v) +} + +func (c *Client) post(path string, payload, v interface{}) (*Response, error) { + return c.Do("POST", path, payload, v) +} + +func (c *Client) put(path string, payload, v interface{}) (*Response, error) { + return c.Do("PUT", path, payload, v) +} + +func (c *Client) delete(path string, payload interface{}) (*Response, error) { + return c.Do("DELETE", path, payload, nil) +} + +// Do sends an API request and returns the API response. +// The API response is JSON decoded and stored in the value pointed by v, +// or returned as an error if an API error has occurred. +// If v implements the io.Writer interface, the raw response body will be written to v, +// without attempting to decode it. +func (c *Client) Do(method, path string, payload, v interface{}) (*Response, error) { + req, err := c.NewRequest(method, path, payload) + if err != nil { + return nil, err + } + + res, err := c.HttpClient.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + response := &Response{Response: res} + + err = CheckResponse(res) + if err != nil { + return response, err + } + + if v != nil { + if w, ok := v.(io.Writer); ok { + io.Copy(w, res.Body) + } else { + err = json.NewDecoder(res.Body).Decode(v) + } + } + + return response, err +} + +// A Response represents an API response. +type Response struct { + *http.Response +} + +// An ErrorResponse represents an error caused by an API request. +type ErrorResponse struct { + Response *http.Response // HTTP response that caused this error + Message string `json:"message"` // human-readable message +} + +// Error implements the error interface. +func (r *ErrorResponse) Error() string { + return fmt.Sprintf("%v %v: %d %v", + r.Response.Request.Method, r.Response.Request.URL, + r.Response.StatusCode, r.Message) +} + +// CheckResponse checks the API response for errors, and returns them if present. +// A response is considered an error if the status code is different than 2xx. Specific requests +// may have additional requirements, but this is sufficient in most of the cases. +func CheckResponse(r *http.Response) error { + if code := r.StatusCode; 200 <= code && code <= 299 { + return nil + } + + errorResponse := &ErrorResponse{Response: r} + err := json.NewDecoder(r.Body).Decode(errorResponse) + if err != nil { + return err + } + + return errorResponse +} + +// Date custom type. +type Date struct { + time.Time +} + +// UnmarshalJSON handles the deserialization of the custom Date type. +func (d *Date) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("date should be a string, got %s", data) + } + t, err := time.Parse("2006-01-02", s) + if err != nil { + return fmt.Errorf("invalid date: %v", err) + } + d.Time = t + return nil +} diff --git a/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains.go b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains.go new file mode 100644 index 0000000..317a7a7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains.go @@ -0,0 +1,121 @@ +package dnsimple + +import ( + "fmt" + "time" +) + +// DomainsService handles communication with the domain related +// methods of the DNSimple API. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/ +type DomainsService struct { + client *Client +} + +type Domain struct { + Id int `json:"id,omitempty"` + UserId int `json:"user_id,omitempty"` + RegistrantId int `json:"registrant_id,omitempty"` + Name string `json:"name,omitempty"` + UnicodeName string `json:"unicode_name,omitempty"` + Token string `json:"token,omitempty"` + State string `json:"state,omitempty"` + Language string `json:"language,omitempty"` + Lockable bool `json:"lockable,omitempty"` + AutoRenew bool `json:"auto_renew,omitempty"` + WhoisProtected bool `json:"whois_protected,omitempty"` + RecordCount int `json:"record_count,omitempty"` + ServiceCount int `json:"service_count,omitempty"` + ExpiresOn *Date `json:"expires_on,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +type domainWrapper struct { + Domain Domain `json:"domain"` +} + +// domainRequest represents a generic wrapper for a domain request, +// when domainWrapper cannot be used because of type constraint on Domain. +type domainRequest struct { + Domain interface{} `json:"domain"` +} + +func domainIdentifier(value interface{}) string { + switch value := value.(type) { + case string: + return value + case int: + return fmt.Sprintf("%d", value) + } + return "" +} + +// domainPath generates the resource path for given domain. +func domainPath(domain interface{}) string { + if domain != nil { + return fmt.Sprintf("domains/%s", domainIdentifier(domain)) + } + return "domains" +} + +// List the domains. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/#list +func (s *DomainsService) List() ([]Domain, *Response, error) { + path := domainPath(nil) + returnedDomains := []domainWrapper{} + + res, err := s.client.get(path, &returnedDomains) + if err != nil { + return []Domain{}, res, err + } + + domains := []Domain{} + for _, domain := range returnedDomains { + domains = append(domains, domain.Domain) + } + + return domains, res, nil +} + +// Create a new domain. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/#create +func (s *DomainsService) Create(domainAttributes Domain) (Domain, *Response, error) { + path := domainPath(nil) + wrappedDomain := domainWrapper{Domain: domainAttributes} + returnedDomain := domainWrapper{} + + res, err := s.client.post(path, wrappedDomain, &returnedDomain) + if err != nil { + return Domain{}, res, err + } + + return returnedDomain.Domain, res, nil +} + +// Get fetches a domain. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/#get +func (s *DomainsService) Get(domain interface{}) (Domain, *Response, error) { + path := domainPath(domain) + returnedDomain := domainWrapper{} + + res, err := s.client.get(path, &returnedDomain) + if err != nil { + return Domain{}, res, err + } + + return returnedDomain.Domain, res, nil +} + +// Delete a domain. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/#delete +func (s *DomainsService) Delete(domain interface{}) (*Response, error) { + path := domainPath(domain) + + return s.client.delete(path, nil) +} diff --git a/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains_records.go b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains_records.go new file mode 100644 index 0000000..8a6e35b --- /dev/null +++ b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains_records.go @@ -0,0 +1,136 @@ +package dnsimple + +import ( + "fmt" + "net/url" + "time" +) + +type Record struct { + Id int `json:"id,omitempty"` + DomainId int `json:"domain_id,omitempty"` + Name string `json:"name,omitempty"` + Content string `json:"content,omitempty"` + TTL int `json:"ttl,omitempty"` + Priority int `json:"prio,omitempty"` + Type string `json:"record_type,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +type recordWrapper struct { + Record Record `json:"record"` +} + +// recordPath generates the resource path for given record that belongs to a domain. +func recordPath(domain interface{}, record interface{}) string { + path := fmt.Sprintf("domains/%s/records", domainIdentifier(domain)) + + if record != nil { + path += fmt.Sprintf("/%d", record) + } + + return path +} + +// List the domain records. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/records/#list +func (s *DomainsService) ListRecords(domain interface{}, recordName, recordType string) ([]Record, *Response, error) { + reqStr := recordPath(domain, nil) + v := url.Values{} + + if recordName != "" { + v.Add("name", recordName) + } + if recordType != "" { + v.Add("type", recordType) + } + reqStr += "?" + v.Encode() + + wrappedRecords := []recordWrapper{} + + res, err := s.client.get(reqStr, &wrappedRecords) + if err != nil { + return []Record{}, res, err + } + + records := []Record{} + for _, record := range wrappedRecords { + records = append(records, record.Record) + } + + return records, res, nil +} + +// CreateRecord creates a domain record. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/records/#create +func (s *DomainsService) CreateRecord(domain interface{}, recordAttributes Record) (Record, *Response, error) { + path := recordPath(domain, nil) + wrappedRecord := recordWrapper{Record: recordAttributes} + returnedRecord := recordWrapper{} + + res, err := s.client.post(path, wrappedRecord, &returnedRecord) + if err != nil { + return Record{}, res, err + } + + return returnedRecord.Record, res, nil +} + +// GetRecord fetches the domain record. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/records/#get +func (s *DomainsService) GetRecord(domain interface{}, recordID int) (Record, *Response, error) { + path := recordPath(domain, recordID) + wrappedRecord := recordWrapper{} + + res, err := s.client.get(path, &wrappedRecord) + if err != nil { + return Record{}, res, err + } + + return wrappedRecord.Record, res, nil +} + +// UpdateRecord updates a domain record. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/records/#update +func (s *DomainsService) UpdateRecord(domain interface{}, recordID int, recordAttributes Record) (Record, *Response, error) { + path := recordPath(domain, recordID) + // name, content, ttl, priority + wrappedRecord := recordWrapper{ + Record: Record{ + Name: recordAttributes.Name, + Content: recordAttributes.Content, + TTL: recordAttributes.TTL, + Priority: recordAttributes.Priority}} + returnedRecord := recordWrapper{} + + res, err := s.client.put(path, wrappedRecord, &returnedRecord) + if err != nil { + return Record{}, res, err + } + + return returnedRecord.Record, res, nil +} + +// DeleteRecord deletes a domain record. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/records/#delete +func (s *DomainsService) DeleteRecord(domain interface{}, recordID int) (*Response, error) { + path := recordPath(domain, recordID) + + return s.client.delete(path, nil) +} + +// UpdateIP updates the IP of specific A record. +// +// This is not part of the standard API. However, +// this is useful for Dynamic DNS (DDNS or DynDNS). +func (record *Record) UpdateIP(client *Client, IP string) error { + newRecord := Record{Content: IP, Name: record.Name} + _, _, err := client.Domains.UpdateRecord(record.DomainId, record.Id, newRecord) + return err +} diff --git a/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains_zones.go b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains_zones.go new file mode 100644 index 0000000..8765bfd --- /dev/null +++ b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/domains_zones.go @@ -0,0 +1,24 @@ +package dnsimple + +import ( + "fmt" +) + +type zoneResponse struct { + Zone string `json:"zone,omitempty"` +} + +// GetZone downloads the Bind-like zone file. +// +// DNSimple API docs: http://developer.dnsimple.com/domains/zones/#get +func (s *DomainsService) GetZone(domain interface{}) (string, *Response, error) { + path := fmt.Sprintf("%s/zone", domainPath(domain)) + zoneResponse := zoneResponse{} + + res, err := s.client.get(path, &zoneResponse) + if err != nil { + return "", res, err + } + + return zoneResponse.Zone, res, nil +} diff --git a/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/registrar.go b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/registrar.go new file mode 100644 index 0000000..38cd85e --- /dev/null +++ b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/registrar.go @@ -0,0 +1,131 @@ +package dnsimple + +import ( + "fmt" +) + +// RegistrarService handles communication with the registrar related +// methods of the DNSimple API. +// +// DNSimple API docs: http://developer.dnsimple.com/registrar/ +type RegistrarService struct { + client *Client +} + +// ExtendedAttributes maps the additional attributes required by some registries. +type ExtendedAttributes map[string]string + +// ExtendedAttributes represents the transfer information. +type TransferOrder struct { + AuthCode string `json:"authinfo,omitempty"` +} + +// registrationRequest represents the body of a register or transfer request. +type registrationRequest struct { + Domain Domain `json:"domain"` + ExtendedAttributes *ExtendedAttributes `json:"extended_attribute,omitempty"` + TransferOrder *TransferOrder `json:"transfer_order,omitempty"` +} + +// IsAvailable checks if the domain is available or registered. +// +// See: http://developer.dnsimple.com/registrar/#check +func (s *RegistrarService) IsAvailable(domain string) (bool, error) { + path := fmt.Sprintf("%s/check", domainPath(domain)) + + res, err := s.client.get(path, nil) + if err != nil && res != nil && res.StatusCode != 404 { + return false, err + } + + return res.StatusCode == 404, nil +} + +// Register a domain. +// +// DNSimple API docs: http://developer.dnsimple.com/registrar/#register +func (s *RegistrarService) Register(domain string, registrantID int, extendedAttributes *ExtendedAttributes) (Domain, *Response, error) { + request := registrationRequest{ + Domain: Domain{Name: domain, RegistrantId: registrantID}, + ExtendedAttributes: extendedAttributes, + } + returnedDomain := domainWrapper{} + + res, err := s.client.post("domain_registrations", request, &returnedDomain) + if err != nil { + return Domain{}, res, err + } + + return returnedDomain.Domain, res, nil +} + +// Transfer a domain. +// +// DNSimple API docs: http://developer.dnsimple.com/registrar/#transfer +func (s *RegistrarService) Transfer(domain string, registrantID int, authCode string, extendedAttributes *ExtendedAttributes) (Domain, *Response, error) { + request := registrationRequest{ + Domain: Domain{Name: domain, RegistrantId: registrantID}, + ExtendedAttributes: extendedAttributes, + TransferOrder: &TransferOrder{AuthCode: authCode}, + } + returnedDomain := domainWrapper{} + + res, err := s.client.post("domain_transfers", request, &returnedDomain) + if err != nil { + return Domain{}, res, err + } + + return returnedDomain.Domain, res, nil +} + +// renewDomain represents the body of a Renew request. +type renewDomain struct { + Name string `json:"name,omitempty"` + RenewWhoisPrivacy bool `json:"renew_whois_privacy,omitempty"` +} + +// Renew the domain, optionally renewing WHOIS privacy service. +// +// DNSimple API docs: http://developer.dnsimple.com/registrar/#renew +func (s *RegistrarService) Renew(domain string, renewWhoisPrivacy bool) (Domain, *Response, error) { + request := domainRequest{Domain: renewDomain{ + Name: domain, + RenewWhoisPrivacy: renewWhoisPrivacy, + }} + returnedDomain := domainWrapper{} + + res, err := s.client.post("domain_renewals", request, &returnedDomain) + if err != nil { + return Domain{}, res, err + } + + return returnedDomain.Domain, res, nil +} + +// EnableAutoRenewal enables the auto-renewal feature for the domain. +// +// DNSimple API docs: http://developer.dnsimple.com/registrar/autorenewal/#enable +func (s *RegistrarService) EnableAutoRenewal(domain interface{}) (*Response, error) { + path := fmt.Sprintf("%s/auto_renewal", domainPath(domain)) + + res, err := s.client.post(path, nil, nil) + if err != nil { + return res, err + } + + return res, nil +} + +// DisableAutoRenewal disables the auto-renewal feature for the domain. +// +// DNSimple API docs: http://developer.dnsimple.com/registrar/autorenewal/#disable +func (s *RegistrarService) DisableAutoRenewal(domain interface{}) (*Response, error) { + path := fmt.Sprintf("%s/auto_renewal", domainPath(domain)) + + res, err := s.client.delete(path, nil) + if err != nil { + return res, err + } + + return res, nil +} diff --git a/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/users.go b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/users.go new file mode 100644 index 0000000..601fb09 --- /dev/null +++ b/Godeps/_workspace/src/github.com/weppos/go-dnsimple/dnsimple/users.go @@ -0,0 +1,35 @@ +package dnsimple + +import () + +// UsersService handles communication with the uer related +// methods of the DNSimple API. +// +// DNSimple API docs: http://developer.dnsimple.com/users/ +type UsersService struct { + client *Client +} + +// User represents a DNSimple user. +type User struct { + Id int `json:"id,omitempty"` + Email string `json:"email,omitempty"` +} + +type userWrapper struct { + User User `json:"user"` +} + +// User gets the logged in user. +// +// DNSimple API docs: http://developer.dnsimple.com/users/ +func (s *UsersService) User() (User, *Response, error) { + wrappedUser := userWrapper{} + + res, err := s.client.get("user", &wrappedUser) + if err != nil { + return User{}, res, err + } + + return wrappedUser.User, res, nil +} diff --git a/providers/dnsimple.go b/providers/dnsimple.go new file mode 100644 index 0000000..64020a6 --- /dev/null +++ b/providers/dnsimple.go @@ -0,0 +1,171 @@ +package providers + +import ( + "fmt" + "github.com/Sirupsen/logrus" + "github.com/rancher/external-dns/dns" + "github.com/weppos/go-dnsimple/dnsimple" + "os" + "strings" +) + +func init() { + apiToken := os.Getenv("DNSIMPLE_TOKEN") + if len(apiToken) == 0 { + logrus.Info("DNSIMPLE_TOKEN is not set, skipping init of DNSimple provider") + return + } + + email := os.Getenv("DNSIMPLE_EMAIL") + if len(email) == 0 { + logrus.Info("DNSIMPLE_EMAIL is not set, skipping init of DNSimple provider") + return + } + + dnsimpleHandler := &DNSimpleHandler{} + if err := RegisterProvider("dnsimple", dnsimpleHandler); err != nil { + logrus.Fatal("Could not register dnsimple provider") + } + + dnsimpleHandler.root = strings.TrimSuffix(dns.RootDomainName, ".") + dnsimpleHandler.client = dnsimple.NewClient(apiToken, email) + + domains, _, err := dnsimpleHandler.client.Domains.List() + if err != nil { + logrus.Fatalf("Failed to list hosted zones: %v", err) + } + + found := false + for _, domain := range domains { + if domain.Name == dnsimpleHandler.root { + found = true + break + } + } + + if !found { + logrus.Fatalf("Hosted zone %s is missing", dnsimpleHandler.root) + } + + logrus.Infof("Configured %s with hosted zone %q ", dnsimpleHandler.GetName(), dnsimpleHandler.root) +} + +type DNSimpleHandler struct { + client *dnsimple.Client + root string +} + +func (*DNSimpleHandler) GetName() string { + return "DNSimple" +} + +func (d *DNSimpleHandler) parseName(record dns.DnsRecord) string { + name := strings.TrimSuffix(record.Fqdn, fmt.Sprintf(".%s.", d.root)) + + return name +} + +func (d *DNSimpleHandler) AddRecord(record dns.DnsRecord) error { + name := d.parseName(record) + for _, rec := range record.Records { + recordInput := dnsimple.Record{ + Name: name, + TTL: record.TTL, + Type: record.Type, + Content: rec, + } + _, _, err := d.client.Domains.CreateRecord(d.root, recordInput) + if err != nil { + return fmt.Errorf("DNSimple API call has failed: %v", err) + } + } + + return nil +} + +func (d *DNSimpleHandler) findRecords(record dns.DnsRecord) ([]dnsimple.Record, error) { + var records []dnsimple.Record + resp, _, err := d.client.Domains.ListRecords(d.root, "", "") + if err != nil { + return records, fmt.Errorf("DNSimple API call has failed: %v", err) + } + + name := d.parseName(record) + for _, rec := range resp { + if rec.Name == name && rec.Type == record.Type { + records = append(records, rec) + } + } + + return records, nil +} + +func (d *DNSimpleHandler) UpdateRecord(record dns.DnsRecord) error { + err := d.RemoveRecord(record) + if err != nil { + return err + } + + return d.AddRecord(record) +} + +func (d *DNSimpleHandler) RemoveRecord(record dns.DnsRecord) error { + records, err := d.findRecords(record) + if err != nil { + return err + } + + for _, rec := range records { + _, err := d.client.Domains.DeleteRecord(d.root, rec.Id) + if err != nil { + return fmt.Errorf("DNSimple API call has failed: %v", err) + } + } + + return nil +} + +func (d *DNSimpleHandler) GetRecords() ([]dns.DnsRecord, error) { + var records []dns.DnsRecord + + recordResp, _, err := d.client.Domains.ListRecords(d.root, "", "") + if err != nil { + return records, fmt.Errorf("DNSimple API call has failed: %v", err) + } + + recordMap := map[string]map[string][]string{} + recordTTLs := map[string]map[string]int{} + + for _, rec := range recordResp { + var fqdn string + if rec.Name == "" { + fqdn = dns.RootDomainName + } else { + fqdn = fmt.Sprintf("%s.%s", rec.Name, dns.RootDomainName) + } + + recordTTLs[fqdn] = map[string]int{} + recordTTLs[fqdn][rec.Type] = rec.TTL + recordSet, exists := recordMap[fqdn] + if exists { + recordSlice, sliceExists := recordSet[rec.Type] + if sliceExists { + recordSlice = append(recordSlice, rec.Content) + } else { + recordSet[rec.Type] = []string{rec.Content} + } + } else { + recordMap[fqdn] = map[string][]string{} + recordMap[fqdn][rec.Type] = []string{rec.Content} + } + } + + for fqdn, recordSet := range recordMap { + for recordType, recordSlice := range recordSet { + ttl := recordTTLs[fqdn][recordType] + record := dns.DnsRecord{Fqdn: fqdn, Records: recordSlice, Type: recordType, TTL: ttl} + records = append(records, record) + } + } + return records, nil +} diff --git a/providers/route53.go b/providers/route53.go index d49aca9..f055634 100644 --- a/providers/route53.go +++ b/providers/route53.go @@ -21,6 +21,21 @@ var ( ) func init() { + if len(os.Getenv("AWS_REGION")) == 0 { + logrus.Info("AWS_REGION is not set, skipping init of Route53 provider") + return + } + + if len(os.Getenv("AWS_ACCESS_KEY")) == 0 { + logrus.Info("AWS_ACCESS_KEY is not set, skipping init of Route53 provider") + return + } + + if len(os.Getenv("AWS_SECRET_KEY")) == 0 { + logrus.Info("AWS_SECRET_KEY is not set, skipping init of Route53 provider") + return + } + route53Handler := &Route53Handler{} if err := RegisterProvider("route53", route53Handler); err != nil { logrus.Fatal("Could not register route53 provider") From 6763550a1db6838673e897221335ca7d57e990ba Mon Sep 17 00:00:00 2001 From: Nathan LaFreniere Date: Mon, 23 Nov 2015 13:16:49 -0800 Subject: [PATCH 2/2] remove unneeded check --- providers/route53.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/providers/route53.go b/providers/route53.go index f055634..876bc92 100644 --- a/providers/route53.go +++ b/providers/route53.go @@ -55,10 +55,6 @@ func init() { func setRegion() error { regionName := os.Getenv("AWS_REGION") - if len(regionName) == 0 { - return fmt.Errorf("AWS_REGION is not set") - } - r, ok := aws.Regions[regionName] if !ok { return fmt.Errorf("Could not find region by name %s", regionName)