Merge pull request #457 from jvassev/dyn-provider

Add Dyn Provider
This commit is contained in:
Nick Jüttner 2018-02-09 11:11:00 +01:00 committed by GitHub
commit 9ed9bae16d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 3769 additions and 5 deletions

8
Gopkg.lock generated
View File

@ -284,6 +284,12 @@
packages = ["pbutil"]
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
[[projects]]
name = "github.com/nesv/go-dynect"
packages = ["dynect"]
revision = "cdd946344b54bdf7dbeac406c2f1fe93150f08ea"
version = "v0.6.0"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
@ -616,6 +622,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "9056760f59de670713a6534dc50e7b4ef8f14b1ad25e5a4eb0e269d2d60c4a51"
inputs-digest = "7af57b8d195abce34060c82b92243ddba947f5d882be8f9a08b1aa827a9061fa"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -59,3 +59,7 @@ ignored = ["github.com/kubernetes/repo-infra/kazel"]
[[override]]
name = "github.com/kubernetes/repo-infra"
revision = "2d2eb5e12b4663fc4d764b5db9daab39334d3f37"
[[constraint]]
name = "github.com/nesv/go-dynect"
version = "0.6.0"

View File

@ -46,7 +46,7 @@ SOURCES = $(shell find . -name '*.go')
IMAGE ?= registry.opensource.zalan.do/teapot/$(BINARY)
VERSION ?= $(shell git describe --tags --always --dirty)
BUILD_FLAGS ?= -v
LDFLAGS ?= -X github.com/kubernetes-incubator/external-dns/pkg/apis/externaldns.version=$(VERSION) -w -s
LDFLAGS ?= -X github.com/kubernetes-incubator/external-dns/pkg/apis/externaldns.Version=$(VERSION) -w -s
build: build/$(BINARY)

View File

