From 7f369695f3ee2b5be0a14e5bfa7ef2247ba98d8c Mon Sep 17 00:00:00 2001 From: Brett Kochendorfer Date: Sat, 6 Jan 2018 16:12:56 -0600 Subject: [PATCH 01/10] Convert glide to dep --- Gopkg.lock | 621 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Gopkg.toml | 61 ++++++ glide.lock | 414 ----------------------------------- glide.yaml | 61 ------ 4 files changed, 682 insertions(+), 475 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml delete mode 100644 glide.lock delete mode 100644 glide.yaml diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000000..408c7f950f --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,621 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "cloud.google.com/go" + packages = [ + "compute/metadata", + "internal" + ] + revision = "3b1ae45394a234c385be014e9a488f2bb6eef821" + +[[projects]] + name = "github.com/Azure/azure-sdk-for-go" + packages = ["arm/dns"] + revision = "2629e2dfcfeab50896230140542c3b9d89b35ae1" + version = "v10.0.4-beta" + +[[projects]] + name = "github.com/Azure/go-autorest" + packages = [ + "autorest", + "autorest/adal", + "autorest/azure", + "autorest/date", + "autorest/to" + ] + revision = "58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d" + version = "v8.0.0" + +[[projects]] + name = "github.com/PuerkitoBio/purell" + packages = ["."] + revision = "8a290539e2e8629dbc4e6bad948158f790ec31f4" + version = "v1.0.0" + +[[projects]] + name = "github.com/PuerkitoBio/urlesc" + packages = ["."] + revision = "5bd2802263f21d8788851d5305584c82a5c75d7e" + +[[projects]] + name = "github.com/alecthomas/kingpin" + packages = ["."] + revision = "1087e65c9441605df944fb12c33f0fe7072d18ca" + version = "v2.2.5" + +[[projects]] + branch = "master" + name = "github.com/alecthomas/template" + packages = [ + ".", + "parse" + ] + revision = "a0175ee3bccc567396460bf5acd36800cb10c49c" + +[[projects]] + branch = "master" + name = "github.com/alecthomas/units" + packages = ["."] + revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" + +[[projects]] + name = "github.com/aws/aws-sdk-go" + packages = [ + "aws", + "aws/awserr", + "aws/awsutil", + "aws/client", + "aws/client/metadata", + "aws/corehandlers", + "aws/credentials", + "aws/credentials/ec2rolecreds", + "aws/credentials/endpointcreds", + "aws/credentials/stscreds", + "aws/defaults", + "aws/ec2metadata", + "aws/endpoints", + "aws/request", + "aws/session", + "aws/signer/v4", + "internal/shareddefaults", + "private/protocol", + "private/protocol/query", + "private/protocol/query/queryutil", + "private/protocol/rest", + "private/protocol/restxml", + "private/protocol/xml/xmlutil", + "service/route53", + "service/sts" + ] + revision = "96358b8282b1a3aa66836d2f2fe66216cc419668" + version = "v1.8.44" + +[[projects]] + branch = "master" + name = "github.com/beorn7/perks" + packages = ["quantile"] + revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" + +[[projects]] + name = "github.com/cloudflare/cloudflare-go" + packages = ["."] + revision = "4c6994ac3877fbb627766edadc67f4e816e8c890" + version = "v0.7.4" + +[[projects]] + name = "github.com/coreos/go-oidc" + packages = [ + "http", + "jose", + "key", + "oauth2", + "oidc" + ] + revision = "be73733bb8cc830d0205609b95d125215f8e9c70" + +[[projects]] + name = "github.com/coreos/pkg" + packages = [ + "health", + "httputil", + "timeutil" + ] + revision = "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8" + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" + +[[projects]] + name = "github.com/dgrijalva/jwt-go" + packages = ["."] + revision = "d2709f9f1f31ebcda9651b03077758c1f3a0018c" + version = "v3.0.0" + +[[projects]] + name = "github.com/digitalocean/godo" + packages = [ + ".", + "context" + ] + revision = "77ea48de76a7b31b234d854f15d003c68bb2fb90" + version = "v1.1.1" + +[[projects]] + name = "github.com/dnsimple/dnsimple-go" + packages = ["dnsimple"] + revision = "d1105abc03b313d7b8d9b04364f6bd053b346e59" + version = "v0.14.0" + +[[projects]] + name = "github.com/docker/distribution" + packages = [ + "digest", + "reference" + ] + revision = "cd27f179f2c10c5d300e6d09025b538c475b0d51" + +[[projects]] + name = "github.com/emicklei/go-restful" + packages = [ + ".", + "log", + "swagger" + ] + revision = "09691a3b6378b740595c1002f40c34dd5f218a22" + +[[projects]] + name = "github.com/ghodss/yaml" + packages = ["."] + revision = "73d445a93680fa1a78ae23a5839bad48f32ba1ee" + +[[projects]] + name = "github.com/go-ini/ini" + packages = ["."] + revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a" + version = "v1.32.0" + +[[projects]] + name = "github.com/go-openapi/jsonpointer" + packages = ["."] + revision = "46af16f9f7b149af66e5d1bd010e3574dc06de98" + +[[projects]] + name = "github.com/go-openapi/jsonreference" + packages = ["."] + revision = "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272" + +[[projects]] + name = "github.com/go-openapi/spec" + packages = ["."] + revision = "6aced65f8501fe1217321abf0749d354824ba2ff" + +[[projects]] + name = "github.com/go-openapi/swag" + packages = ["."] + revision = "1d0bd113de87027671077d3c71eb3ac5d7dbba72" + +[[projects]] + name = "github.com/gogo/protobuf" + packages = [ + "proto", + "sortkeys" + ] + revision = "e18d7aa8f8c624c915db340349aad4c49b10d173" + +[[projects]] + name = "github.com/golang/glog" + packages = ["."] + revision = "44145f04b68cf362d9c4df2182967c2275eaefed" + +[[projects]] + name = "github.com/golang/protobuf" + packages = ["proto"] + revision = "8616e8ee5e20a1704615e6c8d7afcdac06087a67" + +[[projects]] + branch = "master" + name = "github.com/google/go-querystring" + packages = ["query"] + revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a" + +[[projects]] + name = "github.com/google/gofuzz" + packages = ["."] + revision = "44d81051d367757e1c7c6a5a86423ece9afcf63c" + +[[projects]] + name = "github.com/howeyc/gopass" + packages = ["."] + revision = "3ca23474a7c7203e0a0a070fd33508f6efdb9b3d" + +[[projects]] + name = "github.com/imdario/mergo" + packages = ["."] + revision = "6633656539c1639d9d78127b7d47c622b5d7b6dc" + +[[projects]] + branch = "master" + name = "github.com/infobloxopen/infoblox-go-client" + packages = ["."] + revision = "e2811d86bed7bb487eeb0806337b6f9e9d93d5e7" + +[[projects]] + branch = "master" + name = "github.com/jmespath/go-jmespath" + packages = ["."] + revision = "dd801d4f4ce7ac746e7e7b4489d2fa600b3b096b" + +[[projects]] + name = "github.com/jonboulle/clockwork" + packages = ["."] + revision = "72f9bd7c4e0c2a40055ab3d0f09654f730cce982" + +[[projects]] + name = "github.com/juju/ratelimit" + packages = ["."] + revision = "77ed1c8a01217656d2080ad51981f6e99adaa177" + +[[projects]] + name = "github.com/kubernetes/repo-infra" + packages = ["verify/boilerplate/test"] + revision = "2d2eb5e12b4663fc4d764b5db9daab39334d3f37" + +[[projects]] + name = "github.com/linki/instrumented_http" + packages = ["."] + revision = "508103cfb3b315fa9752b5bcd4fb2d97bbc26d89" + version = "v0.2.0" + +[[projects]] + name = "github.com/mailru/easyjson" + packages = [ + "buffer", + "jlexer", + "jwriter" + ] + revision = "d5b7844b561a7bc640052f1b935f7b800330d7e0" + +[[projects]] + branch = "master" + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "f15c970de5b76fac0b59abb32d62c17cc7bed265" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "d8ed2627bdf02c080bf22230dbb337003b7aba2d" + +[[projects]] + name = "github.com/prometheus/client_golang" + packages = [ + "prometheus", + "prometheus/promhttp" + ] + revision = "c5b7fccd204277076155f10851dad72b76a49317" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "github.com/prometheus/client_model" + packages = ["go"] + revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" + +[[projects]] + branch = "master" + name = "github.com/prometheus/common" + packages = [ + "expfmt", + "internal/bitbucket.org/ww/goautoneg", + "model" + ] + revision = "2e54d0b93cba2fd133edc32211dcc32c06ef72ca" + +[[projects]] + name = "github.com/prometheus/procfs" + packages = [ + ".", + "xfs" + ] + revision = "a6e9df898b1336106c743392c48ee0b71f5c4efa" + +[[projects]] + name = "github.com/sirupsen/logrus" + packages = ["."] + revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" + version = "v1.0.3" + +[[projects]] + name = "github.com/spf13/pflag" + packages = ["."] + revision = "9ff6c6923cfffbcd502984b8e0c80539a94968b7" + +[[projects]] + name = "github.com/stretchr/objx" + packages = ["."] + revision = "cbeaeb16a013161a98496fad62933b1d21786672" + +[[projects]] + name = "github.com/stretchr/testify" + packages = [ + "assert", + "mock", + "require", + "suite" + ] + revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" + version = "v1.1.4" + +[[projects]] + branch = "master" + name = "github.com/tent/http-link-go" + packages = ["."] + revision = "ac974c61c2f990f4115b119354b5e0b47550e888" + +[[projects]] + name = "github.com/ugorji/go" + packages = ["codec"] + revision = "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74" + +[[projects]] + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + revision = "d172538b2cfce0c13cee31e647d0367aa8cd2486" + +[[projects]] + name = "golang.org/x/net" + packages = [ + "context", + "context/ctxhttp", + "http2", + "http2/hpack", + "idna", + "lex/httplex" + ] + revision = "e90d6d0afc4c315a0d87a568ae68577cc15149a0" + +[[projects]] + name = "golang.org/x/oauth2" + packages = [ + ".", + "google", + "internal", + "jws", + "jwt" + ] + revision = "3c3a985cb79f52a3190fbc056984415ca6763d01" + +[[projects]] + name = "golang.org/x/sys" + packages = ["unix"] + revision = "8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9" + +[[projects]] + name = "golang.org/x/text" + packages = [ + "cases", + "internal/gen", + "internal/tag", + "internal/triegen", + "internal/ucd", + "language", + "runes", + "secure/bidirule", + "secure/precis", + "transform", + "unicode/bidi", + "unicode/cldr", + "unicode/norm", + "unicode/rangetable", + "width" + ] + revision = "2910a502d2bf9e43193af9d68ca516529614eed3" + +[[projects]] + name = "google.golang.org/api" + packages = [ + "dns/v1", + "gensupport", + "googleapi", + "googleapi/internal/uritemplates" + ] + revision = "a0ff90edab763c86aa88f2b1eb4f3301b82f6336" + +[[projects]] + name = "google.golang.org/appengine" + packages = [ + ".", + "internal", + "internal/app_identity", + "internal/base", + "internal/datastore", + "internal/log", + "internal/modules", + "internal/remote_api", + "internal/urlfetch", + "urlfetch" + ] + revision = "4f7eeb5305a4ba1966344836ba4af9996b7b4e05" + +[[projects]] + name = "gopkg.in/inf.v0" + packages = ["."] + revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4" + version = "v0.9.0" + +[[projects]] + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "53feefa2559fb8dfa8d81baad31be332c97d6c77" + +[[projects]] + name = "k8s.io/apimachinery" + packages = [ + "pkg/api/errors", + "pkg/api/meta", + "pkg/api/resource", + "pkg/apimachinery", + "pkg/apimachinery/announced", + "pkg/apimachinery/registered", + "pkg/apis/meta/v1", + "pkg/apis/meta/v1/unstructured", + "pkg/conversion", + "pkg/conversion/queryparams", + "pkg/fields", + "pkg/labels", + "pkg/openapi", + "pkg/runtime", + "pkg/runtime/schema", + "pkg/runtime/serializer", + "pkg/runtime/serializer/json", + "pkg/runtime/serializer/protobuf", + "pkg/runtime/serializer/recognizer", + "pkg/runtime/serializer/streaming", + "pkg/runtime/serializer/versioning", + "pkg/selection", + "pkg/types", + "pkg/util/errors", + "pkg/util/framer", + "pkg/util/intstr", + "pkg/util/json", + "pkg/util/net", + "pkg/util/rand", + "pkg/util/runtime", + "pkg/util/sets", + "pkg/util/validation", + "pkg/util/validation/field", + "pkg/util/wait", + "pkg/util/yaml", + "pkg/version", + "pkg/watch", + "third_party/forked/golang/reflect" + ] + revision = "85ace5365f33b16fc735c866a12e3c765b9700f2" + +[[projects]] + name = "k8s.io/client-go" + packages = [ + "discovery", + "discovery/fake", + "kubernetes", + "kubernetes/fake", + "kubernetes/scheme", + "kubernetes/typed/apps/v1beta1", + "kubernetes/typed/apps/v1beta1/fake", + "kubernetes/typed/authentication/v1", + "kubernetes/typed/authentication/v1/fake", + "kubernetes/typed/authentication/v1beta1", + "kubernetes/typed/authentication/v1beta1/fake", + "kubernetes/typed/authorization/v1", + "kubernetes/typed/authorization/v1/fake", + "kubernetes/typed/authorization/v1beta1", + "kubernetes/typed/authorization/v1beta1/fake", + "kubernetes/typed/autoscaling/v1", + "kubernetes/typed/autoscaling/v1/fake", + "kubernetes/typed/autoscaling/v2alpha1", + "kubernetes/typed/autoscaling/v2alpha1/fake", + "kubernetes/typed/batch/v1", + "kubernetes/typed/batch/v1/fake", + "kubernetes/typed/batch/v2alpha1", + "kubernetes/typed/batch/v2alpha1/fake", + "kubernetes/typed/certificates/v1beta1", + "kubernetes/typed/certificates/v1beta1/fake", + "kubernetes/typed/core/v1", + "kubernetes/typed/core/v1/fake", + "kubernetes/typed/extensions/v1beta1", + "kubernetes/typed/extensions/v1beta1/fake", + "kubernetes/typed/policy/v1beta1", + "kubernetes/typed/policy/v1beta1/fake", + "kubernetes/typed/rbac/v1alpha1", + "kubernetes/typed/rbac/v1alpha1/fake", + "kubernetes/typed/rbac/v1beta1", + "kubernetes/typed/rbac/v1beta1/fake", + "kubernetes/typed/settings/v1alpha1", + "kubernetes/typed/settings/v1alpha1/fake", + "kubernetes/typed/storage/v1", + "kubernetes/typed/storage/v1/fake", + "kubernetes/typed/storage/v1beta1", + "kubernetes/typed/storage/v1beta1/fake", + "pkg/api", + "pkg/api/install", + "pkg/api/v1", + "pkg/apis/apps", + "pkg/apis/apps/install", + "pkg/apis/apps/v1beta1", + "pkg/apis/authentication", + "pkg/apis/authentication/install", + "pkg/apis/authentication/v1", + "pkg/apis/authentication/v1beta1", + "pkg/apis/authorization", + "pkg/apis/authorization/install", + "pkg/apis/authorization/v1", + "pkg/apis/authorization/v1beta1", + "pkg/apis/autoscaling", + "pkg/apis/autoscaling/install", + "pkg/apis/autoscaling/v1", + "pkg/apis/autoscaling/v2alpha1", + "pkg/apis/batch", + "pkg/apis/batch/install", + "pkg/apis/batch/v1", + "pkg/apis/batch/v2alpha1", + "pkg/apis/certificates", + "pkg/apis/certificates/install", + "pkg/apis/certificates/v1beta1", + "pkg/apis/extensions", + "pkg/apis/extensions/install", + "pkg/apis/extensions/v1beta1", + "pkg/apis/policy", + "pkg/apis/policy/install", + "pkg/apis/policy/v1beta1", + "pkg/apis/rbac", + "pkg/apis/rbac/install", + "pkg/apis/rbac/v1alpha1", + "pkg/apis/rbac/v1beta1", + "pkg/apis/settings", + "pkg/apis/settings/install", + "pkg/apis/settings/v1alpha1", + "pkg/apis/storage", + "pkg/apis/storage/install", + "pkg/apis/storage/v1", + "pkg/apis/storage/v1beta1", + "pkg/util", + "pkg/util/parsers", + "pkg/version", + "plugin/pkg/client/auth", + "plugin/pkg/client/auth/gcp", + "plugin/pkg/client/auth/oidc", + "rest", + "rest/watch", + "testing", + "third_party/forked/golang/template", + "tools/auth", + "tools/clientcmd", + "tools/clientcmd/api", + "tools/clientcmd/api/latest", + "tools/clientcmd/api/v1", + "tools/metrics", + "transport", + "util/cert", + "util/clock", + "util/flowcontrol", + "util/homedir", + "util/integer", + "util/jsonpath" + ] + revision = "21300e3e11c918b8e6a70fb7293b310683d6c046" + version = "v3.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "9056760f59de670713a6534dc50e7b4ef8f14b1ad25e5a4eb0e269d2d60c4a51" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000000..ba07244cbc --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,61 @@ +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. + +required = ["github.com/kubernetes/repo-infra/verify/boilerplate/test"] +ignored = ["github.com/kubernetes/repo-infra/kazel"] + +[[constraint]] + name = "github.com/Azure/azure-sdk-for-go" + version = "~10.0.4-beta" + +[[constraint]] + name = "github.com/Azure/go-autorest" + version = "~8.0.0" + +[[constraint]] + name = "github.com/alecthomas/kingpin" + version = "~2.2.4" + +[[constraint]] + name = "github.com/aws/aws-sdk-go" + version = "~1.8.20" + +[[constraint]] + name = "github.com/cloudflare/cloudflare-go" + version = "0.7.3" + +[[constraint]] + name = "github.com/digitalocean/godo" + version = "~1.1.0" + +[[constraint]] + name = "github.com/dnsimple/dnsimple-go" + version = "0.14.0" + +[[constraint]] + branch = "master" + name = "github.com/infobloxopen/infoblox-go-client" + +[[constraint]] + name = "github.com/linki/instrumented_http" + version = "0.2.0" + +[[constraint]] + name = "github.com/prometheus/client_golang" + version = "0.8.0" + +[[constraint]] + name = "github.com/sirupsen/logrus" + version = "~1.0.3" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "~1.1.4" + +[[constraint]] + name = "k8s.io/client-go" + version = "~3.0.0-beta.0" + +[[override]] + name = "github.com/kubernetes/repo-infra" + revision = "2d2eb5e12b4663fc4d764b5db9daab39334d3f37" diff --git a/glide.lock b/glide.lock deleted file mode 100644 index 17b2408300..0000000000 --- a/glide.lock +++ /dev/null @@ -1,414 +0,0 @@ -hash: 9f1bc270ce828fb6bc7ab7af69f2419a670eadfba41d1298ff1e3c435336000d -updated: 2017-12-01T11:10:40.494044308+01:00 -imports: -- name: cloud.google.com/go - version: 3b1ae45394a234c385be014e9a488f2bb6eef821 - subpackages: - - compute/metadata - - internal -- name: github.com/alecthomas/kingpin - version: 1087e65c9441605df944fb12c33f0fe7072d18ca -- name: github.com/alecthomas/template - version: a0175ee3bccc567396460bf5acd36800cb10c49c - subpackages: - - parse -- name: github.com/alecthomas/units - version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a -- name: github.com/aws/aws-sdk-go - version: 96358b8282b1a3aa66836d2f2fe66216cc419668 - subpackages: - - aws - - aws/awserr - - aws/awsutil - - aws/client - - aws/client/metadata - - aws/corehandlers - - aws/credentials - - aws/credentials/ec2rolecreds - - aws/credentials/endpointcreds - - aws/credentials/stscreds - - aws/defaults - - aws/ec2metadata - - aws/endpoints - - aws/request - - aws/session - - aws/signer/v4 - - internal/shareddefaults - - private/protocol - - private/protocol/query - - private/protocol/query/queryutil - - private/protocol/rest - - private/protocol/restxml - - private/protocol/xml/xmlutil - - service/route53 - - service/sts -- name: github.com/Azure/azure-sdk-for-go - version: 2629e2dfcfeab50896230140542c3b9d89b35ae1 - subpackages: - - arm/dns -- name: github.com/Azure/go-autorest - version: 58f6f26e200fa5dfb40c9cd1c83f3e2c860d779d - subpackages: - - autorest - - autorest/adal - - autorest/azure - - autorest/date - - autorest/to -- name: github.com/beorn7/perks - version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 - subpackages: - - quantile -- name: github.com/cloudflare/cloudflare-go - version: 4c6994ac3877fbb627766edadc67f4e816e8c890 -- name: github.com/coreos/go-oidc - version: be73733bb8cc830d0205609b95d125215f8e9c70 - subpackages: - - http - - jose - - key - - oauth2 - - oidc -- name: github.com/coreos/pkg - version: fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8 - subpackages: - - health - - httputil - - timeutil -- name: github.com/davecgh/go-spew - version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d - subpackages: - - spew -- name: github.com/dgrijalva/jwt-go - version: d2709f9f1f31ebcda9651b03077758c1f3a0018c -- name: github.com/digitalocean/godo - version: 77ea48de76a7b31b234d854f15d003c68bb2fb90 - subpackages: - - context -- name: github.com/dnsimple/dnsimple-go - version: d1105abc03b313d7b8d9b04364f6bd053b346e59 - subpackages: - - dnsimple -- name: github.com/docker/distribution - version: cd27f179f2c10c5d300e6d09025b538c475b0d51 - subpackages: - - digest - - reference -- name: github.com/emicklei/go-restful - version: 09691a3b6378b740595c1002f40c34dd5f218a22 - subpackages: - - log - - swagger -- name: github.com/ghodss/yaml - version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee -- name: github.com/go-ini/ini - version: 32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a -- name: github.com/go-openapi/jsonpointer - version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 -- name: github.com/go-openapi/jsonreference - version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 -- name: github.com/go-openapi/spec - version: 6aced65f8501fe1217321abf0749d354824ba2ff -- name: github.com/go-openapi/swag - version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 -- name: github.com/gogo/protobuf - version: e18d7aa8f8c624c915db340349aad4c49b10d173 - subpackages: - - proto - - sortkeys -- name: github.com/golang/glog - version: 44145f04b68cf362d9c4df2182967c2275eaefed -- name: github.com/golang/protobuf - version: 8616e8ee5e20a1704615e6c8d7afcdac06087a67 - subpackages: - - proto -- name: github.com/google/go-querystring - version: 53e6ce116135b80d037921a7fdd5138cf32d7a8a - subpackages: - - query -- name: github.com/google/gofuzz - version: 44d81051d367757e1c7c6a5a86423ece9afcf63c -- name: github.com/howeyc/gopass - version: 3ca23474a7c7203e0a0a070fd33508f6efdb9b3d -- name: github.com/imdario/mergo - version: 6633656539c1639d9d78127b7d47c622b5d7b6dc -- name: github.com/infobloxopen/infoblox-go-client - version: e2811d86bed7bb487eeb0806337b6f9e9d93d5e7 -- name: github.com/jmespath/go-jmespath - version: dd801d4f4ce7ac746e7e7b4489d2fa600b3b096b -- name: github.com/jonboulle/clockwork - version: 72f9bd7c4e0c2a40055ab3d0f09654f730cce982 -- name: github.com/juju/ratelimit - version: 77ed1c8a01217656d2080ad51981f6e99adaa177 -- name: github.com/kubernetes/repo-infra - version: 2d2eb5e12b4663fc4d764b5db9daab39334d3f37 -- name: github.com/linki/instrumented_http - version: 508103cfb3b315fa9752b5bcd4fb2d97bbc26d89 -- name: github.com/mailru/easyjson - version: d5b7844b561a7bc640052f1b935f7b800330d7e0 - subpackages: - - buffer - - jlexer - - jwriter -- name: github.com/matttproud/golang_protobuf_extensions - version: c12348ce28de40eed0136aa2b644d0ee0650e56c - subpackages: - - pbutil -- name: github.com/pkg/errors - version: f15c970de5b76fac0b59abb32d62c17cc7bed265 -- name: github.com/pmezard/go-difflib - version: d8ed2627bdf02c080bf22230dbb337003b7aba2d - subpackages: - - difflib -- name: github.com/prometheus/client_golang - version: c5b7fccd204277076155f10851dad72b76a49317 - subpackages: - - prometheus - - prometheus/promhttp -- name: github.com/prometheus/client_model - version: 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c - subpackages: - - go -- name: github.com/prometheus/common - version: 2e54d0b93cba2fd133edc32211dcc32c06ef72ca - subpackages: - - expfmt - - internal/bitbucket.org/ww/goautoneg - - model -- name: github.com/prometheus/procfs - version: a6e9df898b1336106c743392c48ee0b71f5c4efa - subpackages: - - xfs -- name: github.com/PuerkitoBio/purell - version: 8a290539e2e8629dbc4e6bad948158f790ec31f4 -- name: github.com/PuerkitoBio/urlesc - version: 5bd2802263f21d8788851d5305584c82a5c75d7e -- name: github.com/sirupsen/logrus - version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e -- name: github.com/spf13/pflag - version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 -- name: github.com/stretchr/objx - version: cbeaeb16a013161a98496fad62933b1d21786672 -- name: github.com/stretchr/testify - version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 - subpackages: - - assert - - mock - - require - - suite -- name: github.com/tent/http-link-go - version: ac974c61c2f990f4115b119354b5e0b47550e888 -- name: github.com/ugorji/go - version: ded73eae5db7e7a0ef6f55aace87a2873c5d2b74 - subpackages: - - codec -- name: golang.org/x/crypto - version: d172538b2cfce0c13cee31e647d0367aa8cd2486 - subpackages: - - ssh/terminal -- name: golang.org/x/net - version: e90d6d0afc4c315a0d87a568ae68577cc15149a0 - subpackages: - - context - - context/ctxhttp - - http2 - - http2/hpack - - idna - - lex/httplex -- name: golang.org/x/oauth2 - version: 3c3a985cb79f52a3190fbc056984415ca6763d01 - subpackages: - - google - - internal - - jws - - jwt -- name: golang.org/x/sys - version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9 - subpackages: - - unix -- name: golang.org/x/text - version: 2910a502d2bf9e43193af9d68ca516529614eed3 - subpackages: - - cases - - internal/tag - - language - - runes - - secure/bidirule - - secure/precis - - transform - - unicode/bidi - - unicode/norm - - width -- name: google.golang.org/api - version: a0ff90edab763c86aa88f2b1eb4f3301b82f6336 - subpackages: - - dns/v1 - - gensupport - - googleapi - - googleapi/internal/uritemplates -- name: google.golang.org/appengine - version: 4f7eeb5305a4ba1966344836ba4af9996b7b4e05 - subpackages: - - internal - - internal/app_identity - - internal/base - - internal/datastore - - internal/log - - internal/modules - - internal/remote_api - - internal/urlfetch - - urlfetch -- name: gopkg.in/inf.v0 - version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 -- name: gopkg.in/yaml.v2 - version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 -- name: k8s.io/apimachinery - version: 85ace5365f33b16fc735c866a12e3c765b9700f2 - subpackages: - - pkg/api/errors - - pkg/api/meta - - pkg/api/resource - - pkg/apimachinery - - pkg/apimachinery/announced - - pkg/apimachinery/registered - - pkg/apis/meta/v1 - - pkg/apis/meta/v1/unstructured - - pkg/conversion - - pkg/conversion/queryparams - - pkg/fields - - pkg/labels - - pkg/openapi - - pkg/runtime - - pkg/runtime/schema - - pkg/runtime/serializer - - pkg/runtime/serializer/json - - pkg/runtime/serializer/protobuf - - pkg/runtime/serializer/recognizer - - pkg/runtime/serializer/streaming - - pkg/runtime/serializer/versioning - - pkg/selection - - pkg/types - - pkg/util/errors - - pkg/util/framer - - pkg/util/intstr - - pkg/util/json - - pkg/util/net - - pkg/util/rand - - pkg/util/runtime - - pkg/util/sets - - pkg/util/validation - - pkg/util/validation/field - - pkg/util/wait - - pkg/util/yaml - - pkg/version - - pkg/watch - - third_party/forked/golang/reflect -- name: k8s.io/client-go - version: 21300e3e11c918b8e6a70fb7293b310683d6c046 - subpackages: - - discovery - - discovery/fake - - kubernetes - - kubernetes/fake - - kubernetes/scheme - - kubernetes/typed/apps/v1beta1 - - kubernetes/typed/apps/v1beta1/fake - - kubernetes/typed/authentication/v1 - - kubernetes/typed/authentication/v1/fake - - kubernetes/typed/authentication/v1beta1 - - kubernetes/typed/authentication/v1beta1/fake - - kubernetes/typed/authorization/v1 - - kubernetes/typed/authorization/v1/fake - - kubernetes/typed/authorization/v1beta1 - - kubernetes/typed/authorization/v1beta1/fake - - kubernetes/typed/autoscaling/v1 - - kubernetes/typed/autoscaling/v1/fake - - kubernetes/typed/autoscaling/v2alpha1 - - kubernetes/typed/autoscaling/v2alpha1/fake - - kubernetes/typed/batch/v1 - - kubernetes/typed/batch/v1/fake - - kubernetes/typed/batch/v2alpha1 - - kubernetes/typed/batch/v2alpha1/fake - - kubernetes/typed/certificates/v1beta1 - - kubernetes/typed/certificates/v1beta1/fake - - kubernetes/typed/core/v1 - - kubernetes/typed/core/v1/fake - - kubernetes/typed/extensions/v1beta1 - - kubernetes/typed/extensions/v1beta1/fake - - kubernetes/typed/policy/v1beta1 - - kubernetes/typed/policy/v1beta1/fake - - kubernetes/typed/rbac/v1alpha1 - - kubernetes/typed/rbac/v1alpha1/fake - - kubernetes/typed/rbac/v1beta1 - - kubernetes/typed/rbac/v1beta1/fake - - kubernetes/typed/settings/v1alpha1 - - kubernetes/typed/settings/v1alpha1/fake - - kubernetes/typed/storage/v1 - - kubernetes/typed/storage/v1/fake - - kubernetes/typed/storage/v1beta1 - - kubernetes/typed/storage/v1beta1/fake - - pkg/api - - pkg/api/install - - pkg/api/v1 - - pkg/apis/apps - - pkg/apis/apps/install - - pkg/apis/apps/v1beta1 - - pkg/apis/authentication - - pkg/apis/authentication/install - - pkg/apis/authentication/v1 - - pkg/apis/authentication/v1beta1 - - pkg/apis/authorization - - pkg/apis/authorization/install - - pkg/apis/authorization/v1 - - pkg/apis/authorization/v1beta1 - - pkg/apis/autoscaling - - pkg/apis/autoscaling/install - - pkg/apis/autoscaling/v1 - - pkg/apis/autoscaling/v2alpha1 - - pkg/apis/batch - - pkg/apis/batch/install - - pkg/apis/batch/v1 - - pkg/apis/batch/v2alpha1 - - pkg/apis/certificates - - pkg/apis/certificates/install - - pkg/apis/certificates/v1beta1 - - pkg/apis/extensions - - pkg/apis/extensions/install - - pkg/apis/extensions/v1beta1 - - pkg/apis/policy - - pkg/apis/policy/install - - pkg/apis/policy/v1beta1 - - pkg/apis/rbac - - pkg/apis/rbac/install - - pkg/apis/rbac/v1alpha1 - - pkg/apis/rbac/v1beta1 - - pkg/apis/settings - - pkg/apis/settings/install - - pkg/apis/settings/v1alpha1 - - pkg/apis/storage - - pkg/apis/storage/install - - pkg/apis/storage/v1 - - pkg/apis/storage/v1beta1 - - pkg/util - - pkg/util/parsers - - pkg/version - - plugin/pkg/client/auth - - plugin/pkg/client/auth/gcp - - plugin/pkg/client/auth/oidc - - rest - - rest/watch - - testing - - third_party/forked/golang/template - - tools/auth - - tools/clientcmd - - tools/clientcmd/api - - tools/clientcmd/api/latest - - tools/clientcmd/api/v1 - - tools/metrics - - transport - - util/cert - - util/clock - - util/flowcontrol - - util/homedir - - util/integer - - util/jsonpath -testImports: [] diff --git a/glide.yaml b/glide.yaml deleted file mode 100644 index 4a384fffdc..0000000000 --- a/glide.yaml +++ /dev/null @@ -1,61 +0,0 @@ -package: github.com/kubernetes-incubator/external-dns -import: -- package: github.com/sirupsen/logrus - version: ~1.0.3 -- package: github.com/alecthomas/kingpin - version: ~2.2.4 -- package: github.com/aws/aws-sdk-go - version: ~1.8.20 - subpackages: - - aws - - aws/session - - service/route53 -- package: github.com/prometheus/client_golang - version: ~0.8.0 - subpackages: - - prometheus/promhttp -- package: github.com/stretchr/testify - version: ~1.1.4 - subpackages: - - assert - - mock - - require -- package: golang.org/x/net - subpackages: - - context -- package: golang.org/x/oauth2 - subpackages: - - google -- package: google.golang.org/api - subpackages: - - dns/v1 - - googleapi -- package: k8s.io/apimachinery - subpackages: - - pkg/apis/meta/v1 -- package: k8s.io/client-go - version: ~3.0.0-beta.0 - subpackages: - - kubernetes - - pkg/api/v1 - - pkg/apis/extensions/v1beta1 - - tools/clientcmd -- package: github.com/kubernetes/repo-infra -- package: github.com/linki/instrumented_http - version: ~0.2.0 -- package: github.com/Azure/azure-sdk-for-go - version: ~10.0.4-beta -- package: github.com/Azure/go-autorest - version: ~8.0.0 -- package: github.com/dgrijalva/jwt-go - version: ~3.0.0 -- package: github.com/dnsimple/dnsimple-go - version: ~0.14.0 - subpackages: - - dnsimple -- package: github.com/cloudflare/cloudflare-go - version: ~0.7.3 -- package: github.com/digitalocean/godo - version: ~1.1.0 -- package: github.com/coreos/go-oidc -- package: github.com/infobloxopen/infoblox-go-client From 3f564f14ad7e7f420b008c0f178e8a6226b12195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Str=C3=B6m?= Date: Mon, 8 Jan 2018 13:50:37 +0100 Subject: [PATCH 02/10] Added TTL annotation check for azure records. --- provider/azure.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/provider/azure.go b/provider/azure.go index 67917dae49..79ebe40cf1 100644 --- a/provider/azure.go +++ b/provider/azure.go @@ -372,11 +372,15 @@ func (p *AzureProvider) recordSetNameForZone(zone string, endpoint *endpoint.End } func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet, error) { + var ttl int64 = azureRecordTTL + if endpoint.RecordTTL.IsConfigured() { + ttl = int64(endpoint.RecordTTL) + } switch dns.RecordType(endpoint.RecordType) { case dns.A: return dns.RecordSet{ RecordSetProperties: &dns.RecordSetProperties{ - TTL: to.Int64Ptr(azureRecordTTL), + TTL: to.Int64Ptr(ttl), ARecords: &[]dns.ARecord{ { Ipv4Address: to.StringPtr(endpoint.Target), @@ -387,7 +391,7 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet case dns.CNAME: return dns.RecordSet{ RecordSetProperties: &dns.RecordSetProperties{ - TTL: to.Int64Ptr(azureRecordTTL), + TTL: to.Int64Ptr(ttl), CnameRecord: &dns.CnameRecord{ Cname: to.StringPtr(endpoint.Target), }, @@ -396,7 +400,7 @@ func (p *AzureProvider) newRecordSet(endpoint *endpoint.Endpoint) (dns.RecordSet case dns.TXT: return dns.RecordSet{ RecordSetProperties: &dns.RecordSetProperties{ - TTL: to.Int64Ptr(azureRecordTTL), + TTL: to.Int64Ptr(ttl), TxtRecords: &[]dns.TxtRecord{ { Value: &[]string{ From f32f0777e864812e27e360aad18a8fe01ff9b983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Str=C3=B6m?= Date: Wed, 10 Jan 2018 15:49:36 +0100 Subject: [PATCH 03/10] Added TTL check when retrieving endpoints. Updated tests to include TTL checks. --- provider/azure.go | 7 +++- provider/azure_test.go | 84 ++++++++++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 33 deletions(-) diff --git a/provider/azure.go b/provider/azure.go index 79ebe40cf1..8815e7bd8b 100644 --- a/provider/azure.go +++ b/provider/azure.go @@ -153,7 +153,12 @@ func (p *AzureProvider) Records() (endpoints []*endpoint.Endpoint, _ error) { log.Errorf("Failed to extract target for '%s' with type '%s'.", name, recordType) return true } - ep := endpoint.NewEndpoint(name, target, recordType) + var ttl endpoint.TTL + if recordSet.TTL != nil { + ttl = endpoint.TTL(*recordSet.TTL) + } + + ep := endpoint.NewEndpointWithTTL(name, target, recordType, endpoint.TTL(ttl)) log.Debugf( "Found %s record for '%s' with target '%s'.", ep.RecordType, diff --git a/provider/azure_test.go b/provider/azure_test.go index 507dd4dd1e..2bed99636d 100644 --- a/provider/azure_test.go +++ b/provider/azure_test.go @@ -24,7 +24,9 @@ import ( "github.com/Azure/go-autorest/autorest/to" "github.com/kubernetes-incubator/external-dns/endpoint" + "github.com/kubernetes-incubator/external-dns/internal/testutils" "github.com/kubernetes-incubator/external-dns/plan" + "github.com/stretchr/testify/assert" ) type mockZonesClient struct { @@ -54,8 +56,9 @@ func (client *mockZonesClient) ListByResourceGroupNextResults(lastResults dns.Zo return dns.ZoneListResult{}, nil } -func aRecordSetPropertiesGetter(value string) *dns.RecordSetProperties { +func aRecordSetPropertiesGetter(value string, ttl int64) *dns.RecordSetProperties { return &dns.RecordSetProperties{ + TTL: to.Int64Ptr(ttl), ARecords: &[]dns.ARecord{ { Ipv4Address: to.StringPtr(value), @@ -64,16 +67,18 @@ func aRecordSetPropertiesGetter(value string) *dns.RecordSetProperties { } } -func cNameRecordSetPropertiesGetter(value string) *dns.RecordSetProperties { +func cNameRecordSetPropertiesGetter(value string, ttl int64) *dns.RecordSetProperties { return &dns.RecordSetProperties{ + TTL: to.Int64Ptr(ttl), CnameRecord: &dns.CnameRecord{ Cname: to.StringPtr(value), }, } } -func txtRecordSetPropertiesGetter(value string) *dns.RecordSetProperties { +func txtRecordSetPropertiesGetter(value string, ttl int64) *dns.RecordSetProperties { return &dns.RecordSetProperties{ + TTL: to.Int64Ptr(ttl), TxtRecords: &[]dns.TxtRecord{ { Value: &[]string{value}, @@ -82,12 +87,16 @@ func txtRecordSetPropertiesGetter(value string) *dns.RecordSetProperties { } } -func othersRecordSetPropertiesGetter(value string) *dns.RecordSetProperties { - return &dns.RecordSetProperties{} +func othersRecordSetPropertiesGetter(value string, ttl int64) *dns.RecordSetProperties { + return &dns.RecordSetProperties{ + TTL: to.Int64Ptr(ttl), + } } - func createMockRecordSet(name, recordType, value string) dns.RecordSet { - var getterFunc func(value string) *dns.RecordSetProperties + return createMockRecordSetWithTTL(name, recordType, value, 0) +} +func createMockRecordSetWithTTL(name, recordType, value string, ttl int64) dns.RecordSet { + var getterFunc func(value string, ttl int64) *dns.RecordSetProperties switch recordType { case endpoint.RecordTypeA: @@ -102,7 +111,7 @@ func createMockRecordSet(name, recordType, value string) dns.RecordSet { return dns.RecordSet{ Name: to.StringPtr(name), Type: to.StringPtr("Microsoft.Network/dnszones/" + recordType), - RecordSetProperties: getterFunc(value), + RecordSetProperties: getterFunc(value, ttl), } } @@ -128,12 +137,17 @@ func (client *mockRecordsClient) Delete(resourceGroupName string, zoneName strin } func (client *mockRecordsClient) CreateOrUpdate(resourceGroupName string, zoneName string, relativeRecordSetName string, recordType dns.RecordType, parameters dns.RecordSet, ifMatch string, ifNoneMatch string) (dns.RecordSet, error) { + var ttl endpoint.TTL + if parameters.TTL != nil { + ttl = endpoint.TTL(*parameters.TTL) + } client.updatedEndpoints = append( client.updatedEndpoints, - endpoint.NewEndpoint( + endpoint.NewEndpointWithTTL( formatAzureDNSName(relativeRecordSetName, zoneName), extractAzureTarget(¶meters), string(recordType), + ttl, ), ) return parameters, nil @@ -150,6 +164,10 @@ func newAzureProvider(domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, dryR } } +func validateAzureEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) { + assert.True(t, testutils.SameEndpoints(endpoints, expected), "expected and actual endpoints don't match. %s:%s", endpoints, expected) +} + func TestAzureRecord(t *testing.T) { zonesClient := mockZonesClient{ mockZoneListResult: &dns.ZoneListResult{ @@ -165,13 +183,14 @@ func TestAzureRecord(t *testing.T) { createMockRecordSet("@", "SOA", "Email: azuredns-hostmaster.microsoft.com"), createMockRecordSet("@", endpoint.RecordTypeA, "123.123.123.122"), createMockRecordSet("@", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), - createMockRecordSet("nginx", endpoint.RecordTypeA, "123.123.123.123"), - createMockRecordSet("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), - createMockRecordSet("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net"), + createMockRecordSetWithTTL("nginx", endpoint.RecordTypeA, "123.123.123.123", 3600), + createMockRecordSetWithTTL("nginx", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default", recordTTL), + createMockRecordSetWithTTL("hack", endpoint.RecordTypeCNAME, "hack.azurewebsites.net", 10), }, } provider := newAzureProvider(NewDomainFilter([]string{"example.com"}), NewZoneIDFilter([]string{""}), true, "k8s", &zonesClient, &recordsClient) + actual, err := provider.Records() if err != nil { @@ -180,12 +199,13 @@ func TestAzureRecord(t *testing.T) { expected := []*endpoint.Endpoint{ endpoint.NewEndpoint("example.com", "123.123.123.122", endpoint.RecordTypeA), endpoint.NewEndpoint("example.com", "heritage=external-dns,external-dns/owner=default", endpoint.RecordTypeTXT), - endpoint.NewEndpoint("nginx.example.com", "123.123.123.123", endpoint.RecordTypeA), - endpoint.NewEndpoint("nginx.example.com", "heritage=external-dns,external-dns/owner=default", endpoint.RecordTypeTXT), - endpoint.NewEndpoint("hack.example.com", "hack.azurewebsites.net", endpoint.RecordTypeCNAME), + endpoint.NewEndpointWithTTL("nginx.example.com", "123.123.123.123", endpoint.RecordTypeA, 3600), + endpoint.NewEndpointWithTTL("nginx.example.com", "heritage=external-dns,external-dns/owner=default", endpoint.RecordTypeTXT, recordTTL), + endpoint.NewEndpointWithTTL("hack.example.com", "hack.azurewebsites.net", endpoint.RecordTypeCNAME, 10), } - validateEndpoints(t, actual, expected) + validateAzureEndpoints(t, actual, expected) + } func TestAzureApplyChanges(t *testing.T) { @@ -193,24 +213,24 @@ func TestAzureApplyChanges(t *testing.T) { testAzureApplyChangesInternal(t, false, &recordsClient) - validateEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{ + validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{ endpoint.NewEndpoint("old.example.com", "", endpoint.RecordTypeA), endpoint.NewEndpoint("oldcname.example.com", "", endpoint.RecordTypeCNAME), endpoint.NewEndpoint("deleted.example.com", "", endpoint.RecordTypeA), endpoint.NewEndpoint("deletedcname.example.com", "", endpoint.RecordTypeCNAME), }) - validateEndpoints(t, recordsClient.updatedEndpoints, []*endpoint.Endpoint{ - endpoint.NewEndpoint("example.com", "1.2.3.4", endpoint.RecordTypeA), - endpoint.NewEndpoint("example.com", "tag", endpoint.RecordTypeTXT), - endpoint.NewEndpoint("foo.example.com", "1.2.3.4", endpoint.RecordTypeA), - endpoint.NewEndpoint("foo.example.com", "tag", endpoint.RecordTypeTXT), - endpoint.NewEndpoint("bar.example.com", "other.com", endpoint.RecordTypeCNAME), - endpoint.NewEndpoint("bar.example.com", "tag", endpoint.RecordTypeTXT), - endpoint.NewEndpoint("other.com", "5.6.7.8", endpoint.RecordTypeA), - endpoint.NewEndpoint("other.com", "tag", endpoint.RecordTypeTXT), - endpoint.NewEndpoint("new.example.com", "111.222.111.222", endpoint.RecordTypeA), - endpoint.NewEndpoint("newcname.example.com", "other.com", endpoint.RecordTypeCNAME), + validateAzureEndpoints(t, recordsClient.updatedEndpoints, []*endpoint.Endpoint{ + endpoint.NewEndpointWithTTL("example.com", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)), + endpoint.NewEndpointWithTTL("example.com", "tag", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL)), + endpoint.NewEndpointWithTTL("foo.example.com", "1.2.3.4", endpoint.RecordTypeA, endpoint.TTL(recordTTL)), + endpoint.NewEndpointWithTTL("foo.example.com", "tag", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL)), + endpoint.NewEndpointWithTTL("bar.example.com", "other.com", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL)), + endpoint.NewEndpointWithTTL("bar.example.com", "tag", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL)), + endpoint.NewEndpointWithTTL("other.com", "5.6.7.8", endpoint.RecordTypeA, endpoint.TTL(recordTTL)), + endpoint.NewEndpointWithTTL("other.com", "tag", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL)), + endpoint.NewEndpointWithTTL("new.example.com", "111.222.111.222", endpoint.RecordTypeA, 3600), + endpoint.NewEndpointWithTTL("newcname.example.com", "other.com", endpoint.RecordTypeCNAME, 10), }) } @@ -219,9 +239,9 @@ func TestAzureApplyChangesDryRun(t *testing.T) { testAzureApplyChangesInternal(t, true, &recordsClient) - validateEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{}) + validateAzureEndpoints(t, recordsClient.deletedEndpoints, []*endpoint.Endpoint{}) - validateEndpoints(t, recordsClient.updatedEndpoints, []*endpoint.Endpoint{}) + validateAzureEndpoints(t, recordsClient.updatedEndpoints, []*endpoint.Endpoint{}) } func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordsClient) { @@ -260,8 +280,8 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordsClie endpoint.NewEndpoint("old.nope.com", "121.212.121.212", endpoint.RecordTypeA), } updatedRecords := []*endpoint.Endpoint{ - endpoint.NewEndpoint("new.example.com", "111.222.111.222", endpoint.RecordTypeA), - endpoint.NewEndpoint("newcname.example.com", "other.com", endpoint.RecordTypeCNAME), + endpoint.NewEndpointWithTTL("new.example.com", "111.222.111.222", endpoint.RecordTypeA, 3600), + endpoint.NewEndpointWithTTL("newcname.example.com", "other.com", endpoint.RecordTypeCNAME, 10), endpoint.NewEndpoint("new.nope.com", "222.111.222.111", endpoint.RecordTypeA), } From a7ad512d0403c7cfc642b16415c51e32a3ae4551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Mathiasen?= Date: Fri, 26 Jan 2018 11:00:59 +0100 Subject: [PATCH 04/10] Add documentation about annotation-filter (#448) There were no documentation describing how to use the annotation-filter parameter --- docs/faq.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 2ae078f708..e334939cd2 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -200,3 +200,15 @@ spec: service ingress ``` + + +### Running an internal and external dns service + +Sometimes you need to run an internal and an external dns service. +The internal one should provision hostnames used on the internal network (perhaps inside a VPC), and the external +one to expose DNS to the internet. + +To do this with external-dns you can use the `--annotation-filter` to specifically tie an instance of external-dns to +an instance of a ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external` +then you can start two external-dns providers one with `--annotation-filter=kubernetes.io/ingress.class=nginx-internal` +and one with `--annotation-filter=kubernetes.io/ingress.class=nginx-external` \ No newline at end of file From 272e12e62a7310bd6d845d88018c1b20fe43ed1e Mon Sep 17 00:00:00 2001 From: shane lee Date: Fri, 26 Jan 2018 21:12:51 +1100 Subject: [PATCH 05/10] [aws-doc-update] docker image version and new arg aws-zone-type (#371) * [aws-doc-update] docker image version and new arg aws-zone-type * changes after review * remove annotation for ingress * docs: modify docs according to suggestions --- docs/tutorials/aws.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 8bba898cf6..9c0c037ccb 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -87,14 +87,49 @@ spec: - --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones - --provider=aws - --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization + - --aws-zone-type=public # only look at public hosted zones (valid values are public, private or no value for both) - --registry=txt - --txt-owner-id=my-identifier ``` -## Verify ExternalDNS works +## Arguments + +This list is not the full list, but a few arguments that where chosen. + +### aws-zone-type + +`aws-zone-type` allows filtering for private and public zones + + +## Verify ExternalDNS works (Ingress example) + +Create an ingress resource manifest file. + +> For ingress objects ExternalDNS will create a DNS record based on the host specified for the ingress object. + +```yaml +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: foo + annotations: + kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller. +spec: + rules: + - host: foo.bar.com + http: + paths: + - backend: + serviceName: foo + servicePort: 80 +``` + +## Verify ExternalDNS works (Service example) Create the following sample application to test that ExternalDNS works. +> For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value. + ```yaml apiVersion: v1 kind: Service From d06fb9da43ae3069219ffb22ffcaa3238e5dbd8e Mon Sep 17 00:00:00 2001 From: Martin Linkhorst Date: Fri, 26 Jan 2018 11:58:12 +0100 Subject: [PATCH 06/10] docs: add missing changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2675f5e28..8edea3e242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - If kubernetes resource is deleted, then another resource may acquire DNS name - "Flapping" target issue is resolved by providing a consistent and defined mechanism for choosing a target - New `--zone-id-filter` parameter allows filtering by zone id (#422) @vboginskey + - TTL annotation check for azure records (#436) @stromming + - Switch from glide to dep (#435) @bkochendorfer ## v0.4.8 - 2017-11-22 From bf93fd884485c11bf441840cb2023bafdf381998 Mon Sep 17 00:00:00 2001 From: Zach Arnold Date: Tue, 30 Jan 2018 01:40:49 -0800 Subject: [PATCH 07/10] add paris region (#452) * add paris region * adds test --- provider/aws.go | 1 + provider/aws_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/provider/aws.go b/provider/aws.go index 8733081c0e..f1f4545e4d 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -52,6 +52,7 @@ var ( "eu-central-1" + elbHostnameSuffix: "Z215JYRZR1TBD5", "eu-west-1" + elbHostnameSuffix: "Z32O12XQLNTSW2", "eu-west-2" + elbHostnameSuffix: "ZHURV8PSTC4K8", + "eu-west-3" + elbHostnameSuffix: "Z3Q77PNBQS71R4", "sa-east-1" + elbHostnameSuffix: "Z2P70J7HTTTPLU", } ) diff --git a/provider/aws_test.go b/provider/aws_test.go index 62a6f85e8d..67e444b410 100644 --- a/provider/aws_test.go +++ b/provider/aws_test.go @@ -710,6 +710,7 @@ func TestAWSCanonicalHostedZone(t *testing.T) { {"foo.eu-central-1.elb.amazonaws.com", "Z215JYRZR1TBD5"}, {"foo.eu-west-1.elb.amazonaws.com", "Z32O12XQLNTSW2"}, {"foo.eu-west-2.elb.amazonaws.com", "ZHURV8PSTC4K8"}, + {"foo.eu-west-3.elb.amazonaws.com", "Z3Q77PNBQS71R4"}, {"foo.sa-east-1.elb.amazonaws.com", "Z2P70J7HTTTPLU"}, {"foo.example.org", ""}, } { From 10b7ceac48d96422bc887f22dce611c95278e658 Mon Sep 17 00:00:00 2001 From: Tamal Saha Date: Thu, 28 Dec 2017 04:47:45 -0500 Subject: [PATCH 08/10] Avoid missing pod hostname for headless service --- source/service.go | 5 ++++- source/service_test.go | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/source/service.go b/source/service.go index ca837f44d5..55fc868e3f 100644 --- a/source/service.go +++ b/source/service.go @@ -134,7 +134,10 @@ func (sc *serviceSource) extractHeadlessEndpoint(svc *v1.Service, hostname strin } for _, v := range pods.Items { - headlessDomain := v.Spec.Hostname + "." + hostname + headlessDomain := hostname + if v.Spec.Hostname != "" { + headlessDomain = v.Spec.Hostname + "." + headlessDomain + } log.Debugf("Generating matching endpoint %s with HostIP %s", headlessDomain, v.Status.HostIP) // To reduce traffice on the DNS API only add record for running Pods. Good Idea? if v.Status.Phase == v1.PodRunning { diff --git a/source/service_test.go b/source/service_test.go index 22fd56059f..b84cdb6a00 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -960,6 +960,7 @@ func TestHeadlessServices(t *testing.T) { hostIP string selector map[string]string lbs []string + podnames []string hostnames []string phases []v1.PodPhase expected []*endpoint.Endpoint @@ -984,6 +985,7 @@ func TestHeadlessServices(t *testing.T) { }, []string{}, []string{"foo-0", "foo-1"}, + []string{"foo-0", "foo-1"}, []v1.PodPhase{v1.PodRunning, v1.PodRunning}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", Target: "1.1.1.1"}, @@ -1010,12 +1012,40 @@ func TestHeadlessServices(t *testing.T) { }, []string{}, []string{"foo-0", "foo-1"}, + []string{"foo-0", "foo-1"}, []v1.PodPhase{v1.PodRunning, v1.PodFailed}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", Target: "1.1.1.1"}, }, false, }, + { + "annotated Headless services return endpoints for pods missing hostname", + "", + "testing", + "foo", + v1.ServiceTypeClusterIP, + "", + "", + map[string]string{"component": "foo"}, + map[string]string{ + hostnameAnnotationKey: "service.example.org", + }, + v1.ClusterIPNone, + "1.1.1.1", + map[string]string{ + "component": "foo", + }, + []string{}, + []string{"foo-0", "foo-1"}, + []string{"", ""}, + []v1.PodPhase{v1.PodRunning, v1.PodRunning}, + []*endpoint.Endpoint{ + {DNSName: "service.example.org", Target: "1.1.1.1"}, + {DNSName: "service.example.org", Target: "1.1.1.1"}, + }, + false, + }, } { t.Run(tc.title, func(t *testing.T) { // Create a Kubernetes testing client @@ -1038,15 +1068,15 @@ func TestHeadlessServices(t *testing.T) { _, err := kubernetes.CoreV1().Services(service.Namespace).Create(service) require.NoError(t, err) - for i, hostname := range tc.hostnames { + for i, podname := range tc.podnames { pod := &v1.Pod{ Spec: v1.PodSpec{ Containers: []v1.Container{}, - Hostname: hostname, + Hostname: tc.hostnames[i], }, ObjectMeta: metav1.ObjectMeta{ Namespace: tc.svcNamespace, - Name: hostname, + Name: podname, Labels: tc.labels, Annotations: tc.annotations, }, From 414d394354230335a9a02669e1bdd1e29d976bab Mon Sep 17 00:00:00 2001 From: Julian Vassev Date: Sun, 4 Feb 2018 15:16:15 -0800 Subject: [PATCH 09/10] Add Dyn Provider * add "dyn" provider * add several --dyn-* args to configure Dyn login * add github.com/nesv/go-dynect/dynect@0.6.0 to Gopkg and vender/ (the client of choice by Terraform) * make externdns.Version public so it can be stored when committing zone changes * add tutorial for Ingress resources and update root README.md file Dyn REST API is documented here: https://help.dyn.com/dns-api-knowledge-base/ Example usage: external-dns \ --provider=dyn \ --dyn-customer-name=acme \ --dyn-username=acme-api \ --dyn-password=t0pS3cr3t \ --domain-filter=portal.acme.com \ --zone-id-filter=acme.com \ --namespace=my-test-ns \ --log-level=debug \ --txt-prefix=_ --- Gopkg.lock | 8 +- Gopkg.toml | 4 + Makefile | 2 +- README.md | 2 + docs/tutorials/dyn.md | 145 +++++ main.go | 12 + pkg/apis/externaldns/types.go | 13 +- provider/dyn.go | 564 ++++++++++++++++++ provider/dyn_test.go | 298 +++++++++ vendor/github.com/nesv/go-dynect/CHANGELOG.md | 102 ++++ vendor/github.com/nesv/go-dynect/LICENSE.md | 21 + vendor/github.com/nesv/go-dynect/README.md | 43 ++ .../nesv/go-dynect/dynect/client.go | 281 +++++++++ .../nesv/go-dynect/dynect/client_test.go | 123 ++++ .../go-dynect/dynect/convenient_client.go | 242 ++++++++ .../dynect/convenient_client_test.go | 241 ++++++++ .../github.com/nesv/go-dynect/dynect/dsfs.go | 117 ++++ .../dynect/fixtures/convenient_create_mx.yaml | 161 +++++ .../fixtures/convenient_create_zone.yaml | 134 +++++ .../fixtures/convenient_delete_sub_zone.yaml | 153 +++++ .../fixtures/convenient_delete_zone.yaml | 101 ++++ .../dynect/fixtures/convenient_get_a.yaml | 104 ++++ .../fixtures/convenient_get_a_not_found.yaml | 79 +++ .../dynect/fixtures/convenient_get_cname.yaml | 104 ++++ .../fixtures/fetching_all_zone_records.yaml | 269 +++++++++ .../dynect/fixtures/login_logout.yaml | 53 ++ .../dynect/fixtures/zones_request.yaml | 79 +++ .../nesv/go-dynect/dynect/helpers.go | 30 + .../github.com/nesv/go-dynect/dynect/job.go | 8 + .../github.com/nesv/go-dynect/dynect/json.go | 65 ++ .../nesv/go-dynect/dynect/record.go | 12 + .../nesv/go-dynect/dynect/records.go | 171 ++++++ .../github.com/nesv/go-dynect/dynect/zone.go | 9 + .../github.com/nesv/go-dynect/dynect/zones.go | 24 + 34 files changed, 3769 insertions(+), 5 deletions(-) create mode 100644 docs/tutorials/dyn.md create mode 100644 provider/dyn.go create mode 100644 provider/dyn_test.go create mode 100644 vendor/github.com/nesv/go-dynect/CHANGELOG.md create mode 100644 vendor/github.com/nesv/go-dynect/LICENSE.md create mode 100644 vendor/github.com/nesv/go-dynect/README.md create mode 100644 vendor/github.com/nesv/go-dynect/dynect/client.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/client_test.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/convenient_client.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/convenient_client_test.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/dsfs.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_create_mx.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_create_zone.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_delete_sub_zone.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_delete_zone.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_a.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_a_not_found.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_cname.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/fetching_all_zone_records.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/login_logout.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/fixtures/zones_request.yaml create mode 100644 vendor/github.com/nesv/go-dynect/dynect/helpers.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/job.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/json.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/record.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/records.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/zone.go create mode 100644 vendor/github.com/nesv/go-dynect/dynect/zones.go diff --git a/Gopkg.lock b/Gopkg.lock index 408c7f950f..3c8c1d7b27 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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 diff --git a/Gopkg.toml b/Gopkg.toml index ba07244cbc..eef8ea8bd1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -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" diff --git a/Makefile b/Makefile index 0a3ebaa3fd..e893623cea 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/README.md b/README.md index 29862893a4..87c1ea612a 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/docs/tutorials/dyn.md b/docs/tutorials/dyn.md new file mode 100644 index 0000000000..1bc8898f53 --- /dev/null +++ b/docs/tutorials/dyn.md @@ -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 < `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 diff --git a/provider/dyn.go b/provider/dyn.go new file mode 100644 index 0000000000..725e4b872b --- /dev/null +++ b/provider/dyn.go @@ -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 +} diff --git a/provider/dyn_test.go b/provider/dyn_test.go new file mode 100644 index 0000000000..6ae0e831ea --- /dev/null +++ b/provider/dyn_test.go @@ -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")) +} diff --git a/vendor/github.com/nesv/go-dynect/CHANGELOG.md b/vendor/github.com/nesv/go-dynect/CHANGELOG.md new file mode 100644 index 0000000000..d909754878 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/CHANGELOG.md @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/LICENSE.md b/vendor/github.com/nesv/go-dynect/LICENSE.md new file mode 100644 index 0000000000..9f66f66053 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/LICENSE.md @@ -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. diff --git a/vendor/github.com/nesv/go-dynect/README.md b/vendor/github.com/nesv/go-dynect/README.md new file mode 100644 index 0000000000..c7c0e48e56 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/README.md @@ -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! diff --git a/vendor/github.com/nesv/go-dynect/dynect/client.go b/vendor/github.com/nesv/go-dynect/dynect/client.go new file mode 100644 index 0000000000..51d534d89b --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/client.go @@ -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: +//
    +//
  • Auth-Token
  • +//
  • Content-Type
  • +//
+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)) +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/client_test.go b/vendor/github.com/nesv/go-dynect/dynect/client_test.go new file mode 100644 index 0000000000..8aafd46c80 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/client_test.go @@ -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") + } + }) +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/convenient_client.go b/vendor/github.com/nesv/go-dynect/dynect/convenient_client.go new file mode 100644 index 0000000000..e3f06fd50e --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/convenient_client.go @@ -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 +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/convenient_client_test.go b/vendor/github.com/nesv/go-dynect/dynect/convenient_client_test.go new file mode 100644 index 0000000000..9b29d0b272 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/convenient_client_test.go @@ -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") + }) +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/dsfs.go b/vendor/github.com/nesv/go-dynect/dynect/dsfs.go new file mode 100644 index 0000000000..e099f4112f --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/dsfs.go @@ -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"` +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_create_mx.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_create_mx.yaml new file mode 100644 index 0000000000..e47e9f1e37 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_create_mx.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_create_zone.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_create_zone.yaml new file mode 100644 index 0000000000..af6ce94ce0 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_create_zone.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_delete_sub_zone.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_delete_sub_zone.yaml new file mode 100644 index 0000000000..9687b72cba --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_delete_sub_zone.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_delete_zone.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_delete_zone.yaml new file mode 100644 index 0000000000..3dc96063c2 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_delete_zone.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_a.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_a.yaml new file mode 100644 index 0000000000..1a1a0ba312 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_a.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_a_not_found.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_a_not_found.yaml new file mode 100644 index 0000000000..a74a6b5757 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_a_not_found.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_cname.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_cname.yaml new file mode 100644 index 0000000000..a1eea9b582 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/convenient_get_cname.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/fetching_all_zone_records.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/fetching_all_zone_records.yaml new file mode 100644 index 0000000000..c2e8507dfd --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/fetching_all_zone_records.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/login_logout.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/login_logout.yaml new file mode 100644 index 0000000000..cd13281bfb --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/login_logout.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/fixtures/zones_request.yaml b/vendor/github.com/nesv/go-dynect/dynect/fixtures/zones_request.yaml new file mode 100644 index 0000000000..d7a461d492 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/fixtures/zones_request.yaml @@ -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 diff --git a/vendor/github.com/nesv/go-dynect/dynect/helpers.go b/vendor/github.com/nesv/go-dynect/dynect/helpers.go new file mode 100644 index 0000000000..060dddd596 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/helpers.go @@ -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 +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/job.go b/vendor/github.com/nesv/go-dynect/dynect/job.go new file mode 100644 index 0000000000..f502ef2a4c --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/job.go @@ -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"` +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/json.go b/vendor/github.com/nesv/go-dynect/dynect/json.go new file mode 100644 index 0000000000..26f4a037bb --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/json.go @@ -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"` +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/record.go b/vendor/github.com/nesv/go-dynect/dynect/record.go new file mode 100644 index 0000000000..f77f5abde4 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/record.go @@ -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 +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/records.go b/vendor/github.com/nesv/go-dynect/dynect/records.go new file mode 100644 index 0000000000..83a65b78ac --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/records.go @@ -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/ +// or https://api/dynect.net/REST/AllRecord///. +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"` +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/zone.go b/vendor/github.com/nesv/go-dynect/dynect/zone.go new file mode 100644 index 0000000000..70a9d8e21e --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/zone.go @@ -0,0 +1,9 @@ +package dynect + +// Zone struct to hold record details +type Zone struct { + Serial string + SerialStyle string + Zone string + Type string +} diff --git a/vendor/github.com/nesv/go-dynect/dynect/zones.go b/vendor/github.com/nesv/go-dynect/dynect/zones.go new file mode 100644 index 0000000000..0852c6cf00 --- /dev/null +++ b/vendor/github.com/nesv/go-dynect/dynect/zones.go @@ -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"` +} From 2d632f229d1f845c69c81e94f237a54dca64a013 Mon Sep 17 00:00:00 2001 From: Eike Herzbach Date: Fri, 9 Feb 2018 10:15:28 +0100 Subject: [PATCH 10/10] Typos --- docs/faq.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index e334939cd2..a14aca89ec 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -24,7 +24,7 @@ entrypoint frontend.example.org,backend.example.org 35.186.250.78 80 But there's nothing that actually makes clients resolve those hostnames to the Ingress' IP address. Again, you normally have to register each entry with your DNS provider. Only if you're lucky can you use a wildcard, like in the example above. -EnternalDNS can solve this for you as well. +ExternalDNS can solve this for you as well. ### Which DNS providers are supported? @@ -208,7 +208,7 @@ Sometimes you need to run an internal and an external dns service. The internal one should provision hostnames used on the internal network (perhaps inside a VPC), and the external one to expose DNS to the internet. -To do this with external-dns you can use the `--annotation-filter` to specifically tie an instance of external-dns to +To do this with ExternalDNS you can use the `--annotation-filter` to specifically tie an instance of ExternalDNS to an instance of a ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external` -then you can start two external-dns providers one with `--annotation-filter=kubernetes.io/ingress.class=nginx-internal` -and one with `--annotation-filter=kubernetes.io/ingress.class=nginx-external` \ No newline at end of file +then you can start two ExternalDNS providers one with `--annotation-filter=kubernetes.io/ingress.class=nginx-internal` +and one with `--annotation-filter=kubernetes.io/ingress.class=nginx-external`.