@ -31,6 +31,7 @@ ExternalDNS' current release is `v0.4`. This version allows you to keep selected
* [DigitalOcean](https://www.digitalocean.com/products/networking)
* [DNSimple](https://dnsimple.com/)
* [Infoblox](https://www.infoblox.com/products/dns/)
* [Dyn](https://dyn.com/dns/)
From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.4` with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.
@ -47,6 +48,7 @@ The following tutorials are provided:
* [Cloudflare](docs/tutorials/cloudflare.md)
* [DigitalOcean](docs/tutorials/digitalocean.md)
* [Infoblox](docs/tutorials/infoblox.md)
* [Dyn](docs/tutorials/dyn.md)
* Google Container Engine
* [Using Google's Default Ingress Controller](docs/tutorials/gke.md)
* [Using the Nginx Ingress Controller](docs/tutorials/nginx-ingress.md)

145
docs/tutorials/dyn.md Normal file
View File

@ -0,0 +1,145 @@
# Setting up ExternalDNS for Dyn
## Creating a Dyn Configuration Secret
For ExternalDNS to access the Dyn API, create a Kubernetes secret.
To create the secret:
```
$ kubectl create secret generic external-dns \
--from-literal=EXTERNAL_DNS_DYN_CUSTOMER_NAME=${DYN_CUSTOMER_NAME} \
--from-literal=EXTERNAL_DNS_DYN_USERNAME=${DYN_USERNAME} \
--from-literal=EXTERNAL_DNS_DYN_PASSWORD=${DYN_PASSWORD}
```
The credentials are the same ones created during account registration. As best practise, you are advised to
create an API-only user that is entitled to only the zones intended to be changed by ExternalDNS
## Deploy ExternalDNS
The rest of this tutorial assumes you own `example.com` domain and your DNS provider is Dyn. Change `example.com`
with a domain/zone that you really own.
In case of the dyn provider, the flag `--zone-id-filter` is mandatory as it specifies which zones to scan for records. Without it
Create a deployment file called `externaldns.yaml` with the following contents:
```
$ cat > externaldns.yaml <<EOF
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:v0.4.8
args:
- --source=ingress
- --txt-prefix=_d
- --namespace=example
- --zone-id-filter=example.com
- --domain-filter=example.com
- --provider=dyn
env:
- name: EXTERNAL_DNS_DYN_CUSTOMER_NAME
valueFrom:
secretKeyRef:
name: external-dns
key: EXTERNAL_DNS_DYN_CUSTOMER_NAME
- name: EXTERNAL_DNS_DYN_USERNAME
valueFrom:
secretKeyRef:
name: external-dns
key: EXTERNAL_DNS_DYN_USERNAME
- name: EXTERNAL_DNS_DYN_PASSWORD
valueFrom:
secretKeyRef:
name: external-dns
key: EXTERNAL_DNS_DYN_PASSWORD
EOF
```
As we'll be creating an Ingress resource, you need `--txt-prefix=_d` as a CNAME cannot coexist with a TXT record. You can change the prefix to
any valid start of a FQDN.
Create the deployment for ExternalDNS:
```
$ kubectl create -f externaldns.yaml
```
## Running a locally build version
If you just want to test ExternalDNS in dry-run mode locally without doing the above deployment you can also do it.
Make sure your kubectl is configured correctly . Assuming you have the sources, build and run it like so:
```bash
make
# output skipped
./build/external-dns \
--provider=dyn \
--dyn-customer-name=${DYN_CUSTOMER_NAME} \
--dyn-username=${DYN_USERNAME} \
--dyn-password=${DYN_PASSWORD} \
--domain-filter=example.com \
--zone-id-filter=example.com \
--namespace=example \
--log-level=debug \
--txt-prefix=_ \
--dry-run=true
INFO[0000] running in dry-run mode. No changes to DNS records will be made.
INFO[0000] Connected to cluster at https://some-k8s-cluster.example.com
INFO[0001] Zones: [example.com]
# output skipped
```
Having `--dry-run=true` and `--log-level=debug` is a great way to see _exactly_ what DynamicDNS is doing or is about to do.
## Deploying an Ingress Resource
Create a file called 'test-ingress.yaml' with the following contents:
```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test-ingress
namespace: example
spec:
rules:
- host: test-ingress.example.com
http:
paths:
- backend:
serviceName: my-awesome-service
servicePort: 8080
```
As the DNS name `test-ingress.example.com` matches the filter, external-dns will create two records:
a CNAME for test-ingress.example.com and TXT for _dtest-ingress.example.com.
Create the Igress:
```
$ kubectl create -f test-ingress.yaml
```
By default external-dns scans for changes every minute so give it some time to catch up with the
## Verifying Dyn DNS records
Login to the console at https://portal.dynect.net/login/ and verify records are created
## Clean up
Login to the console at https://portal.dynect.net/login/ and delete the records created. Alternatively, just delete the sample
Ingress resources and external-dns will delete the records.

12
main.go
View File

@ -118,6 +118,18 @@ func main() {
DryRun: cfg.DryRun,
},
)
case "dyn":
p, err = provider.NewDynProvider(
provider.DynConfig{
DomainFilter: domainFilter,
ZoneIDFilter: zoneIDFilter,
DryRun: cfg.DryRun,
CustomerName: cfg.DynCustomerName,
Username: cfg.DynUsername,
Password: cfg.DynPassword,
AppVersion: externaldns.Version,
},
)
case "inmemory":
p, err = provider.NewInMemoryProvider(provider.InMemoryInitZones(cfg.InMemoryZones), provider.InMemoryWithDomain(domainFilter), provider.InMemoryWithLogging()), nil
default:

View File

@ -25,7 +25,8 @@ import (
)
var (
version = "unknown"
// Version is the current version of the app, generated at build time
Version = "unknown"
)
// Config is a project-wide configuration
@ -52,6 +53,9 @@ type Config struct {
InfobloxWapiPassword string
InfobloxWapiVersion string
InfobloxSSLVerify bool
DynCustomerName string
DynUsername string
DynPassword string
InMemoryZones []string
Policy string
Registry string
@ -117,7 +121,7 @@ func allLogLevelsAsStrings() []string {
// ParseFlags adds and parses flags from command line
func (cfg *Config) ParseFlags(args []string) error {
app := kingpin.New("external-dns", "ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.\n\nNote that all flags may be replaced with env vars - `--flag` -> `EXTERNAL_DNS_FLAG=1` or `--flag value` -> `EXTERNAL_DNS_FLAG=value`")
app.Version(version)
app.Version(Version)
app.DefaultEnvars()
// Flags related to Kubernetes
@ -133,7 +137,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
// Flags related to providers
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, dnsimple, infoblox, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "inmemory")
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, google, azure, cloudflare, digitalocean, dnsimple, infoblox, dyn, inmemory)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "google", "azure", "cloudflare", "digitalocean", "dnsimple", "infoblox", "dyn", "inmemory")
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter)
app.Flag("google-project", "When using the Google provider, specify the Google project (required when --provider=google)").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
@ -147,6 +151,9 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("infoblox-wapi-password", "When using the Infoblox provider, specify the WAPI password (required when --provider=infoblox)").Default(defaultConfig.InfobloxWapiPassword).StringVar(&cfg.InfobloxWapiPassword)
app.Flag("infoblox-wapi-version", "When using the Infoblox provider, specify the WAPI version (default: 2.3.1)").Default(defaultConfig.InfobloxWapiVersion).StringVar(&cfg.InfobloxWapiVersion)
app.Flag("infoblox-ssl-verify", "When using the Infoblox provider, specify whether to verify the SSL certificate (default: true, disable with --no-infoblox-ssl-verify)").Default(strconv.FormatBool(defaultConfig.InfobloxSSLVerify)).BoolVar(&cfg.InfobloxSSLVerify)
app.Flag("dyn-customer-name", "When using the Dyn provider, specify the Customer Name").Default("").StringVar(&cfg.DynCustomerName)
app.Flag("dyn-username", "When using the Dyn provider, specify the Username").Default("").StringVar(&cfg.DynUsername)
app.Flag("dyn-password", "When using the Dyn provider, specify the pasword").Default("").StringVar(&cfg.DynPassword)
app.Flag("inmemory-zone", "Provide a list of pre-configured zones for the inmemory provider; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.InMemoryZones)
// Flags related to policies

564
provider/dyn.go Normal file
View File

@ -0,0 +1,564 @@
/*
Copyright 2018 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 provider
import (
"fmt"
"os"
"strings"
"time"
log "github.com/sirupsen/logrus"
"github.com/nesv/go-dynect/dynect"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
)
const (
// 10 minutes default timeout if not configured using flags
dynDefaultTTL = 600
// can store 20000 entries globally, that's about 4MB of memory
// may be made configurable in the future but 20K records seems like enough for a few zones
cacheMaxSize = 20000
// this prefix must be stripped from resource links before feeding them to dynect.Client.Do()
restAPIPrefix = "/REST/"
)
// A simple non-thread-safe cache with TTL. The TTL of the records is used here to
// This cache is used to save on requests to DynAPI
type cache struct {
contents map[string]*entry
}
type entry struct {
expires int64
ep *endpoint.Endpoint
}
func (c *cache) Put(link string, ep *endpoint.Endpoint) {
// flush the whole cache on overflow
if len(c.contents) >= cacheMaxSize {
c.contents = make(map[string]*entry)
}
c.contents[link] = &entry{
ep: ep,
expires: int64(time.Now().Unix()) + int64(ep.RecordTTL),
}
}
func (c *cache) Get(link string) *endpoint.Endpoint {
result, ok := c.contents[link]
if !ok {
return nil
}
now := int64(time.Now().Unix())
if result.expires < now {
delete(c.contents, link)
return nil
}
return result.ep
}
// DynConfig hold connection parameters to dyn.com and interanl state
type DynConfig struct {
DomainFilter DomainFilter
ZoneIDFilter ZoneIDFilter
DryRun bool
CustomerName string
Username string
Password string
AppVersion string
DynVersion string
}
// DynProvider is the actual interface impl.
type dynProviderState struct {
DynConfig
Cache *cache
}
// ZoneChange is missing from dynect: https://help.dyn.com/get-zone-changeset-api/
type ZoneChange struct {
ID int `json:"id"`
UserID int `json:"user_id"`
Zone string `json:"zone"`
FQDN string `json:"FQDN"`
Serial int `json:"serial"`
TTL int `json:"ttl"`
Type string `json:"rdata_type"`
RData dynect.DataBlock `json:"rdata"`
}
// ZoneChangesResponse is missing from dynect: https://help.dyn.com/get-zone-changeset-api/
type ZoneChangesResponse struct {
dynect.ResponseBlock
Data []ZoneChange `json:"data"`
}
// ZonePublishRequest is missing from dynect but the notes field is a nice place to let
// external-dns report some internal info during commit
type ZonePublishRequest struct {
Publish bool `json:"publish"`
Notes string `json:"notes"`
}
// ZonePublisResponse holds the status after publish
type ZonePublisResponse struct {
dynect.ResponseBlock
Data map[string]interface{} `json:"data"`
}
// NewDynProvider initializes a new Dyn Provider.
func NewDynProvider(config DynConfig) (Provider, error) {
return &dynProviderState{
DynConfig: config,
Cache: &cache{
contents: make(map[string]*entry),
},
}, nil
}
// filterAndFixLinks removes from `links` all the records we don't care about
// and strops the /REST/ prefix
func filterAndFixLinks(links []string, filter DomainFilter) []string {
var result []string
for _, link := range links {
link = strings.TrimPrefix(link, restAPIPrefix)
// covert resource link to just the FQDN so we can filter on it
domain := link[0:strings.LastIndexByte(link, '/')]
domain = domain[strings.LastIndexByte(domain, '/')+1:]
// simply ignore all record types we don't care about
if !strings.HasPrefix(link, endpoint.RecordTypeA) &&
!strings.HasPrefix(link, endpoint.RecordTypeCNAME) &&
!strings.HasPrefix(link, endpoint.RecordTypeTXT) {
continue
}
if filter.Match(domain) {
result = append(result, link)
}
}
return result
}
func fixMissingTTL(ttl endpoint.TTL) string {
i := dynDefaultTTL
if ttl.IsConfigured() {
i = int(ttl)
}
return fmt.Sprintf("%d", i)
}
// merge produces a singe list of records that can be used as a replacement.
// Dyn allows to replace all records with a single call
// Invariant: the result contains only elements from the updateNew parameter
func merge(updateOld, updateNew []*endpoint.Endpoint) []*endpoint.Endpoint {
findMatch := func(template *endpoint.Endpoint) *endpoint.Endpoint {
for _, new := range updateNew {
if template.DNSName == new.DNSName &&
template.RecordType == new.RecordType {
return new
}
}
return nil
}
var result []*endpoint.Endpoint
for _, old := range updateOld {
matchingNew := findMatch(old)
if matchingNew == nil {
// no match, shouldn't happen
continue
}
if matchingNew.Target != old.Target {
// new target: always update, TTL will be overwritten too if necessary
result = append(result, matchingNew)
continue
}
if matchingNew.RecordTTL != 0 && matchingNew.RecordTTL != old.RecordTTL {
// same target, but new non-zero TTL set in k8s, must update
// probably would happen only if there is a bug in the code calling the provider
result = append(result, matchingNew)
}
}
return result
}
// extractTarget populates the correct field given a record type.
// See dynect.DataBlock comments for details. Empty response means nothing
// was populated - basically an error
func extractTarget(recType string, data *dynect.DataBlock) string {
result := ""
if recType == endpoint.RecordTypeA {
result = data.Address
}
if recType == endpoint.RecordTypeCNAME {
result = data.CName
result = strings.TrimSuffix(result, ".")
}
if recType == endpoint.RecordTypeTXT {
result = data.TxtData
}
return result
}
// recordLinkToEndpoint makes an Endpoint given a resource link optinally making a remote call if a cached entry is expired
func (d *dynProviderState) recordLinkToEndpoint(client *dynect.Client, recordLink string) (*endpoint.Endpoint, error) {
result := d.Cache.Get(recordLink)
if result != nil {
log.Infof("Using cached endpoint for %s: %+v", recordLink, result)
return result, nil
}
rec := dynect.RecordResponse{}
err := client.Do("GET", recordLink, nil, &rec)
if err != nil {
return nil, err
}
// ignore all records but the types supported by external-
target := extractTarget(rec.Data.RecordType, &rec.Data.RData)
if target == "" {
return nil, nil
}
result = &endpoint.Endpoint{
DNSName: rec.Data.FQDN,
RecordTTL: endpoint.TTL(rec.Data.TTL),
RecordType: rec.Data.RecordType,
Target: target,
}
log.Debugf("Fetched new endpoint for %s: %+v", recordLink, result)
d.Cache.Put(recordLink, result)
return result, nil
}
func errorOrValue(err error, value interface{}) interface{} {
if err == nil {
return value
}
return err
}
// endpointToRecord puts the Target of an Endpoint in the correct field of DataBlock.
// See DataBlock comments for more info
func endpointToRecord(ep *endpoint.Endpoint) *dynect.DataBlock {
result := dynect.DataBlock{}
if ep.RecordType == endpoint.RecordTypeA {
result.Address = ep.Target
} else if ep.RecordType == endpoint.RecordTypeCNAME {
result.CName = ep.Target
} else if ep.RecordType == endpoint.RecordTypeTXT {
result.TxtData = ep.Target
}
return &result
}
// fetchAllRecordLinksInZone list all records in a zone with a single call. Records not matched by the
// DomainFilter are ignored. The response is a list of links that can be fed to dynect.Client.Do()
// directly
func (d *dynProviderState) fetchAllRecordLinksInZone(client *dynect.Client, zone string) ([]string, error) {
var allRecords dynect.AllRecordsResponse
err := client.Do("GET", fmt.Sprintf("AllRecord/%s/", zone), nil, &allRecords)
if err != nil {
return nil, err
}
return filterAndFixLinks(allRecords.Data, d.DomainFilter), nil
}
// buildLinkToRecord build a resource link. The symmetry of the dyn API is used to save
// switch-case boilerplate.
// Empty response means the endpoint is not mappable to a records link: either because the fqdn
// is not matched by the domainFilter or it is in the wrong zone
func (d *dynProviderState) buildLinkToRecord(ep *endpoint.Endpoint) string {
if ep == nil {
return ""
}
var matchingZone = ""
for _, zone := range d.ZoneIDFilter.zoneIDs {
if strings.HasSuffix(ep.DNSName, zone) {
matchingZone = zone
break
}
}
if matchingZone == "" {
fmt.Printf("no zone")
// no matching zone, ignore
return ""
}
if !d.DomainFilter.Match(ep.DNSName) {
// no matching domain, ignore
return ""
}
return fmt.Sprintf("%sRecord/%s/%s/", ep.RecordType, matchingZone, ep.DNSName)
}
// create a dynect client and performs login. You need to clean it up.
// This method also stores the DynAPI version.
// Don't user the dynect.Client.Login()
func (d *dynProviderState) login() (*dynect.Client, error) {
client := dynect.NewClient(d.CustomerName)
var req = dynect.LoginBlock{
Username: d.Username,
Password: d.Password,
CustomerName: d.CustomerName}
var resp dynect.LoginResponse
err := client.Do("POST", "Session", req, &resp)
if err != nil {
return nil, err
}
client.Token = resp.Data.Token
// this is the only change from the original
d.DynVersion = resp.Data.Version
return client, nil
}
// the zones we are allowed to touch. Currently only exact matches are considered, not all
// zones with the given suffix
func (d *dynProviderState) zones(client *dynect.Client) []string {
return d.ZoneIDFilter.zoneIDs
}
func (d *dynProviderState) buildRecordRequest(ep *endpoint.Endpoint) (string, *dynect.RecordRequest) {
link := d.buildLinkToRecord(ep)
if link == "" {
return "", nil
}
record := dynect.RecordRequest{
TTL: fixMissingTTL(ep.RecordTTL),
RData: *endpointToRecord(ep),
}
return link, &record
}
// deleteRecord deletes all existing records (CNAME, TXT, A) for the given Endpoint.DNSName with 1 API call
func (d *dynProviderState) deleteRecord(client *dynect.Client, ep *endpoint.Endpoint) error {
link := d.buildLinkToRecord(ep)
if link == "" {
return nil
}
response := dynect.RecordResponse{}
err := client.Do("DELETE", link, nil, &response)
log.Debugf("Deleting record %s: %+v,", link, errorOrValue(err, &response))
return err
}
// replaceRecord replaces all existing records pf the given type for the Endpoint.DNSName with 1 API call
func (d *dynProviderState) replaceRecord(client *dynect.Client, ep *endpoint.Endpoint) error {
link, record := d.buildRecordRequest(ep)
if link == "" {
return nil
}
response := dynect.RecordResponse{}
err := client.Do("PUT", link, record, &response)
log.Debugf("Replacing record %s: %+v,", link, errorOrValue(err, &response))
return err
}
// createRecord creates a single record with 1 API call
func (d *dynProviderState) createRecord(client *dynect.Client, ep *endpoint.Endpoint) error {
link, record := d.buildRecordRequest(ep)
if link == "" {
return nil
}
response := dynect.RecordResponse{}
err := client.Do("POST", link, record, &response)
log.Debugf("Creating record %s: %+v,", link, errorOrValue(err, &response))
return err
}
// commit commits all pending changes. It will always attempt to commit, if there are no
func (d *dynProviderState) commit(client *dynect.Client) error {
errs := []error{}
for _, zone := range d.zones(client) {
// extra call if in debug mode to fetch pending changes
if log.GetLevel() >= log.DebugLevel {
response := ZoneChangesResponse{}
err := client.Do("GET", fmt.Sprintf("ZoneChanges/%s/", zone), nil, &response)
log.Debugf("Pending changes for zone %s: %+v", zone, errorOrValue(err, &response))
}
h, err := os.Hostname()
if err != nil {
h = "unknown-host"
}
notes := fmt.Sprintf("Change by external-dns@%s, DynAPI@%s, %s on %s",
d.AppVersion,
d.DynVersion,
time.Now().Format(time.RFC3339),
h,
)
zonePublish := ZonePublishRequest{
Publish: true,
Notes: notes,
}
response := ZonePublisResponse{}
err = client.Do("PUT", fmt.Sprintf("Zone/%s/", zone), &zonePublish, &response)
log.Infof("Commiting changes for zone %s: %+v", zone, errorOrValue(err, &response))
}
switch len(errs) {
case 0:
return nil
case 1:
return errs[0]
default:
return fmt.Errorf("Multiple errors committing: %+v", errs)
}
}
// Records makes on average C + 2*Z requests (Z = number of zones): 1 login + 1 fetchAllRecords
// A cache is used to avoid querying for every single record found. C is proportional to the number
// of expired/changed records
func (d *dynProviderState) Records() ([]*endpoint.Endpoint, error) {
client, err := d.login()
if err != nil {
return nil, err
}
defer client.Logout()
log.Debugf("Using DynAPI@%s", d.DynVersion)
var result []*endpoint.Endpoint
zones := d.zones(client)
log.Infof("Zones found: %+v", zones)
for _, zone := range zones {
recordLinks, err := d.fetchAllRecordLinksInZone(client, zone)
if err != nil {
return nil, err
}
log.Infof("Relevant records found in zone %s: %+v", zone, recordLinks)
for _, link := range recordLinks {
ep, err := d.recordLinkToEndpoint(client, link)
if err != nil {
return nil, err
}
if ep != nil {
result = append(result, ep)
}
}
}
return result, nil
}
// this method does C + 2*Z requests: C=total number of changes, Z = number of
// affected zones (1 login + 1 commit)
func (d *dynProviderState) ApplyChanges(changes *plan.Changes) error {
log.Debugf("Processing chages: %+v", changes)
if d.DryRun {
log.Infof("Will NOT delete these records: %+v", changes.Delete)
log.Infof("Will NOT create these records: %+v", changes.Create)
log.Infof("Will NOT update these records: %+v", merge(changes.UpdateOld, changes.UpdateNew))
return nil
}
client, err := d.login()
if err != nil {
return err
}
defer client.Logout()
var errs []error
needsCommit := false
for _, ep := range changes.Delete {
err := d.deleteRecord(client, ep)
if err != nil {
errs = append(errs, err)
} else {
needsCommit = true
}
}
for _, ep := range changes.Create {
err := d.createRecord(client, ep)
if err != nil {
errs = append(errs, err)
} else {
needsCommit = true
}
}
updates := merge(changes.UpdateOld, changes.UpdateNew)
log.Debugf("Updates after merging: %+v", updates)
for _, ep := range updates {
err := d.replaceRecord(client, ep)
if err != nil {
errs = append(errs, err)
} else {
needsCommit = true
}
}
switch len(errs) {
case 0:
case 1:
return errs[0]
default:
return fmt.Errorf("Multiple errors committing: %+v", errs)
}
if needsCommit {
return d.commit(client)
}
return nil
}

298
provider/dyn_test.go Normal file
View File

@ -0,0 +1,298 @@
/*
Copyright 2018 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 provider
import (
"errors"
"fmt"
"testing"
"time"
"github.com/nesv/go-dynect/dynect"
"github.com/stretchr/testify/assert"
"github.com/kubernetes-incubator/external-dns/endpoint"
)
func TestDynMerge_NoUpdateOnTTL0Changes(t *testing.T) {
updateOld := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeA,
},
{
DNSName: "name2",
Target: "target2",
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeA,
},
}
updateNew := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
RecordTTL: endpoint.TTL(0),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
RecordTTL: endpoint.TTL(0),
RecordType: endpoint.RecordTypeCNAME,
},
}
assert.Equal(t, 0, len(merge(updateOld, updateNew)))
}
func TestDynMerge_UpdateOnTTLChanges(t *testing.T) {
updateOld := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeCNAME,
},
}
updateNew := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
RecordTTL: endpoint.TTL(77),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
RecordTTL: endpoint.TTL(10),
RecordType: endpoint.RecordTypeCNAME,
},
}
merged := merge(updateOld, updateNew)
assert.Equal(t, 2, len(merged))
assert.Equal(t, "name1", merged[0].DNSName)
}
func TestDynMerge_AlwaysUpdateTarget(t *testing.T) {
updateOld := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
RecordTTL: endpoint.TTL(1),
RecordType: endpoint.RecordTypeCNAME,
},
}
updateNew := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1-changed",
RecordTTL: endpoint.TTL(0),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
RecordTTL: endpoint.TTL(0),
RecordType: endpoint.RecordTypeCNAME,
},
}
merged := merge(updateOld, updateNew)
assert.Equal(t, 1, len(merged))
assert.Equal(t, "target1-changed", merged[0].Target)
}
func TestDynMerge_NoUpdateIfTTLUnchanged(t *testing.T) {
updateOld := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
RecordTTL: endpoint.TTL(55),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
RecordTTL: endpoint.TTL(55),
RecordType: endpoint.RecordTypeCNAME,
},
}
updateNew := []*endpoint.Endpoint{
{
DNSName: "name1",
Target: "target1",
RecordTTL: endpoint.TTL(55),
RecordType: endpoint.RecordTypeCNAME,
},
{
DNSName: "name2",
Target: "target2",
RecordTTL: endpoint.TTL(55),
RecordType: endpoint.RecordTypeCNAME,
},
}
merged := merge(updateOld, updateNew)
assert.Equal(t, 0, len(merged))
}
func TestDyn_extractTarget(t *testing.T) {
tests := []struct {
recordType string
block *dynect.DataBlock
target string
}{
{"A", &dynect.DataBlock{Address: "address"}, "address"},
{"CNAME", &dynect.DataBlock{CName: "name."}, "name"}, // note trailing dot is trimmed for CNAMEs
{"TXT", &dynect.DataBlock{TxtData: "text."}, "text."},
}
for _, tc := range tests {
assert.Equal(t, tc.target, extractTarget(tc.recordType, tc.block))
}
}
func TestDyn_endpointToRecord(t *testing.T) {
tests := []struct {
ep *endpoint.Endpoint
extractor func(*dynect.DataBlock) string
}{
{endpoint.NewEndpoint("address", "the-target", "A"), func(b *dynect.DataBlock) string { return b.Address }},
{endpoint.NewEndpoint("cname", "the-target", "CNAME"), func(b *dynect.DataBlock) string { return b.CName }},
{endpoint.NewEndpoint("text", "the-target", "TXT"), func(b *dynect.DataBlock) string { return b.TxtData }},
}
for _, tc := range tests {
block := endpointToRecord(tc.ep)
assert.Equal(t, "the-target", tc.extractor(block))
}
}
func TestDyn_buildLinkToRecord(t *testing.T) {
provider := &dynProviderState{
DynConfig: DynConfig{
ZoneIDFilter: NewZoneIDFilter([]string{"example.com"}),
DomainFilter: NewDomainFilter([]string{"the-target.example.com"}),
},
}
tests := []struct {
ep *endpoint.Endpoint
link string
}{
{endpoint.NewEndpoint("sub.the-target.example.com", "address", "A"), "ARecord/example.com/sub.the-target.example.com/"},
{endpoint.NewEndpoint("the-target.example.com", "cname", "CNAME"), "CNAMERecord/example.com/the-target.example.com/"},
{endpoint.NewEndpoint("the-target.example.com", "text", "TXT"), "TXTRecord/example.com/the-target.example.com/"},
{endpoint.NewEndpoint("the-target.google.com", "text", "TXT"), ""},
{endpoint.NewEndpoint("mail.example.com", "text", "TXT"), ""},
{nil, ""},
}
for _, tc := range tests {
assert.Equal(t, tc.link, provider.buildLinkToRecord(tc.ep))
}
}
func TestDyn_errorOrValue(t *testing.T) {
e := errors.New("an error")
val := "value"
assert.Equal(t, e, errorOrValue(e, val))
assert.Equal(t, val, errorOrValue(nil, val))
}
func TestDyn_filterAndFixLinks(t *testing.T) {
links := []string{
"/REST/ARecord/example.com/the-target.example.com/",
"/REST/ARecord/example.com/the-target.google.com/",
"/REST/TXTRecord/example.com/the-target.example.com/",
"/REST/TXTRecord/example.com/the-target.google.com/",
"/REST/CNAMERecord/example.com/the-target.google.com/",
"/REST/CNAMERecord/example.com/the-target.example.com/",
"/REST/NSRecord/example.com/the-target.google.com/",
"/REST/NSRecord/example.com/the-target.example.com/",
}
filter := NewDomainFilter([]string{"example.com"})
result := filterAndFixLinks(links, filter)
// should skip non-example.com records and NS records too
assert.Equal(t, 3, len(result))
assert.Equal(t, "ARecord/example.com/the-target.example.com/", result[0])
assert.Equal(t, "TXTRecord/example.com/the-target.example.com/", result[1])
assert.Equal(t, "CNAMERecord/example.com/the-target.example.com/", result[2])
}
func TestDyn_fixMissingTTL(t *testing.T) {
assert.Equal(t, fmt.Sprintf("%v", dynDefaultTTL), fixMissingTTL(endpoint.TTL(0)))
// nothing to fix
assert.Equal(t, "111", fixMissingTTL(endpoint.TTL(111)))
}
func TestDyn_cachePut(t *testing.T) {
c := cache{
contents: make(map[string]*entry),
}
c.Put("link", &endpoint.Endpoint{
DNSName: "name",
Target: "target",
RecordTTL: endpoint.TTL(10000),
RecordType: "A",
})
found := c.Get("link")
assert.NotNil(t, found)
}
func TestDyn_cachePutExpired(t *testing.T) {
c := cache{
contents: make(map[string]*entry),
}
c.Put("link", &endpoint.Endpoint{
DNSName: "name",
Target: "target",
RecordTTL: endpoint.TTL(0),
RecordType: "A",
})
time.Sleep(2 * time.Second)
found := c.Get("link")
assert.Nil(t, found)
assert.Nil(t, c.Get("no-such-records"))
}

102
vendor/github.com/nesv/go-dynect/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,102 @@
# Changelog
## Tue Jan 9 2018 - 0.6.0
- use VCR and fixtures for tests
- test ConvenientClient operations
- add support for zone create/delete operations
## Wed Aug 23 2017 - 0.5.3
- BUG-FIX: don't prepend dot for record with FQDN of Zone name
## Fri Aug 18 2017 - 0.5.2
- Handle errors reading response body in verbose mode (PR#20)
## Mon Jun 5 2017 - 0.5.1
- Update CHANGELOG
## Mon Jun 5 2017 - 0.5.0
- Add support for ALIAS, MX, NS, and SOA records, to the ConvenientClient
(PR#17)
## Mon Jun 5 2017 - 0.4.1
- Handle rate limit errors
## Mon Jun 5 2017 - 0.4.0
- Fix nil-transport issue with the ConvenientClinent (PR#16)
## Fri Apr 21 2017 - 0.3.1
- Proxy support configurable with HTTP(S)_PROXY env variables
- BACKPORT: Handle rate limit errors
## Thu Sep 22 2016 - 0.3.0
- Verbose mode prints full url
- Handle Job redirections
- Support for unknown Content-Length
- Addition of ConvenientClient
- Support for Traffic Director (DSF) service
- BUGFIX: Don't override global log prefix
## Fri Nov 15 2013 - 0.2.0
- Fixed some struct field types
- Modified some of the tests
- Felt like it deserved a minor version bump
## Thu Nov 14 2013 - 0.1.9
- If verbosity is enabled, any unmarshaling errors will print the complete
response body out, via logger
## Thu Nov 14 2013 - 0.1.8
## Wed Nov 13 2013 - 0.1.7
- Fixed a bug where empty request bodies would result in the API service
responding with a 400 Bad Request
- Added some proper tests
## Wed Nov 13 2013 - 0.1.6
- Added a "verbose" mode to the client
## Tue Nov 12 2013 - 0.1.5
- Bug fixes
- Logic bug in the *Client.Do() function, where it would not allow the
POST /Session call if the client was logged out (POST /Session is used for
logging in)
## Tue Nov 12 2013 - 0.1.4
- Includes 0.1.3
- Bug fixes
- Testing laid out, but there is not much there, as of right now
## Tue Nov 12 2013 - 0.1.2
- Bug fixes
## Tue Nov 12 2013 - 0.1.1
- Added structs for zone responses
## Tue Nov 12 2013 - 0.1.0
- Initial release
- The base client is complete; it will allow you to establish a session,
terminate a session, and issue requests to the DynECT REST API endpoints
- TODO
- Structs for marshaling and unmarshaling requests and responses still need
to be done, as the current set of provided struct is all that is needed
to be able to log in and create a session
- More structs will be added on an "as I need them" basis

21
vendor/github.com/nesv/go-dynect/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Nick Saika
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.

43
vendor/github.com/nesv/go-dynect/README.md generated vendored Normal file
View File

@ -0,0 +1,43 @@
# go-dynect
A DynECT REST client for the Go programming language.
## Installation
$ go get github.com/nesv/go-dynect/dynect
## Usage
package main
import (
"github.com/nesv/go-dynect/dynect"
"log"
)
func main() {
client := dynect.NewClient("my-dyn-customer-name")
err := client.Login("my-dyn-username", "my-dyn-password")
if err != nil {
log.Fatal(err)
}
defer func() {
err := client.Logout()
if err != nil {
log.Fatal(err)
}
}()
// Make a request to the API, to get a list of all, managed DNS zones
var response dynect.ZonesResponse
if err := client.Do("GET", "Zone", nil, &response); err != nil {
log.Println(err)
}
for _, zone := range response.Data {
log.Println("Zone", zone)
}
}
More to come!

281
vendor/github.com/nesv/go-dynect/dynect/client.go generated vendored Normal file
View File

@ -0,0 +1,281 @@
package dynect
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
)
const (
DynAPIPrefix = "https://api.dynect.net/REST"
)
var (
PollingInterval = 1 * time.Second
ErrPromotedToJob = errors.New("promoted to job")
ErrRateLimited = errors.New("too many requests")
)
// handleJobRedirect overrides the net/http.DefaultClient's redirection policy
// function.
//
// This function will set the Content-Type, and Auth-Token headers, so that we
// don't get an error back from the API.
func handleJobRedirect(req *http.Request, via []*http.Request) error {
// Set the Content-Type header.
req.Header.Set("Content-Type", "application/json")
// Now, try and divine the Auth-Token header's value from previous
// requests.
for _, r := range via {
if authHdr := r.Header.Get("Auth-Token"); authHdr != "" {
req.Header.Set("Auth-Token", authHdr)
return nil
}
}
return fmt.Errorf("failed to set Auth-Token header from previous requests")
}
// A client for use with DynECT's REST API.
type Client struct {
Token string
CustomerName string
Transport http.RoundTripper
verbose bool
}
// Creates a new Httpclient.
func NewClient(customerName string) *Client {
return &Client{
CustomerName: customerName,
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
}
}
// Sets the transport for the client.
func (c *Client) SetTransport(t http.RoundTripper) {
c.Transport = t
}
// Enable, or disable verbose output from the client.
//
// This will enable (or disable) logging messages that explain what the client
// is about to do, like the endpoint it is about to make a request to. If the
// request fails with an unexpected HTTP response code, then the response body
// will be logged out, as well.
func (c *Client) Verbose(p bool) {
c.verbose = p
}
// Establishes a new session with the DynECT API.
func (c *Client) Login(username, password string) error {
var req = LoginBlock{
Username: username,
Password: password,
CustomerName: c.CustomerName}
var resp LoginResponse
err := c.Do("POST", "Session", req, &resp)
if err != nil {
return err
}
c.Token = resp.Data.Token
return nil
}
func (c *Client) LoggedIn() bool {
return len(c.Token) > 0
}
func (c *Client) Logout() error {
return c.Do("DELETE", "Session", nil, nil)
}
// newRequest creates a new *http.Request, and sets the following headers:
// <ul>
// <li>Auth-Token</li>
// <li>Content-Type</li>
// </ul>
func (c *Client) newRequest(method, urlStr string, data []byte) (*http.Request, error) {
var r *http.Request
var err error
if data != nil {
r, err = http.NewRequest(method, urlStr, bytes.NewReader(data))
} else {
r, err = http.NewRequest(method, urlStr, nil)
}
r.Header.Set("Auth-Token", c.Token)
r.Header.Set("Content-Type", "application/json")
return r, err
}
func (c *Client) Do(method, endpoint string, requestData, responseData interface{}) error {
// Throw an error if the user tries to make a request if the client is
// logged out/unauthenticated, but make an exemption for when the
// caller is trying to log in.
if !c.LoggedIn() && method != "POST" && endpoint != "Session" {
return errors.New("Will not perform request; client is closed")
}
var err error
// Marshal the request data into a byte slice.
if c.verbose {
log.Println("dynect: marshaling request data")
}
var js []byte
if requestData != nil {
js, err = json.Marshal(requestData)
} else {
js = []byte("")
}
if err != nil {
return err
}
urlStr := fmt.Sprintf("%s/%s", DynAPIPrefix, endpoint)
// Create a new http.Request.
req, err := c.newRequest(method, urlStr, js)
if err != nil {
return err
}
if c.verbose {
log.Printf("Making %s request to %q", method, urlStr)
}
var resp *http.Response
resp, err = c.Transport.RoundTrip(req)
if err != nil {
if c.verbose {
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
log.Printf("%s", string(respBody))
}
return err
}
defer resp.Body.Close()
switch resp.StatusCode {
case 200:
if resp.ContentLength == 0 {
// Zero-length content body?
log.Println("dynect: warning: zero-length response body; skipping decoding of response")
return nil
}
//dec := json.NewDecoder(resp.Body)
text, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("Could not read response body")
}
if err := json.Unmarshal(text, &responseData); err != nil {
return fmt.Errorf("Error unmarshalling response:", err)
}
return nil
case 307:
// Handle the temporary redirect, which should point to a
// /REST/Jobs endpoint.
loc := resp.Header.Get("Location")
log.Println("dynect: request is taking too long to complete: redirecting to", loc)
// Going in to this blind, the documentation says that it will
// return a URI when promoting a long-running request to a
// job.
//
// Since a URL is technically a URI, we should do some checks
// on the returned URI to sanitize it, and make sure that it is
// in the format we would like it to be.
if strings.HasPrefix(loc, "/REST/") {
loc = strings.TrimLeft(loc, "/REST/")
}
if !strings.HasPrefix(loc, DynAPIPrefix) {
loc = fmt.Sprintf("%s/%s", DynAPIPrefix, loc)
}
log.Println("Fetching location:", loc)
// Generate a new request.
req, err := c.newRequest("GET", loc, nil)
if err != nil {
return err
}
var jobData JobData
// Poll the API endpoint, until we get a response back.
for {
select {
case <-time.After(PollingInterval):
resp, err := c.Transport.RoundTrip(req)
if err != nil {
return err
}
defer resp.Body.Close()
text, err := ioutil.ReadAll(resp.Body)
//log.Println(string(text))
if err != nil {
return fmt.Errorf("Could not read response body:", err)
}
if err := json.Unmarshal(text, &jobData); err != nil {
return fmt.Errorf("failed to decode job response body:", err)
}
// Check to see the status of the job.
//
// If it is "incomplete", loop around again.
//
// Should the job's status be "success", then
// return the data, business-as-usual.
//
// TODO(nesv): Figure out what to do in the
// event of a "failure" job status.
switch jobData.Status {
case "incomplete":
continue
case "success":
if err := json.Unmarshal(text, &responseData); err != nil {
return fmt.Errorf("failed to decode response body:", err)
}
return nil
case "failure":
return fmt.Errorf("request failed: %v", jobData.Messages)
}
}
}
return nil
case 429:
return ErrRateLimited
}
// If we got here, this means that the client does not know how to
// interpret the response, and it should just error out.
reason, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read in response body")
}
return fmt.Errorf("server responded with %v: %v",
resp.Status,
string(reason))
}

123
vendor/github.com/nesv/go-dynect/dynect/client_test.go generated vendored Normal file
View File

@ -0,0 +1,123 @@
package dynect
import (
"log"
"os"
"strings"
"testing"
"github.com/dnaeon/go-vcr/recorder"
)
var (
DynCustomerName string
DynUsername string
DynPassword string
testZone string
)
func getenv(key, defaultValue string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return defaultValue
}
func init() {
DynCustomerName = getenv("DYNECT_CUSTOMER_NAME", "go-dynect")
DynUsername = getenv("DYNECT_USER_NAME", "dynect-user")
DynPassword = getenv("DYNECT_PASSWORD", "p@55w0rd")
testZone = getenv("DYNECT_TEST_ZONE", "go-dynect.test")
}
// test helper to begin recording or playback of vcr cassette
func withCassette(cassetteName string, f func(*recorder.Recorder)) {
r, err := recorder.New(cassetteName)
if err != nil {
log.Fatal(err)
}
defer r.Stop()
f(r)
}
// test helper to setup client with vcr cassette
func withClient(cassetteName string, f func(*Client)) {
withCassette(cassetteName, func(r *recorder.Recorder) {
c := NewClient(DynCustomerName)
c.SetTransport(r)
c.Verbose(true)
f(c)
})
}
// test helper to setup authenticated client with vcr cassette
func testWithClientSession(cassetteName string, t *testing.T, f func(*Client)) {
withClient(cassetteName, func(c *Client) {
if err := c.Login(DynUsername, DynPassword); err != nil {
t.Fatal(err)
}
defer func() {
if err := c.Logout(); err != nil {
t.Error(err)
}
}()
f(c)
})
}
func TestLoginLogout(t *testing.T) {
withClient("fixtures/login_logout", func(c *Client) {
if err := c.Login(DynUsername, DynPassword); err != nil {
t.Error(err)
}
if err := c.Logout(); err != nil {
t.Error(err)
}
})
}
func TestZonesRequest(t *testing.T) {
testWithClientSession("fixtures/zones_request", t, func(c *Client) {
var resp ZonesResponse
if err := c.Do("GET", "Zone", nil, &resp); err != nil {
t.Fatal(err)
}
nresults := len(resp.Data)
for i, zone := range resp.Data {
parts := strings.Split(zone, "/")
t.Logf("(%d/%d) %q", i+1, nresults, parts[len(parts)-2])
}
})
}
func TestFetchingAllZoneRecords(t *testing.T) {
testWithClientSession("fixtures/fetching_all_zone_records", t, func(c *Client) {
var resp AllRecordsResponse
if err := c.Do("GET", "AllRecord/"+testZone, nil, &resp); err != nil {
t.Error(err)
}
for _, zr := range resp.Data {
parts := strings.Split(zr, "/")
uri := strings.Join(parts[2:], "/")
t.Log(uri)
var record RecordResponse
if err := c.Do("GET", uri, nil, &record); err != nil {
t.Fatal(err)
}
t.Log("OK")
}
})
}

View File

@ -0,0 +1,242 @@
package dynect
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
)
// ConvenientClient A client with extra helper methods for common actions
type ConvenientClient struct {
Client
}
// NewConvenientClient Creates a new ConvenientClient
func NewConvenientClient(customerName string) *ConvenientClient {
return &ConvenientClient{
Client{
CustomerName: customerName,
Transport: &http.Transport{Proxy: http.ProxyFromEnvironment},
}}
}
// CreateZone method to create a zone
func (c *ConvenientClient) CreateZone(zone, rname, serialStyle, ttl string) error {
url := fmt.Sprintf("Zone/%s/", zone)
data := &CreateZoneBlock{
RName: rname,
SerialStyle: serialStyle,
TTL: ttl,
}
if err := c.Do("POST", url, data, nil); err != nil {
return fmt.Errorf("Failed to create zone: %s", err)
}
return nil
}
// GetZone method to read a zone
func (c *ConvenientClient) GetZone(z *Zone) error {
url := fmt.Sprintf("Zone/%s", z.Zone)
data := &ZoneResponse{}
if err := c.Do("GET", url, nil, data); err != nil {
return fmt.Errorf("Failed to get zone: %s", err)
}
z.Serial = strconv.Itoa(data.Data.Serial)
z.SerialStyle = data.Data.SerialStyle
z.Zone = data.Data.Zone
z.Type = data.Data.ZoneType
return nil
}
// PublishZone Publish a specific zone and the changes for the current session
func (c *ConvenientClient) PublishZone(zone string) error {
url := fmt.Sprintf("Zone/%s", zone)
data := &PublishZoneBlock{
Publish: true,
}
if err := c.Do("PUT", url, data, nil); err != nil {
return fmt.Errorf("Failed to publish zone: %s", err)
}
return nil
}
// DeleteZoneNode method to delete everything in a zone
func (c *ConvenientClient) DeleteZoneNode(zone string) error {
parentZone := strings.Join(strings.Split(zone, ".")[1:], ".")
url := fmt.Sprintf("Node/%s/%s", parentZone, zone)
if err := c.Do("DELETE", url, nil, nil); err != nil {
return fmt.Errorf("Failed to delete zone node: %s", err)
}
return nil
}
// DeleteZone method to delete a zone
func (c *ConvenientClient) DeleteZone(zone string) error {
url := fmt.Sprintf("Zone/%s/", zone)
if err := c.Do("DELETE", url, nil, nil); err != nil {
return fmt.Errorf("Failed to delete zone: %s", err)
}
return nil
}
// GetRecordID finds the dns record ID by fetching all records for a FQDN
func (c *ConvenientClient) GetRecordID(record *Record) error {
finalID := ""
url := fmt.Sprintf("AllRecord/%s/%s", record.Zone, record.FQDN)
var records AllRecordsResponse
err := c.Do("GET", url, nil, &records)
if err != nil {
return fmt.Errorf("Failed to find Dyn record id: %s", err)
}
for _, recordURL := range records.Data {
id := strings.TrimPrefix(recordURL, fmt.Sprintf("/REST/%sRecord/%s/%s/", record.Type, record.Zone, record.FQDN))
if !strings.Contains(id, "/") && id != "" {
finalID = id
log.Printf("[INFO] Found Dyn record ID: %s", id)
}
}
if finalID == "" {
return fmt.Errorf("Failed to find Dyn record id!")
}
record.ID = finalID
return nil
}
// CreateRecord Method to create a DNS record
func (c *ConvenientClient) CreateRecord(record *Record) error {
if record.FQDN == "" && record.Name == "" {
record.FQDN = record.Zone
} else if record.FQDN == "" {
record.FQDN = fmt.Sprintf("%s.%s", record.Name, record.Zone)
}
rdata, err := buildRData(record)
if err != nil {
return fmt.Errorf("Failed to create Dyn RData: %s", err)
}
url := fmt.Sprintf("%sRecord/%s/%s", record.Type, record.Zone, record.FQDN)
data := &RecordRequest{
RData: rdata,
TTL: record.TTL,
}
return c.Do("POST", url, data, nil)
}
// UpdateRecord Method to update a DNS record
func (c *ConvenientClient) UpdateRecord(record *Record) error {
if record.FQDN == "" {
record.FQDN = fmt.Sprintf("%s.%s", record.Name, record.Zone)
}
rdata, err := buildRData(record)
if err != nil {
return fmt.Errorf("Failed to create Dyn RData: %s", err)
}
url := fmt.Sprintf("%sRecord/%s/%s/%s", record.Type, record.Zone, record.FQDN, record.ID)
data := &RecordRequest{
RData: rdata,
TTL: record.TTL,
}
return c.Do("PUT", url, data, nil)
}
// DeleteRecord Method to delete a DNS record
func (c *ConvenientClient) DeleteRecord(record *Record) error {
if record.FQDN == "" {
record.FQDN = fmt.Sprintf("%s.%s", record.Name, record.Zone)
}
// safety check that we have an ID, otherwise we could accidentally delete everything
if record.ID == "" {
return fmt.Errorf("No ID found! We can't continue!")
}
url := fmt.Sprintf("%sRecord/%s/%s/%s", record.Type, record.Zone, record.FQDN, record.ID)
return c.Do("DELETE", url, nil, nil)
}
// GetRecord Method to get record details
func (c *ConvenientClient) GetRecord(record *Record) error {
url := fmt.Sprintf("%sRecord/%s/%s/%s", record.Type, record.Zone, record.FQDN, record.ID)
var rec RecordResponse
err := c.Do("GET", url, nil, &rec)
if err != nil {
return err
}
record.Zone = rec.Data.Zone
record.FQDN = rec.Data.FQDN
record.Name = strings.TrimSuffix(rec.Data.FQDN, "."+rec.Data.Zone)
record.Type = rec.Data.RecordType
record.TTL = strconv.Itoa(rec.Data.TTL)
switch rec.Data.RecordType {
case "A", "AAAA":
record.Value = rec.Data.RData.Address
case "ALIAS":
record.Value = rec.Data.RData.Alias
case "CNAME":
record.Value = rec.Data.RData.CName
case "MX":
record.Value = fmt.Sprintf("%d %s", rec.Data.RData.Preference, rec.Data.RData.Exchange)
case "NS":
record.Value = rec.Data.RData.NSDName
case "SOA":
record.Value = rec.Data.RData.RName
case "TXT", "SPF":
record.Value = rec.Data.RData.TxtData
default:
fmt.Println("unknown response", rec)
return fmt.Errorf("Invalid Dyn record type: %s", rec.Data.RecordType)
}
return nil
}
func buildRData(r *Record) (DataBlock, error) {
var rdata DataBlock
switch r.Type {
case "A", "AAAA":
rdata = DataBlock{
Address: r.Value,
}
case "ALIAS":
rdata = DataBlock{
Alias: r.Value,
}
case "CNAME":
rdata = DataBlock{
CName: r.Value,
}
case "MX":
rdata = DataBlock{}
fmt.Sscanf(r.Value, "%d %s", &rdata.Preference, &rdata.Exchange)
case "NS":
rdata = DataBlock{
NSDName: r.Value,
}
case "SOA":
rdata = DataBlock{
RName: r.Value,
}
case "TXT", "SPF":
rdata = DataBlock{
TxtData: r.Value,
}
default:
return rdata, fmt.Errorf("Invalid Dyn record type: %s", r.Type)
}
return rdata, nil
}

View File

@ -0,0 +1,241 @@
package dynect
import (
"fmt"
"strconv"
"strings"
"testing"
"github.com/dnaeon/go-vcr/recorder"
)
// test helper to setup convenient client with vcr cassette
func withConvenientClient(cassetteName string, f func(*ConvenientClient)) {
withCassette(cassetteName, func(r *recorder.Recorder) {
c := NewConvenientClient(DynCustomerName)
c.SetTransport(r)
c.Verbose(true)
f(c)
})
}
// test helper to setup authenticated convenient client with vcr cassette
func testWithConvenientClientSession(cassetteName string, t *testing.T, f func(*ConvenientClient)) {
withConvenientClient(cassetteName, func(c *ConvenientClient) {
if err := c.Login(DynUsername, DynPassword); err != nil {
t.Error(err)
}
defer func() {
if err := c.Logout(); err != nil {
t.Error(err)
}
}()
f(c)
})
}
func TestConvenientLoginLogout(t *testing.T) {
withConvenientClient("fixtures/login_logout", func(c *ConvenientClient) {
if err := c.Login(DynUsername, DynPassword); err != nil {
t.Error(err)
}
if err := c.Logout(); err != nil {
t.Error(err)
}
})
}
func TestConvenientGetA(t *testing.T) {
testWithConvenientClientSession("fixtures/convenient_get_a", t, func(c *ConvenientClient) {
actual := Record{
Zone: testZone,
Type: "A",
FQDN: "foobar." + testZone,
}
if err := c.GetRecordID(&actual); err != nil {
t.Fatal(err)
}
if err := c.GetRecord(&actual); err != nil {
t.Fatal(err)
}
if actual.Value != "10.9.8.7" {
t.Fatalf("Incorrect value %q for %q (expected %q)", actual.Value, actual.FQDN, "foobar.go-dynect.test.")
}
t.Log("OK")
})
}
func TestConvenientGetANotFound(t *testing.T) {
testWithConvenientClientSession("fixtures/convenient_get_a_not_found", t, func(c *ConvenientClient) {
actual := Record{
Zone: testZone,
Type: "A",
FQDN: "unknown." + testZone,
}
if err := c.GetRecordID(&actual); err == nil {
t.Fatalf("Expected error getting %q", actual.FQDN)
} else if !strings.HasPrefix(err.Error(), "Failed to find Dyn record id:") {
t.Fatalf("Expected error %q for %q (actual error %q)", "Failed to find Dyn record id:", actual.FQDN, err.Error())
}
t.Log("OK")
})
}
func TestConvenientGetCNAME(t *testing.T) {
testWithConvenientClientSession("fixtures/convenient_get_cname", t, func(c *ConvenientClient) {
actual := Record{
Zone: testZone,
Type: "CNAME",
FQDN: "foo." + testZone,
}
if err := c.GetRecordID(&actual); err != nil {
t.Fatal(err)
}
if err := c.GetRecord(&actual); err != nil {
t.Fatal(err)
}
if actual.Value != "foobar.go-dynect.test." {
t.Fatalf("Incorrect value %q (expected %q)", actual.Value, "foobar.go-dynect.test.")
}
t.Log("OK")
})
}
func TestConvenientCreateMX(t *testing.T) {
testWithConvenientClientSession("fixtures/convenient_create_mx", t, func(c *ConvenientClient) {
record := Record{
Zone: testZone,
Type: "MX",
Value: "123 mx.example.com.",
TTL: "12345",
}
if err := c.CreateRecord(&record); err != nil {
t.Fatal(err)
}
if err := c.PublishZone(testZone); err != nil {
t.Fatal(err)
}
if err := c.GetRecordID(&record); err != nil {
t.Fatal(err)
}
if err := c.GetRecord(&record); err != nil {
t.Fatal(err)
}
if record.FQDN != testZone {
t.Fatalf("Expected FQDN %q (actual %q)", testZone, record.FQDN)
}
id, err := strconv.Atoi(record.ID)
if err != nil || id <= 0 {
t.Fatalf("Expected ID to be positive integer (actual %q)", record.ID)
}
ttl, err := strconv.Atoi(record.TTL)
if err != nil || ttl != 12345 {
t.Fatalf("Expected ID to be 12345 (actual %q)", record.TTL)
}
t.Log("OK")
})
}
func TestConvenientCreateZone(t *testing.T) {
testWithConvenientClientSession("fixtures/convenient_create_zone", t, func(c *ConvenientClient) {
subZone := fmt.Sprintf("subzone.%s", testZone)
if err := c.CreateZone(subZone, "admin@example.com", "day", "1800"); err != nil {
t.Fatal(err)
}
if err := c.PublishZone(subZone); err != nil {
t.Fatal(err)
}
z := &Zone{Zone: subZone}
if err := c.GetZone(z); err != nil {
t.Fatal(err)
}
if z.Zone != subZone {
t.Fatalf("Expected Zone of %q (actual %q)", subZone, z.Zone)
}
if z.Type != "Primary" {
t.Fatalf("Expected Zone Type of %q (actual %q)", "Primary", z.Type)
}
if z.SerialStyle != "day" {
t.Fatalf("Expected SerialStyle of %q (actual %q)", "day", z.SerialStyle)
}
if z.Serial == "" {
t.Fatalf("Expected non-empty Serial (actual %q)", z.Serial)
}
t.Log("OK")
})
}
func TestConvenientDeleteZone(t *testing.T) {
testWithConvenientClientSession("fixtures/convenient_delete_zone", t, func(c *ConvenientClient) {
subZone := fmt.Sprintf("zone-%s", testZone)
if err := c.DeleteZone(subZone); err != nil {
t.Fatal(err)
}
z := &Zone{Zone: subZone}
if err := c.GetZone(z); err == nil {
t.Fatalf("Zone %q not deleted", subZone)
}
t.Log("OK")
})
}
func TestConvenientDeleteSubZone(t *testing.T) {
testWithConvenientClientSession("fixtures/convenient_delete_sub_zone", t, func(c *ConvenientClient) {
subZone := fmt.Sprintf("subzone.%s", testZone)
if err := c.DeleteZone(subZone); err != nil {
t.Fatal(err)
}
if err := c.DeleteZoneNode(subZone); err != nil {
t.Fatal(err)
}
if err := c.PublishZone(testZone); err != nil {
t.Fatal(err)
}
z := &Zone{Zone: subZone}
if err := c.GetZone(z); err == nil {
t.Fatalf("Zone %q not deleted", subZone)
}
t.Log("OK")
})
}

117
vendor/github.com/nesv/go-dynect/dynect/dsfs.go generated vendored Normal file
View File

@ -0,0 +1,117 @@
package dynect
// DSFSResponse is used for holding the data returned by a call to
// "https://api.dynect.net/REST/DSF/" with 'detail: Y'.
type AllDSFDetailedResponse struct {
ResponseBlock
Data []DSFService `json:"data"`
}
// DSFResponse is used for holding the data returned by a call to
// "https://api.dynect.net/REST/DSF/SERVICE_ID".
type DSFResponse struct {
ResponseBlock
Data DSFService `json:"data"`
}
// Type DSFService is used as a nested struct, which holds the data for a
// DSF Service returned by a call to "https://api.dynect.net/REST/DSF/SERVICE_ID".
type DSFService struct {
ID string `json:"service_id"`
Label string `json:"label"`
Active string `json:"active"`
TTL string `json:"ttl"`
PendingChange string `json:"pending_change"`
Notifiers []Notifier `json:"notifiers"`
Nodes []DSFNode `json:"nodes"`
Rulesets []DSFRuleset `json:"rulesets"`
}
type DSFRuleset struct {
ID string `json:"dsf_ruleset_id`
Label string `json:"label"`
CriteriaType string `json:"criteria_type"`
Criteria interface{} `json:"criteria"`
Ordering string `json:"ordering"`
Eligible string `json:"eligible"`
PendingChange string `json:"pending_change"`
ResponsePools []DSFResponsePool `json:"response_pools"`
}
type DSFResponsePool struct {
ID string `json:"dsf_response_pool_id"`
Label string `json:"label"`
Automation string `json:"automation"`
CoreSetCount string `json:"core_set_count"`
Eligible string `json:"eligible"`
PendingChange string `json:"pending_change"`
RsChains []DSFRecordSetChain `json:"rs_chains"`
Rulesets []DSFRuleset `json:"rulesets"`
Status string `json:"status"`
LastMonitored string `json:"last_monitored"`
Notifier string `json:"notifier"`
}
type DSFRecordSetChain struct {
ID string `json:"dsf_record_set_failover_chain_id"`
Status string `json:"status"`
Core string `json:"core"`
Label string `json:"label"`
DSFResponsePoolID string `json:"dsf_response_pool_id"`
DSFServiceID string `json:"service_id"`
PendingChange string `json:"pending_change"`
DSFRecordSets []DSFRecordSet `json:"record_sets"`
}
type DSFRecordSet struct {
Status string `json:"status"`
Eligible string `json:"eligible"`
ID string `json:"dsf_record_set_id"`
MonitorID string `json:"dsf_monitor_id"`
Label string `json:"label"`
TroubleCount string `json:"trouble_count"`
Records []DSFRecord `json:"records"`
FailCount string `json:"fail_count"`
TorpidityMax string `json:"torpidity_max"`
TTLDerived string `json:"ttl_derived"`
LastMonitored string `json:"last_monitored"`
TTL string `json:"ttl"`
ServiceID string `json:"service_id"`
ServeCount string `json:"serve_count"`
Automation string `json:"automation"`
PendingChange string `json:"pending_change"`
}
type DSFRecord struct {
Status string `json:"status"`
Endpoints []string `json:"endpoints"`
RDataClass string `json:"rdata_class"`
Weight int `json:"weight"`
Eligible string `json:"eligible"`
ID string `json:"dsf_record_id"`
DSFRecordSetID string `json:"dsf_record_set_id"`
//RData interface{} `json:"rdata"`
EndpointUpCount int `json:"endpoint_up_count"`
Label string `json:"label"`
MasterLine string `json:"master_line"`
Torpidity int `json:"torpidity"`
LastMonitored int `json:"last_monitored"`
TTL string `json:"ttl"`
DSFServiceID string `json:"service_id"`
PendingChange string `json:"pending_change"`
Automation string `json:"automation"`
ReponseTime int `json:"response_time"`
Publish string `json:"publish",omit_empty`
}
type DSFNode struct {
Zone string `json:"zone"`
FQDN string `json:"fqdn"`
}
type Notifier struct {
ID int `json:"notifier_id"`
Label string `json:"label"`
Recipients string `json:"recipients"`
Active string `json:"active"`
}

View File

@ -0,0 +1,161 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "5Trj0G1M2B0g1t1IY09yFwGpn31tjWNRNU81RhYHaUp6kxGa3UVK5F9hQqlZBu9SNLxkj6cAk6q93ndW246hIesr496yLD+eOHeJSdBtxxKgB+Gmk4ydsrR1trDIlK0Yq3l9J2XVPTT+/pKtyKmRxLWwNGHvhdJDfs92MiS3+7M=",
"version": "3.7.9"}, "job_id": 4342593698, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 14:45:37 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: '{"rdata":{"exchange":"mx.example.com.","preference":123},"ttl":"12345"}'
form: {}
headers:
Auth-Token:
- 5Trj0G1M2B0g1t1IY09yFwGpn31tjWNRNU81RhYHaUp6kxGa3UVK5F9hQqlZBu9SNLxkj6cAk6q93ndW246hIesr496yLD+eOHeJSdBtxxKgB+Gmk4ydsrR1trDIlK0Yq3l9J2XVPTT+/pKtyKmRxLWwNGHvhdJDfs92MiS3+7M=
Content-Type:
- application/json
url: https://api.dynect.net/REST/MXRecord/go-dynect.test/go-dynect.test
method: POST
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "ttl": 12345,
"fqdn": "go-dynect.test", "record_type": "MX", "rdata": {"preference": 123,
"exchange": "mx.example.com."}, "record_id": 0}, "job_id": 4342593703, "msgs":
[{"INFO": "add: Record added", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 14:45:37 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: '{"publish":true}'
form: {}
headers:
Auth-Token:
- 5Trj0G1M2B0g1t1IY09yFwGpn31tjWNRNU81RhYHaUp6kxGa3UVK5F9hQqlZBu9SNLxkj6cAk6q93ndW246hIesr496yLD+eOHeJSdBtxxKgB+Gmk4ydsrR1trDIlK0Yq3l9J2XVPTT+/pKtyKmRxLWwNGHvhdJDfs92MiS3+7M=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/go-dynect.test
method: PUT
response:
body: '{"status": "success", "data": {"zone_type": "Primary", "task_id": "230305365",
"serial": 2017122005, "serial_style": "day", "zone": "go-dynect.test"}, "job_id":
4342593710, "msgs": [{"INFO": "publish: go-dynect.test published", "SOURCE":
"BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 14:45:37 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- 5Trj0G1M2B0g1t1IY09yFwGpn31tjWNRNU81RhYHaUp6kxGa3UVK5F9hQqlZBu9SNLxkj6cAk6q93ndW246hIesr496yLD+eOHeJSdBtxxKgB+Gmk4ydsrR1trDIlK0Yq3l9J2XVPTT+/pKtyKmRxLWwNGHvhdJDfs92MiS3+7M=
Content-Type:
- application/json
url: https://api.dynect.net/REST/AllRecord/go-dynect.test/go-dynect.test
method: GET
response:
body: '{"status": "success", "data": ["/REST/CNAMERecord/go-dynect.test/foo.go-dynect.test/318905322",
"/REST/SOARecord/go-dynect.test/go-dynect.test/318812133", "/REST/MXRecord/go-dynect.test/go-dynect.test/319018246",
"/REST/NSRecord/go-dynect.test/go-dynect.test/318812135", "/REST/NSRecord/go-dynect.test/go-dynect.test/318812136",
"/REST/NSRecord/go-dynect.test/go-dynect.test/318812137", "/REST/NSRecord/go-dynect.test/go-dynect.test/318812138",
"/REST/ARecord/go-dynect.test/foobar.go-dynect.test/319014258"], "job_id": 4342593722,
"msgs": [{"INFO": "get_tree: Here is your zone tree", "SOURCE": "BLL", "ERR_CD":
null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 14:45:37 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- 5Trj0G1M2B0g1t1IY09yFwGpn31tjWNRNU81RhYHaUp6kxGa3UVK5F9hQqlZBu9SNLxkj6cAk6q93ndW246hIesr496yLD+eOHeJSdBtxxKgB+Gmk4ydsrR1trDIlK0Yq3l9J2XVPTT+/pKtyKmRxLWwNGHvhdJDfs92MiS3+7M=
Content-Type:
- application/json
url: https://api.dynect.net/REST/MXRecord/go-dynect.test/go-dynect.test/319018246
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "ttl": 12345,
"fqdn": "go-dynect.test", "record_type": "MX", "rdata": {"preference": 123,
"exchange": "mx.example.com."}, "record_id": 319018246}, "job_id": 4342593727,
"msgs": [{"INFO": "get: Found the record", "SOURCE": "API-B", "ERR_CD": null,
"LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 14:45:37 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- 5Trj0G1M2B0g1t1IY09yFwGpn31tjWNRNU81RhYHaUp6kxGa3UVK5F9hQqlZBu9SNLxkj6cAk6q93ndW246hIesr496yLD+eOHeJSdBtxxKgB+Gmk4ydsrR1trDIlK0Yq3l9J2XVPTT+/pKtyKmRxLWwNGHvhdJDfs92MiS3+7M=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4342593732, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 14:45:37 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,134 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "vTjIj83plGwG5lf+2gzYpI/JlEh8JQSM88ang3OVjUQfe8JfirVWlvl1786+nJbsg987HR6aiIcx6MuseIOvvNaeqxFwwR9xTJvP5EikWS2Cn/Xg/WVDulJ66xl1vjxusJVVlhgLY0MF0nGlBbVMjIYILxbg9pbkB7N4WL+dgu4=",
"version": "3.7.9"}, "job_id": 4344013458, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Thu, 21 Dec 2017 00:57:07 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: '{"rname":"admin@example.com","serial_style":"day","ttl":"1800"}'
form: {}
headers:
Auth-Token:
- vTjIj83plGwG5lf+2gzYpI/JlEh8JQSM88ang3OVjUQfe8JfirVWlvl1786+nJbsg987HR6aiIcx6MuseIOvvNaeqxFwwR9xTJvP5EikWS2Cn/Xg/WVDulJ66xl1vjxusJVVlhgLY0MF0nGlBbVMjIYILxbg9pbkB7N4WL+dgu4=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/subzone.go-dynect.test/
method: POST
response:
body: '{"status": "success", "data": {"zone_type": "Primary", "serial_style":
"day", "serial": 0, "zone": "subzone.go-dynect.test"}, "job_id": 4344013466,
"msgs": [{"INFO": "setup: If you plan to provide your own secondary DNS for
the zone, allow notify requests from these IP addresses on your nameserver:
208.78.68.66, 2600:2003:0:1::66", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"},
{"INFO": "create: New zone subzone.go-dynect.test created. Publish it to put
it on our server.", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Thu, 21 Dec 2017 00:57:09 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: '{"publish":true}'
form: {}
headers:
Auth-Token:
- vTjIj83plGwG5lf+2gzYpI/JlEh8JQSM88ang3OVjUQfe8JfirVWlvl1786+nJbsg987HR6aiIcx6MuseIOvvNaeqxFwwR9xTJvP5EikWS2Cn/Xg/WVDulJ66xl1vjxusJVVlhgLY0MF0nGlBbVMjIYILxbg9pbkB7N4WL+dgu4=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/subzone.go-dynect.test
method: PUT
response:
body: '{"status": "success", "data": {"zone_type": "Primary", "task_id": "230378639",
"serial": 2017122100, "serial_style": "day", "zone": "subzone.go-dynect.test"},
"job_id": 4344013515, "msgs": [{"INFO": "publish: subzone.go-dynect.test published",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Thu, 21 Dec 2017 00:57:09 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- vTjIj83plGwG5lf+2gzYpI/JlEh8JQSM88ang3OVjUQfe8JfirVWlvl1786+nJbsg987HR6aiIcx6MuseIOvvNaeqxFwwR9xTJvP5EikWS2Cn/Xg/WVDulJ66xl1vjxusJVVlhgLY0MF0nGlBbVMjIYILxbg9pbkB7N4WL+dgu4=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/subzone.go-dynect.test
method: GET
response:
body: '{"status": "success", "data": {"zone_type": "Primary", "serial_style":
"day", "serial": 2017122100, "zone": "subzone.go-dynect.test"}, "job_id": 4344013521,
"msgs": [{"INFO": "get: Your zone, subzone.go-dynect.test", "SOURCE": "BLL",
"ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Thu, 21 Dec 2017 00:57:09 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- vTjIj83plGwG5lf+2gzYpI/JlEh8JQSM88ang3OVjUQfe8JfirVWlvl1786+nJbsg987HR6aiIcx6MuseIOvvNaeqxFwwR9xTJvP5EikWS2Cn/Xg/WVDulJ66xl1vjxusJVVlhgLY0MF0nGlBbVMjIYILxbg9pbkB7N4WL+dgu4=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4344013527, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Thu, 21 Dec 2017 00:57:09 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,153 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "THqiI1SkDZateC3XF1y3bF6ZlPlYlIP+uEAr7mE9G3XT84cwTLNkbmArR4xGPnfJmsKYOT+mN3PO0z1G8wu4R7W4DufXuywZoNQWYxv51+X3ZQd0MkFA7OMvtPTRxts+E0Kc5HGLjqmZLD/AEpfHu/5XemknyRMD",
"version": "3.7.9"}, "job_id": 4349697720, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:43:51 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- THqiI1SkDZateC3XF1y3bF6ZlPlYlIP+uEAr7mE9G3XT84cwTLNkbmArR4xGPnfJmsKYOT+mN3PO0z1G8wu4R7W4DufXuywZoNQWYxv51+X3ZQd0MkFA7OMvtPTRxts+E0Kc5HGLjqmZLD/AEpfHu/5XemknyRMD
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/subzone.go-dynect.test/
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4349697722, "msgs": [{"INFO":
"remove: Zone removed", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:43:51 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- THqiI1SkDZateC3XF1y3bF6ZlPlYlIP+uEAr7mE9G3XT84cwTLNkbmArR4xGPnfJmsKYOT+mN3PO0z1G8wu4R7W4DufXuywZoNQWYxv51+X3ZQd0MkFA7OMvtPTRxts+E0Kc5HGLjqmZLD/AEpfHu/5XemknyRMD
Content-Type:
- application/json
url: https://api.dynect.net/REST/Node/go-dynect.test/subzone.go-dynect.test
method: DELETE
response:
body: '{"status": "success", "data": {"zone_type": "Primary", "serial_style":
"day", "serial": 2017122201, "zone": "go-dynect.test"}, "job_id": 4349697733,
"msgs": [{"INFO": "remove_node: subzone.go-dynect.test removed from tree. All
records also removed.", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:43:51 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: '{"publish":true}'
form: {}
headers:
Auth-Token:
- THqiI1SkDZateC3XF1y3bF6ZlPlYlIP+uEAr7mE9G3XT84cwTLNkbmArR4xGPnfJmsKYOT+mN3PO0z1G8wu4R7W4DufXuywZoNQWYxv51+X3ZQd0MkFA7OMvtPTRxts+E0Kc5HGLjqmZLD/AEpfHu/5XemknyRMD
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/go-dynect.test
method: PUT
response:
body: '{"status": "success", "data": {"zone_type": "Primary", "task_id": "230665571",
"serial": 2017122202, "serial_style": "day", "zone": "go-dynect.test"}, "job_id":
4349697739, "msgs": [{"INFO": "publish: go-dynect.test published", "SOURCE":
"BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:43:52 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- THqiI1SkDZateC3XF1y3bF6ZlPlYlIP+uEAr7mE9G3XT84cwTLNkbmArR4xGPnfJmsKYOT+mN3PO0z1G8wu4R7W4DufXuywZoNQWYxv51+X3ZQd0MkFA7OMvtPTRxts+E0Kc5HGLjqmZLD/AEpfHu/5XemknyRMD
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/subzone.go-dynect.test
method: GET
response:
body: '{"status": "failure", "data": {}, "job_id": 4349697747, "msgs": [{"INFO":
"zone: No such zone", "SOURCE": "API-B", "ERR_CD": "NOT_FOUND", "LVL": "ERROR"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:43:52 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 404 Not Found
code: 404
- request:
body: ""
form: {}
headers:
Auth-Token:
- THqiI1SkDZateC3XF1y3bF6ZlPlYlIP+uEAr7mE9G3XT84cwTLNkbmArR4xGPnfJmsKYOT+mN3PO0z1G8wu4R7W4DufXuywZoNQWYxv51+X3ZQd0MkFA7OMvtPTRxts+E0Kc5HGLjqmZLD/AEpfHu/5XemknyRMD
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4349697755, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:43:52 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,101 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "ehqnDu44eQcNVPhHf+iyRd8/Ilgx5SpF0uR7OCPbjNGMA131GlouJtqLN5VS8flT+cChPirW9NEbjo3PfJOJmCasbumDfEg7PdAyd2rOvhKG4/XHze/FRv7bAnsFFafZHL5wfSoGgqdZlv+vZRJctWpDkXLHt9RHM8UejSS0/Qo=",
"version": "3.7.9"}, "job_id": 4349699356, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:44:40 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- ehqnDu44eQcNVPhHf+iyRd8/Ilgx5SpF0uR7OCPbjNGMA131GlouJtqLN5VS8flT+cChPirW9NEbjo3PfJOJmCasbumDfEg7PdAyd2rOvhKG4/XHze/FRv7bAnsFFafZHL5wfSoGgqdZlv+vZRJctWpDkXLHt9RHM8UejSS0/Qo=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/zone-go-dynect.test/
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4349699362, "msgs": [{"INFO":
"remove: Zone removed", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:44:41 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- ehqnDu44eQcNVPhHf+iyRd8/Ilgx5SpF0uR7OCPbjNGMA131GlouJtqLN5VS8flT+cChPirW9NEbjo3PfJOJmCasbumDfEg7PdAyd2rOvhKG4/XHze/FRv7bAnsFFafZHL5wfSoGgqdZlv+vZRJctWpDkXLHt9RHM8UejSS0/Qo=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone/zone-go-dynect.test
method: GET
response:
body: '{"status": "failure", "data": {}, "job_id": 4349699368, "msgs": [{"INFO":
"zone: No such zone", "SOURCE": "API-B", "ERR_CD": "NOT_FOUND", "LVL": "ERROR"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:44:41 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 404 Not Found
code: 404
- request:
body: ""
form: {}
headers:
Auth-Token:
- ehqnDu44eQcNVPhHf+iyRd8/Ilgx5SpF0uR7OCPbjNGMA131GlouJtqLN5VS8flT+cChPirW9NEbjo3PfJOJmCasbumDfEg7PdAyd2rOvhKG4/XHze/FRv7bAnsFFafZHL5wfSoGgqdZlv+vZRJctWpDkXLHt9RHM8UejSS0/Qo=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4349699370, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Fri, 22 Dec 2017 17:44:41 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,104 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "S7uWFq5OnRrL0divNfQgijM1gPTh8aIa3qHvoH+t1GVF84hwfMF8e9BD5ty09DuMprfDW1pDMgG45mEYNJE+KT2Xow8s5tfcA9mijaNemE+7gQ4DlkVd7PHggpUckUFN+faA5vPOTfSEn6T+MEux5ZoTnncnawkgqtu40DhzVV8=",
"version": "3.7.9"}, "job_id": 4342164998, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:22:46 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- S7uWFq5OnRrL0divNfQgijM1gPTh8aIa3qHvoH+t1GVF84hwfMF8e9BD5ty09DuMprfDW1pDMgG45mEYNJE+KT2Xow8s5tfcA9mijaNemE+7gQ4DlkVd7PHggpUckUFN+faA5vPOTfSEn6T+MEux5ZoTnncnawkgqtu40DhzVV8=
Content-Type:
- application/json
url: https://api.dynect.net/REST/AllRecord/go-dynect.test/foobar.go-dynect.test
method: GET
response:
body: '{"status": "success", "data": ["/REST/ARecord/go-dynect.test/foobar.go-dynect.test/318905321"],
"job_id": 4342165004, "msgs": [{"INFO": "get_tree: Here is your zone tree",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:22:46 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- S7uWFq5OnRrL0divNfQgijM1gPTh8aIa3qHvoH+t1GVF84hwfMF8e9BD5ty09DuMprfDW1pDMgG45mEYNJE+KT2Xow8s5tfcA9mijaNemE+7gQ4DlkVd7PHggpUckUFN+faA5vPOTfSEn6T+MEux5ZoTnncnawkgqtu40DhzVV8=
Content-Type:
- application/json
url: https://api.dynect.net/REST/ARecord/go-dynect.test/foobar.go-dynect.test/318905321
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "ttl": 3600, "fqdn":
"foobar.go-dynect.test", "record_type": "A", "rdata": {"address": "10.9.8.7"},
"record_id": 318905321}, "job_id": 4342165009, "msgs": [{"INFO": "get: Found
the record", "SOURCE": "API-B", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:22:46 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- S7uWFq5OnRrL0divNfQgijM1gPTh8aIa3qHvoH+t1GVF84hwfMF8e9BD5ty09DuMprfDW1pDMgG45mEYNJE+KT2Xow8s5tfcA9mijaNemE+7gQ4DlkVd7PHggpUckUFN+faA5vPOTfSEn6T+MEux5ZoTnncnawkgqtu40DhzVV8=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4342165013, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:22:47 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,79 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "l5QZksz8FRhr+DekJ0SHgoeziztmUOyFYVtVhjS/yOLQSqS/fr72nuCtQhtQtUoJLperzxQ6wid9CIg6i5SOlzBBv2iVeYUAr4ilU1jueUcuS/AYNoRU6O6IBegImDB+nP1+Ao7MekShnUZfUr2e3spRYIUg3eUg60hBa61nT70=",
"version": "3.7.9"}, "job_id": 4342199438, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:38:50 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- l5QZksz8FRhr+DekJ0SHgoeziztmUOyFYVtVhjS/yOLQSqS/fr72nuCtQhtQtUoJLperzxQ6wid9CIg6i5SOlzBBv2iVeYUAr4ilU1jueUcuS/AYNoRU6O6IBegImDB+nP1+Ao7MekShnUZfUr2e3spRYIUg3eUg60hBa61nT70=
Content-Type:
- application/json
url: https://api.dynect.net/REST/AllRecord/go-dynect.test/unknown.go-dynect.test
method: GET
response:
body: '{"status": "failure", "data": {}, "job_id": 4342199445, "msgs": [{"INFO":
"node: Node is not in the zone", "SOURCE": "BLL", "ERR_CD": "NOT_FOUND", "LVL":
"ERROR"}, {"INFO": "get_tree: Node name not found within the zone", "SOURCE":
"BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:38:50 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 404 Not Found
code: 404
- request:
body: ""
form: {}
headers:
Auth-Token:
- l5QZksz8FRhr+DekJ0SHgoeziztmUOyFYVtVhjS/yOLQSqS/fr72nuCtQhtQtUoJLperzxQ6wid9CIg6i5SOlzBBv2iVeYUAr4ilU1jueUcuS/AYNoRU6O6IBegImDB+nP1+Ao7MekShnUZfUr2e3spRYIUg3eUg60hBa61nT70=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4342199451, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:38:50 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,104 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "JhswjAiu7O3gRKuQlCTCuurkwkdZZ0yYOzTZiUa9Fkr5YlzPmQIQJGtqOKV4dGmaYIkldRpIDbH6muKPDSmoa6TMFv0SNH0+vj6MgGeOqmW2H6vahp6ENWlZICR5ra56OTANL4CNuznc8PAp1e6dQI4yAsfZ9J1ZFKjrajm67K4=",
"version": "3.7.9"}, "job_id": 4342161224, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:20:42 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- JhswjAiu7O3gRKuQlCTCuurkwkdZZ0yYOzTZiUa9Fkr5YlzPmQIQJGtqOKV4dGmaYIkldRpIDbH6muKPDSmoa6TMFv0SNH0+vj6MgGeOqmW2H6vahp6ENWlZICR5ra56OTANL4CNuznc8PAp1e6dQI4yAsfZ9J1ZFKjrajm67K4=
Content-Type:
- application/json
url: https://api.dynect.net/REST/AllRecord/go-dynect.test/foo.go-dynect.test
method: GET
response:
body: '{"status": "success", "data": ["/REST/CNAMERecord/go-dynect.test/foo.go-dynect.test/318905322"],
"job_id": 4342161229, "msgs": [{"INFO": "get_tree: Here is your zone tree",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:20:42 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- JhswjAiu7O3gRKuQlCTCuurkwkdZZ0yYOzTZiUa9Fkr5YlzPmQIQJGtqOKV4dGmaYIkldRpIDbH6muKPDSmoa6TMFv0SNH0+vj6MgGeOqmW2H6vahp6ENWlZICR5ra56OTANL4CNuznc8PAp1e6dQI4yAsfZ9J1ZFKjrajm67K4=
Content-Type:
- application/json
url: https://api.dynect.net/REST/CNAMERecord/go-dynect.test/foo.go-dynect.test/318905322
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "ttl": 3600, "fqdn":
"foo.go-dynect.test", "record_type": "CNAME", "rdata": {"cname": "foobar.go-dynect.test."},
"record_id": 318905322}, "job_id": 4342161235, "msgs": [{"INFO": "get: Found
the record", "SOURCE": "API-B", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:20:42 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- JhswjAiu7O3gRKuQlCTCuurkwkdZZ0yYOzTZiUa9Fkr5YlzPmQIQJGtqOKV4dGmaYIkldRpIDbH6muKPDSmoa6TMFv0SNH0+vj6MgGeOqmW2H6vahp6ENWlZICR5ra56OTANL4CNuznc8PAp1e6dQI4yAsfZ9J1ZFKjrajm67K4=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4342161240, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 11:20:42 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,269 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "/4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=",
"version": "3.7.9"}, "job_id": 4341026959, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:58 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/AllRecord/go-dynect.test
method: GET
response:
body: '{"status": "success", "data": ["/REST/CNAMERecord/go-dynect.test/foo.go-dynect.test/318905322",
"/REST/SOARecord/go-dynect.test/go-dynect.test/318812133", "/REST/NSRecord/go-dynect.test/go-dynect.test/318812135",
"/REST/NSRecord/go-dynect.test/go-dynect.test/318812136", "/REST/NSRecord/go-dynect.test/go-dynect.test/318812137",
"/REST/NSRecord/go-dynect.test/go-dynect.test/318812138", "/REST/ARecord/go-dynect.test/foobar.go-dynect.test/318905321"],
"job_id": 4341026968, "msgs": [{"INFO": "get_tree: Here is your zone tree",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:58 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/CNAMERecord/go-dynect.test/foo.go-dynect.test/318905322
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "ttl": 3600, "fqdn":
"foo.go-dynect.test", "record_type": "CNAME", "rdata": {"cname": "foobar.go-dynect.test."},
"record_id": 318905322}, "job_id": 4341026976, "msgs": [{"INFO": "get: Found
the record", "SOURCE": "API-B", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:58 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/SOARecord/go-dynect.test/go-dynect.test/318812133
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "ttl": 3600, "fqdn":
"go-dynect.test", "record_type": "SOA", "rdata": {"rname": "admin@go-dynect.com.",
"retry": 600, "mname": "ns1.p19.dynect.net.", "minimum": 1800, "refresh": 3600,
"expire": 604800, "serial": 2017122000}, "record_id": 318812133, "serial_style":
"day"}, "job_id": 4341026980, "msgs": [{"INFO": "get: Found the record", "SOURCE":
"API-B", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:58 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/NSRecord/go-dynect.test/go-dynect.test/318812135
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "service_class":
"Primary", "ttl": 86400, "fqdn": "go-dynect.test", "record_type": "NS", "rdata":
{"nsdname": "ns1.p19.dynect.net."}, "record_id": 318812135}, "job_id": 4341026990,
"msgs": [{"INFO": "get: Found the record", "SOURCE": "API-B", "ERR_CD": null,
"LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:59 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/NSRecord/go-dynect.test/go-dynect.test/318812136
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "service_class":
"Primary", "ttl": 86400, "fqdn": "go-dynect.test", "record_type": "NS", "rdata":
{"nsdname": "ns2.p19.dynect.net."}, "record_id": 318812136}, "job_id": 4341026995,
"msgs": [{"INFO": "get: Found the record", "SOURCE": "API-B", "ERR_CD": null,
"LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:59 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/NSRecord/go-dynect.test/go-dynect.test/318812137
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "service_class":
"Primary", "ttl": 86400, "fqdn": "go-dynect.test", "record_type": "NS", "rdata":
{"nsdname": "ns3.p19.dynect.net."}, "record_id": 318812137}, "job_id": 4341027001,
"msgs": [{"INFO": "get: Found the record", "SOURCE": "API-B", "ERR_CD": null,
"LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:59 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/NSRecord/go-dynect.test/go-dynect.test/318812138
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "service_class":
"Primary", "ttl": 86400, "fqdn": "go-dynect.test", "record_type": "NS", "rdata":
{"nsdname": "ns4.p19.dynect.net."}, "record_id": 318812138}, "job_id": 4341027006,
"msgs": [{"INFO": "get: Found the record", "SOURCE": "API-B", "ERR_CD": null,
"LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:59 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/ARecord/go-dynect.test/foobar.go-dynect.test/318905321
method: GET
response:
body: '{"status": "success", "data": {"zone": "go-dynect.test", "ttl": 3600, "fqdn":
"foobar.go-dynect.test", "record_type": "A", "rdata": {"address": "10.9.8.7"},
"record_id": 318905321}, "job_id": 4341027017, "msgs": [{"INFO": "get: Found
the record", "SOURCE": "API-B", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:08:59 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- /4dfk/aAs9zK0QqVlmx6VHP+3vAvsevOQ6cGM3+KIO9MFiMAEbc52KBi/ayBwkqGP/N1Ou1Kbf7calZZ4tSvIrmH/7EtTox9qGFbgQkvMLXQz7E4GEZSLd7ejFKjxkZh8ttUSBwzkhQZoBPyy0nry1i/jakCgu09P3eAxPiBJ0U=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4341027020, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:09:00 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,53 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "+cG++GbemK1hoYxrvq2SFGz00mY78zRZhWntTbLxYF42k22o7w/d0Vk+sEvJayq5jSo8ivphahLFmZV8b99TJMRNZFcpFC0NyYeyL/7l8Grsdpplh6l1pMmkInSe3mXVuvgKS5cVSUN5Z8e6DVf9K0Jz/aLZBeL70Qc5VQktaKc=",
"version": "3.7.9"}, "job_id": 4340980452, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 02:46:34 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- +cG++GbemK1hoYxrvq2SFGz00mY78zRZhWntTbLxYF42k22o7w/d0Vk+sEvJayq5jSo8ivphahLFmZV8b99TJMRNZFcpFC0NyYeyL/7l8Grsdpplh6l1pMmkInSe3mXVuvgKS5cVSUN5Z8e6DVf9K0Jz/aLZBeL70Qc5VQktaKc=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4340980466, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 02:46:34 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

View File

@ -0,0 +1,79 @@
---
version: 1
rwmutex: {}
interactions:
- request:
body: '{"user_name":"dynect-user","password":"p@55w0rd","customer_name":"go-dynect"}'
form: {}
headers:
Auth-Token:
- ""
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: POST
response:
body: '{"status": "success", "data": {"token": "PBz+i35+fKgVUdmzmvGJzcq0F+p+ExJI9Y5fe8VgiJZhFFsY/Vp2KVZb9JBj/CSCewT7rum6IgoBxf8BzK2LuTNzFSgzsWkztKsF+awruRWFdAtl8XfBoG6pIDAcLIgjuE/vUt4WOUH007w6G7FTKt+dojSTK19mw130KtUHik8=",
"version": "3.7.9"}, "job_id": 4341009633, "msgs": [{"INFO": "login: Login successful",
"SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:00:16 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- PBz+i35+fKgVUdmzmvGJzcq0F+p+ExJI9Y5fe8VgiJZhFFsY/Vp2KVZb9JBj/CSCewT7rum6IgoBxf8BzK2LuTNzFSgzsWkztKsF+awruRWFdAtl8XfBoG6pIDAcLIgjuE/vUt4WOUH007w6G7FTKt+dojSTK19mw130KtUHik8=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Zone
method: GET
response:
body: '{"status": "success", "data": ["/REST/Zone/example.com/",
"/REST/Zone/example.net", "/REST/Zone/go-dynect.test/"], "job_id": 4341009645,
"msgs": [{"INFO": "get: Your 3 zones", "SOURCE": "BLL", "ERR_CD": null,
"LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:00:18 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200
- request:
body: ""
form: {}
headers:
Auth-Token:
- PBz+i35+fKgVUdmzmvGJzcq0F+p+ExJI9Y5fe8VgiJZhFFsY/Vp2KVZb9JBj/CSCewT7rum6IgoBxf8BzK2LuTNzFSgzsWkztKsF+awruRWFdAtl8XfBoG6pIDAcLIgjuE/vUt4WOUH007w6G7FTKt+dojSTK19mw130KtUHik8=
Content-Type:
- application/json
url: https://api.dynect.net/REST/Session
method: DELETE
response:
body: '{"status": "success", "data": {}, "job_id": 4341009725, "msgs": [{"INFO":
"logout: Logout successful", "SOURCE": "BLL", "ERR_CD": null, "LVL": "INFO"}]}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Wed, 20 Dec 2017 03:00:18 GMT
Server:
- nginx/1.4.6 (Ubuntu)
status: 200 OK
code: 200

30
vendor/github.com/nesv/go-dynect/dynect/helpers.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package dynect
import "fmt"
func GetAllDSFServicesDetailed(c *Client) (error, []DSFService) {
var dsfsResponse AllDSFDetailedResponse
requestData := struct {
Detail string `json:"detail"`
}{Detail: "Y"}
if err := c.Do("GET", "DSF", requestData, &dsfsResponse); err != nil {
return err, nil
}
return nil, dsfsResponse.Data
}
func GetDSFServiceDetailed(c *Client, id string) (error, DSFService) {
var dsfsResponse DSFResponse
requestData := struct {
Detail string `json:"detail"`
}{Detail: "Y"}
loc := fmt.Sprintf("DSF/%s", id)
if err := c.Do("GET", loc, requestData, &dsfsResponse); err != nil {
return err, DSFService{}
}
return nil, dsfsResponse.Data
}

8
vendor/github.com/nesv/go-dynect/dynect/job.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
package dynect
type JobData struct {
Status string `json:"status"`
Data interface{} `json:"data"`
ID int `json:"job_id"`
Messages []MessageBlock `json:"msgs"`
}

65
vendor/github.com/nesv/go-dynect/dynect/json.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package dynect
/*
This struct represents the request body that would be sent to the DynECT API
for logging in and getting a session token for future requests.
*/
type LoginBlock struct {
Username string `json:"user_name"`
Password string `json:"password"`
CustomerName string `json:"customer_name"`
}
// Type ResponseBlock holds the "header" information returned by any call to
// the DynECT API.
//
// All response-type structs should include this as an anonymous/embedded field.
type ResponseBlock struct {
Status string `json:"status"`
JobId int `json:"job_id,omitempty"`
Messages []MessageBlock `json:"msgs,omitempty"`
}
// Type MessageBlock holds the message information from the server, and is
// nested within the ResponseBlock type.
type MessageBlock struct {
Info string `json:"INFO"`
Source string `json:"SOURCE"`
ErrorCode string `json:"ERR_CD"`
Level string `json:"LVL"`
}
// Type LoginResponse holds the data returned by an HTTP POST call to
// https://api.dynect.net/REST/Session/.
type LoginResponse struct {
ResponseBlock
Data LoginDataBlock `json:"data"`
}
// Type LoginDataBlock holds the token and API version information from an HTTP
// POST call to https://api.dynect.net/REST/Session/.
//
// It is nested within the LoginResponse struct.
type LoginDataBlock struct {
Token string `json:"token"`
Version string `json:"version"`
}
// RecordRequest holds the request body for a record create/update
type RecordRequest struct {
RData DataBlock `json:"rdata"`
TTL string `json:"ttl,omitempty"`
}
// CreateZoneBlock holds the request body for a zone create
type CreateZoneBlock struct {
RName string `json:"rname"`
SerialStyle string `json:"serial_style,omitempty"`
TTL string `json:"ttl"`
}
// PublishZoneBlock holds the request body for a publish zone request
// https://help.dyn.com/update-zone-api/
type PublishZoneBlock struct {
Publish bool `json:"publish"`
}

12
vendor/github.com/nesv/go-dynect/dynect/record.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
package dynect
// Record simple struct to hold record details
type Record struct {
ID string
Zone string
Name string
Value string
Type string
FQDN string
TTL string
}

171
vendor/github.com/nesv/go-dynect/dynect/records.go generated vendored Normal file
View File

@ -0,0 +1,171 @@
package dynect
// Type AllRecordsResponse is a struct for holding a list of all URIs returned
// from an HTTP GET call to either https://api.dynect.net/REST/AllRecord/<zone>
// or https://api/dynect.net/REST/AllRecord/<zone>/<FQDN>/.
type AllRecordsResponse struct {
ResponseBlock
Data []string `json:"data"`
}
// Type RecordResponse is used to hold the information for a single DNS record
// returned from Dyn's DynECT API.
type RecordResponse struct {
ResponseBlock
Data BaseRecord `json:"data"`
}
/*
The base struct for record data returned from the Dyn REST API.
It should never be directly passed to the *Client.Do() function for marshaling
response data to. Instead, it should aid in the composition of a more-specific
response struct.
*/
type BaseRecord struct {
FQDN string `json:"fqdn"`
RecordId int `json:"record_id"`
RecordType string `json:"record_type"`
TTL int `json:"ttl"`
Zone string `json:"zone"`
RData DataBlock `json:"rdata"`
}
// Type DataBlock is nested within the BaseRecord struct, and is used for
// holding record information.
//
// The comment above each field indicates which record types you can expect
// the information to be provided.
type DataBlock struct {
// A, AAAA
Address string `json:"address,omitempty" bson:"address,omitempty"`
// ALIAS
Alias string `json:"alias,omitempty" bson:"alias,omitempty"`
// CERT, DNSKEY, DS, IPSECKEY, KEY, SSHFP
Algorithm string `json:"algorithm,omitempty" bson:"algorithm,omitempty"`
// LOC
Altitude string `json:"altitude,omitempty" bson:"altitude,omitempty"`
// CNAME
CName string `json:"cname,omitempty" bson:"cname,omitempty"`
// CERT
Certificate string `json:"certificate,omitempty" bson:"algorithm,omitempty"`
// DNAME
DName string `json:"dname,omitempty" bson:"dname,omitempty"`
// DHCID, DS
Digest string `json:"digest,omitempty" bson:"digest,omitempty"`
// DS
DigestType string `json:"digtype,omitempty" bson:"digest_type,omitempty"`
// KX, MX
Exchange string `json:"exchange,omitempty" bson:"exchange,omitempty"`
// SSHFP
FPType string `json:"fptype,omitempty" bson:"fp_type,omitempty"`
// SSHFP
Fingerprint string `json:"fingerprint,omitempty" bson:"fingerprint,omitempty"`
// DNSKEY, KEY, NAPTR
Flags string `json:"flags,omitempty" bson:"flags,omitempty"`
// CERT
Format string `json:"format,omitempty" bson:"format,omitempty"`
// IPSECKEY
GatewayType string `json:"gatetype,omitempty" bson:"gateway_type,omitempty"`
// LOC
HorizPre string `json:"horiz_pre,omitempty" bson:"horiz_pre,omitempty"`
// DS
KeyTag string `json:"keytag,omitempty" bson:"keytag,omitempty"`
// LOC
Latitude string `json:"latitude,omitempty" bson:"latitude,omitempty"`
// LOC
Longitude string `json:"longitude,omitempty" bson:"longitude,omitempty"`
// PX
Map822 string `json:"map822,omitempty" bson:"map_822,omitempty"`
// PX
MapX400 string `json:"mapx400,omitempty" bson:"map_x400,omitempty"`
// RP
Mbox string `json:"mbox,omitempty" bson:"mbox,omitempty"`
// NS
NSDName string `json:"nsdname,omitempty" bson:"nsdname,omitempty"`
// NSAP
NSAP string `json:"nsap,omitempty" bson:"nsap,omitempty"`
// NAPTR
Order string `json:"order,omitempty" bson:"order,omitempty"`
// SRV
Port string `json:"port,omitempty" bson:"port,omitempty"`
// IPSECKEY
Precendence string `json:"precendence,omitempty" bson:"precendence,omitempty"`
// KX, MX, NAPTR, PX
Preference int `json:"preference,omitempty" bson:"preference,omitempty"`
// SRV
Priority int `json:"priority,omitempty" bson:"priority,omitempty"`
// DNSKEY, KEY
Protocol string `json:"protocol,omitempty" bson:"protocol,omitempty"`
// PTR
PTRDname string `json:"ptrdname,omitempty" bson:"ptrdname,omitempty"`
// DNSKEY, IPSECKEY, KEY
PublicKey string `json:"public_key,omitempty" bson:"public_key,omitempty"`
// NAPTR
Regexp string `json:"regexp,omitempty" bson:"regexp,omitempty"`
// NAPTR
Replacement string `json:"replacement,omitempty" bson:"replacement,omitempty"`
// SOA
RName string `json:"rname,omitempty" bson:"rname,omitempty"`
// NAPTR
Services string `json:"services,omitempty" bson:"services,omitempty"`
// LOC
Size string `json:"size,omitempty" bson:"size,omitempty"`
// CERT
Tag string `json:"tag,omitempty" bson:"tag,omitempty"`
// SRV
Target string `json:"target,omitempty" bson:"target,omitempty"`
// RP
TxtDName string `json:"txtdname,omitempty" bson:"txtdname,omitempty"`
// SPF, TXT
TxtData string `json:"txtdata,omitempty" bson:"txtdata,omitempty"`
// LOC
Version string `json:"version,omitempty" bson:"version,omitempty"`
// LOC
VertPre string `json:"vert_pre,omitempty" bson:"vert_pre,omitempty"`
// SRV
Weight string `json:"weight,omitempty" bson:"weight,omitempty"`
}

9
vendor/github.com/nesv/go-dynect/dynect/zone.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package dynect
// Zone struct to hold record details
type Zone struct {
Serial string
SerialStyle string
Zone string
Type string
}

24
vendor/github.com/nesv/go-dynect/dynect/zones.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
package dynect
// ZonesResponse is used for holding the data returned by a call to
// "https://api.dynect.net/REST/Zone/".
type ZonesResponse struct {
ResponseBlock
Data []string `json:"data"`
}
// ZoneResponse is used for holding the data returned by a call to
// "https://api.dynect.net/REST/Zone/ZONE_NAME".
type ZoneResponse struct {
ResponseBlock
Data ZoneDataBlock `json:"data"`
}
// Type ZoneDataBlock is used as a nested struct, which holds the data for a
// zone returned by a call to "https://api.dynect.net/REST/Zone/ZONE_NAME".
type ZoneDataBlock struct {
Serial int `json:"serial"`
SerialStyle string `json:"serial_style"`
Zone string `json:"zone"`
ZoneType string `json:"zone_type"`
}