diff --git a/Gopkg.lock b/Gopkg.lock index 470fba59c..a8091b3ea 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,66 +2,53 @@ [[projects]] - digest = "1:ae9d0182a5cf7dbb025a8fc5821234cc1f26ca342fc41d951a99f71b9adc1b87" name = "cloud.google.com/go" packages = [ "compute/metadata", - "internal", + "internal" ] - pruneopts = "" revision = "3b1ae45394a234c385be014e9a488f2bb6eef821" [[projects]] - digest = "1:fd38e3b8c27cab6561a7d2e8557205c3ca5c57cbb6d3a79e10f22e73e84fd776" name = "github.com/Azure/azure-sdk-for-go" packages = ["arm/dns"] - pruneopts = "" revision = "2629e2dfcfeab50896230140542c3b9d89b35ae1" version = "v10.0.4-beta" [[projects]] - digest = "1:f719ef698feb8da2923ebda9a8d553b977320b02182f3797e185202e588a94b1" name = "github.com/Azure/go-autorest" packages = [ "autorest", "autorest/adal", "autorest/azure", "autorest/date", - "autorest/to", + "autorest/to" ] - pruneopts = "" revision = "aa2a4534ab680e938d933870f58f23f77e0e208e" version = "v10.9.0" [[projects]] - digest = "1:7dc69d1597e4773ec5f64e5c078d55f0f011bb05ec0435346d0649ad978a23fd" name = "github.com/alecthomas/kingpin" packages = ["."] - pruneopts = "" revision = "1087e65c9441605df944fb12c33f0fe7072d18ca" version = "v2.2.5" [[projects]] branch = "master" - digest = "1:a74730e052a45a3fab1d310fdef2ec17ae3d6af16228421e238320846f2aaec8" name = "github.com/alecthomas/template" packages = [ ".", - "parse", + "parse" ] - pruneopts = "" revision = "a0175ee3bccc567396460bf5acd36800cb10c49c" [[projects]] branch = "master" - digest = "1:8483994d21404c8a1d489f6be756e25bfccd3b45d65821f25695577791a08e68" name = "github.com/alecthomas/units" packages = ["."] - pruneopts = "" revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" [[projects]] - digest = "1:d2dc5d0ccc137594ea6fb3870964967b96b43daac19b8093930c7b02873fd5ca" name = "github.com/aliyun/alibaba-cloud-sdk-go" packages = [ "sdk", @@ -74,14 +61,12 @@ "sdk/responses", "sdk/utils", "services/alidns", - "services/pvtz", + "services/pvtz" ] - pruneopts = "" revision = "cad214d7d71fba7883fcf3b7e550ba782c15b400" version = "1.27.7" [[projects]] - digest = "1:1c82dd6a02941a3c4f23a32eca182717ab79691939e97d6b971466b780f06eea" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -113,18 +98,15 @@ "private/protocol/xml/xmlutil", "service/route53", "service/servicediscovery", - "service/sts", + "service/sts" ] - pruneopts = "" revision = "9b0098a71f6d4d473a26ec8ad3c2feaac6eb1da6" version = "v1.13.32" [[projects]] branch = "master" - digest = "1:0c5485088ce274fac2e931c1b979f2619345097b39d91af3239977114adf0320" name = "github.com/beorn7/perks" packages = ["quantile"] - pruneopts = "" revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" [[projects]] @@ -136,7 +118,6 @@ revision = "0c85496d873009e53e64d391ade643e7ac02e0d4" [[projects]] - digest = "1:eaeede87b418b97f9dee473f8940fd9b65ca5cdac0503350c7c8f8965ea3cf4d" name = "github.com/coreos/etcd" packages = [ "auth/authpb", @@ -144,108 +125,84 @@ "etcdserver/api/v3rpc/rpctypes", "etcdserver/etcdserverpb", "mvcc/mvccpb", - "pkg/types", + "pkg/types" ] - pruneopts = "" revision = "1b3ac99e8a431b381e633802cc42fe70e663baf5" version = "v3.2.15" [[projects]] - digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b" name = "github.com/davecgh/go-spew" packages = ["spew"] - pruneopts = "" revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" [[projects]] branch = "master" - digest = "1:dc166ce7345c060c2153561130d6d49ac580c804226ac675e368d533b36eb169" name = "github.com/denverdino/aliyungo" packages = [ "metadata", - "util", + "util" ] - pruneopts = "" revision = "69560d9530f5265ba00ffad2520d7ef01c2cddd4" [[projects]] - digest = "1:6098222470fe0172157ce9bbef5d2200df4edde17ee649c5d6e48330e4afa4c6" name = "github.com/dgrijalva/jwt-go" packages = ["."] - pruneopts = "" revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e" version = "v3.2.0" [[projects]] - digest = "1:32d1941b093bb945de75b0276348494be318d34f3df39c4413d61e002c800bc6" name = "github.com/digitalocean/godo" packages = [ ".", - "context", + "context" ] - pruneopts = "" revision = "77ea48de76a7b31b234d854f15d003c68bb2fb90" version = "v1.1.1" [[projects]] - digest = "1:5ffd39844bdd1259a6227d544f582c6686ce43c8c44399a46052fe3bd2bed93c" name = "github.com/dnsimple/dnsimple-go" packages = ["dnsimple"] - pruneopts = "" revision = "d1105abc03b313d7b8d9b04364f6bd053b346e59" version = "v0.14.0" [[projects]] - digest = "1:e17d18b233f506404061c27ac4a08624dad38dcd0d49f9cfdae67a7772a4fb8c" name = "github.com/exoscale/egoscale" packages = ["."] - pruneopts = "" revision = "c6d915cb993f1a54f604acefc0fc15cf6578a87a" version = "v0.11.0" [[projects]] branch = "master" - digest = "1:ae7fb2062735e966ab69d14d2a091f3778b0d676dc8d1f01d092bcb0fb8ed45b" name = "github.com/ffledgling/pdns-go" packages = ["."] - pruneopts = "" revision = "524e7daccd99651cdb56426eb15b7d61f9597a5c" [[projects]] - digest = "1:b13707423743d41665fd23f0c36b2f37bb49c30e94adb813319c44188a51ba22" name = "github.com/ghodss/yaml" packages = ["."] - pruneopts = "" revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7" version = "v1.0.0" [[projects]] - digest = "1:a00483fe4106b86fb1187a92b5cf6915c85f294ed4c129ccbe7cb1f1a06abd46" name = "github.com/go-ini/ini" packages = ["."] - pruneopts = "" revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a" version = "v1.32.0" [[projects]] - digest = "1:8e67153fc0a9fb0d6c9707e36cf80e217a012364307b222eb4ba6828f7e881e6" name = "github.com/go-resty/resty" packages = ["."] - pruneopts = "" revision = "97a15579492cd5f35632499f315d7a8df94160a1" version = "v1.8.0" [[projects]] - digest = "1:54d5c6a784a9de9c836fc070d51d0689c3e99ee6d24dba8a36f0762039dae830" name = "github.com/gogo/googleapis" packages = ["google/rpc"] - pruneopts = "" revision = "8558fb44d2f1fc223118afc694129d2c2d2924d1" version = "v1.1.0" [[projects]] - digest = "1:6e73003ecd35f4487a5e88270d3ca0a81bc80dc88053ac7e4dcfec5fba30d918" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -253,23 +210,19 @@ "proto", "protoc-gen-gogo/descriptor", "sortkeys", - "types", + "types" ] - pruneopts = "" revision = "636bf0302bc95575d69441b25a2603156ffdddf1" version = "v1.1.1" [[projects]] branch = "master" - digest = "1:b12aff239810a9fa71e901a712a52f9da4c6e536852e943be693dec1d4519dfd" name = "github.com/golang/glog" packages = ["."] - pruneopts = "" revision = "3fa5b9870d1d29f6d7907b29f1ae8c6eeb403829" source = "github.com/kubermatic/glog-logrus" [[projects]] - digest = "1:3dd078fda7500c341bc26cfbc6c6a34614f295a2457149fc1045cab767cbcf18" name = "github.com/golang/protobuf" packages = [ "jsonpb", @@ -279,50 +232,51 @@ "ptypes/any", "ptypes/duration", "ptypes/struct", - "ptypes/timestamp", + "ptypes/timestamp" ] - pruneopts = "" revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" version = "v1.2.0" [[projects]] branch = "master" - digest = "1:be28c0531a755f2178acf1e327e6f5a8a3968feb5f2567cdc968064253141751" name = "github.com/google/btree" packages = ["."] - pruneopts = "" revision = "e89373fe6b4a7413d7acd6da1725b83ef713e6e4" +[[projects]] + name = "github.com/google/go-cmp" + packages = [ + "cmp", + "cmp/internal/diff", + "cmp/internal/function", + "cmp/internal/value" + ] + revision = "3af367b6b30c263d47e8895973edcca9a49cf029" + version = "v0.2.0" + [[projects]] branch = "master" - digest = "1:9abc49f39e3e23e262594bb4fb70abf74c0c99e94f99153f43b143805e850719" name = "github.com/google/go-querystring" packages = ["query"] - pruneopts = "" revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a" [[projects]] - digest = "1:a2823c34933d4a2b36284f617f483d51fe156a443923284b3660f183dcfa3338" name = "github.com/google/gofuzz" packages = ["."] - pruneopts = "" revision = "44d81051d367757e1c7c6a5a86423ece9afcf63c" [[projects]] - digest = "1:16b2837c8b3cf045fa2cdc82af0cf78b19582701394484ae76b2c3bc3c99ad73" name = "github.com/googleapis/gnostic" packages = [ "OpenAPIv2", "compiler", - "extensions", + "extensions" ] - pruneopts = "" revision = "7c663266750e7d82587642f65e60bc4083f1f84e" version = "v0.2.0" [[projects]] branch = "master" - digest = "1:54a44d48a24a104e078ef5f94d82f025a6be757e7c42b4370c621a3928d6ab7c" name = "github.com/gophercloud/gophercloud" packages = [ ".", @@ -333,100 +287,77 @@ "openstack/identity/v2/tokens", "openstack/identity/v3/tokens", "openstack/utils", - "pagination", + "pagination" ] - pruneopts = "" revision = "bfc4756e1a693a850d7d459f4b28b21f35a24b5a" [[projects]] - digest = "1:dbbeb8ddb0be949954c8157ee8439c2adfd8dc1c9510eb44a6e58cb68c3dce28" name = "github.com/gorilla/context" packages = ["."] - pruneopts = "" revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42" version = "v1.1.1" [[projects]] - digest = "1:c2c8666b4836c81a1d247bdf21c6a6fc1ab586538ab56f74437c2e0df5c375e1" name = "github.com/gorilla/mux" packages = ["."] - pruneopts = "" revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf" version = "v1.6.2" [[projects]] branch = "master" - digest = "1:009a1928b8c096338b68b5822d838a72b4d8520715c1463614476359f3282ec8" name = "github.com/gregjones/httpcache" packages = [ ".", - "diskcache", + "diskcache" ] - pruneopts = "" revision = "9cad4c3443a7200dd6400aef47183728de563a38" [[projects]] - digest = "1:8e3bd93036b4a925fe2250d3e4f38f21cadb8ef623561cd80c3c50c114b13201" name = "github.com/hashicorp/errwrap" packages = ["."] - pruneopts = "" revision = "8a6fb523712970c966eefc6b39ed2c5e74880354" version = "v1.0.0" [[projects]] branch = "master" - digest = "1:72308fdd6d5ef61106a95be7ca72349a5565809042b6426a3cfb61d99483b824" name = "github.com/hashicorp/go-multierror" packages = ["."] - pruneopts = "" revision = "886a7fbe3eb1c874d46f623bfa70af45f425b3d1" [[projects]] - digest = "1:3313a63031ae281e5f6fd7b0bbca733dfa04d2429df86519e3b4d4c016ccb836" name = "github.com/hashicorp/golang-lru" packages = [ ".", - "simplelru", + "simplelru" ] - pruneopts = "" revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768" version = "v0.5.0" [[projects]] - digest = "1:af7e132906cb360f4d7c34a9e1434825467f21c4ff5c521ad4cc5b55352876a8" name = "github.com/imdario/mergo" packages = ["."] - pruneopts = "" revision = "6633656539c1639d9d78127b7d47c622b5d7b6dc" [[projects]] - digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" name = "github.com/inconshreveable/mousetrap" packages = ["."] - pruneopts = "" revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" [[projects]] branch = "master" - digest = "1:e0a13d0a368c028716e78448db972657b5292c7238d61405e8289f47c05c8706" name = "github.com/infobloxopen/infoblox-go-client" packages = ["."] - pruneopts = "" revision = "61dc5f9b0a655ebf43026f0d8a837ad1e28e4b96" [[projects]] - digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e" name = "github.com/jmespath/go-jmespath" packages = ["."] - pruneopts = "" revision = "0b12d6b5" [[projects]] - digest = "1:53ac4e911e12dde0ab68655e2006449d207a5a681f084974da2b06e5dbeaca72" name = "github.com/json-iterator/go" packages = ["."] - pruneopts = "" revision = "ab8a2e0c74be9d3be70b3184d9acc634935ded82" version = "1.1.4" @@ -439,239 +370,185 @@ revision = "1bcb110c8726cee477939f507f4760a95e155347" [[projects]] - digest = "1:7c23a751ce2f84663fa411acb87eae0da2d09c39a8e99b08bd8f65fae75d8928" name = "github.com/linki/instrumented_http" packages = ["."] - pruneopts = "" revision = "508103cfb3b315fa9752b5bcd4fb2d97bbc26d89" version = "v0.2.0" [[projects]] - digest = "1:1c41354ef11c9dbae2fe1ceee8369fcb2634977ba07e701e19ea53e8742c5420" name = "github.com/linode/linodego" packages = ["."] - pruneopts = "" revision = "7edbc87f0140b7561dbc20458877a56bdded5eb8" version = "v0.3.0" [[projects]] branch = "master" - digest = "1:63722a4b1e1717be7b98fc686e0b30d5e7f734b9e93d7dee86293b6deab7ea28" name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] - pruneopts = "" revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" [[projects]] - digest = "1:4c8d8358c45ba11ab7bb15df749d4df8664ff1582daead28bae58cf8cbe49890" name = "github.com/miekg/dns" packages = ["."] - pruneopts = "" revision = "5a2b9fab83ff0f8bfc99684bd5f43a37abe560f1" version = "v1.0.8" [[projects]] - digest = "1:0c0ff2a89c1bb0d01887e1dac043ad7efbf3ec77482ef058ac423d13497e16fd" name = "github.com/modern-go/concurrent" packages = ["."] - pruneopts = "" revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" version = "1.0.3" [[projects]] - digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855" name = "github.com/modern-go/reflect2" packages = ["."] - pruneopts = "" revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" version = "1.0.1" [[projects]] - digest = "1:11c58e19ff7ce22740423bb933f1ddca3bf575def40d5ac3437ec12871b1648b" name = "github.com/natefinch/lumberjack" packages = ["."] - pruneopts = "" revision = "a96e63847dc3c67d17befa69c303767e2f84e54f" version = "v2.1" [[projects]] - digest = "1:d8b5d0ecca348c835914a1ed8589f17a6a7f309befab7327b0470324531f7ac4" name = "github.com/nesv/go-dynect" packages = ["dynect"] - pruneopts = "" revision = "cdd946344b54bdf7dbeac406c2f1fe93150f08ea" version = "v0.6.0" [[projects]] - digest = "1:70df8e71a953626770223d4982801fa73e7e99cbfcca068b95127f72af9b9edd" name = "github.com/oracle/oci-go-sdk" packages = [ "common", - "dns", + "dns" ] - pruneopts = "" revision = "a2ded717dc4bb4916c0416ec79f81718b576dbc4" version = "v1.8.0" [[projects]] branch = "master" - digest = "1:c24598ffeadd2762552269271b3b1510df2d83ee6696c1e543a0ff653af494bc" name = "github.com/petar/GoLLRB" packages = ["llrb"] - pruneopts = "" revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4" [[projects]] - digest = "1:b46305723171710475f2dd37547edd57b67b9de9f2a6267cafdd98331fd6897f" name = "github.com/peterbourgon/diskv" packages = ["."] - pruneopts = "" revision = "5f041e8faa004a95c88a202771f4cc3e991971e6" version = "v2.0.1" [[projects]] - digest = "1:cf172c58bb2a13ed39ea1c9e79525567c63bcc2c4afbb6cf023e87b31780f249" name = "github.com/pkg/errors" packages = ["."] - pruneopts = "" revision = "f15c970de5b76fac0b59abb32d62c17cc7bed265" [[projects]] - digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" name = "github.com/pmezard/go-difflib" packages = ["difflib"] - pruneopts = "" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] - digest = "1:2f69dc6b2685b31a1a410ef697410aa8a669704fb201d45dbd8c1911728afa75" name = "github.com/prometheus/client_golang" packages = [ "prometheus", - "prometheus/promhttp", + "prometheus/promhttp" ] - pruneopts = "" revision = "967789050ba94deca04a5e84cce8ad472ce313c1" version = "v0.9.0-pre1" [[projects]] branch = "master" - digest = "1:60aca47f4eeeb972f1b9da7e7db51dee15ff6c59f7b401c1588b8e6771ba15ef" name = "github.com/prometheus/client_model" packages = ["go"] - pruneopts = "" revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" [[projects]] branch = "master" - digest = "1:e3aa5178be4fc4ae8cdb37d11c02f7490c00450a9f419e6aa84d02d3b47e90d2" name = "github.com/prometheus/common" packages = [ "expfmt", "internal/bitbucket.org/ww/goautoneg", - "model", + "model" ] - pruneopts = "" revision = "2e54d0b93cba2fd133edc32211dcc32c06ef72ca" [[projects]] - digest = "1:a6a85fc81f2a06ccac3d45005523afbeee45138d781d4f3cb7ad9889d5c65aab" name = "github.com/prometheus/procfs" packages = [ ".", - "xfs", + "xfs" ] - pruneopts = "" revision = "a6e9df898b1336106c743392c48ee0b71f5c4efa" [[projects]] branch = "master" - digest = "1:bb2ccb2d56cbafdec58af0f473f45304e19876f09fa671960ca87802b656a9c0" name = "github.com/sanyu/dynectsoap" packages = ["dynectsoap"] pruneopts = "" revision = "b83de5edc4e022f22903eeb3b428d2f39fb740e5" [[projects]] - digest = "1:7f569d906bdd20d906b606415b7d794f798f91a62fcfb6a4daa6d50690fb7a3f" name = "github.com/satori/go.uuid" packages = ["."] - pruneopts = "" revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3" version = "v1.2.0" [[projects]] - digest = "1:3ac248add5bb40a3c631c5334adcd09aa72d15af2768a5bc0274084ea7b2e5ba" name = "github.com/sirupsen/logrus" packages = ["."] - pruneopts = "" revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" version = "v1.0.3" [[projects]] - digest = "1:a1403cc8a94b8d7956ee5e9694badef0e7b051af289caad1cf668331e3ffa4f6" name = "github.com/spf13/cobra" packages = ["."] - pruneopts = "" revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" version = "v0.0.3" [[projects]] - digest = "1:0a52bcb568386d98f4894575d53ce3e456f56471de6897bb8b9de13c33d9340e" name = "github.com/spf13/pflag" packages = ["."] - pruneopts = "" revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" version = "v1.0.2" [[projects]] - digest = "1:306417ea2f31ea733df356a2b895de63776b6a5107085b33458e5cd6eb1d584d" name = "github.com/stretchr/objx" packages = ["."] - pruneopts = "" revision = "facf9a85c22f48d2f52f2380e4efce1768749a89" version = "v0.1" [[projects]] - digest = "1:a30066593578732a356dc7e5d7f78d69184ca65aeeff5939241a3ab10559bb06" name = "github.com/stretchr/testify" packages = [ "assert", "mock", "require", - "suite", + "suite" ] - pruneopts = "" revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" version = "v1.2.1" [[projects]] branch = "master" - digest = "1:81f435c83e3523a7ee3f277769727f73ca66218ca8188d96a0935a4841b47a76" name = "github.com/tent/http-link-go" packages = ["."] - pruneopts = "" revision = "ac974c61c2f990f4115b119354b5e0b47550e888" [[projects]] - digest = "1:74f86c458e82e1c4efbab95233e0cf51b7cc02dc03193be9f62cd81224e10401" name = "go.uber.org/atomic" packages = ["."] - pruneopts = "" revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289" version = "v1.3.2" [[projects]] - digest = "1:22c7effcb4da0eacb2bb1940ee173fac010e9ef3c691f5de4b524d538bd980f5" name = "go.uber.org/multierr" packages = ["."] - pruneopts = "" revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a" version = "v1.1.0" [[projects]] - digest = "1:246f378f80fba6fcf0f191c486b6613265abd2bc0f2fa55a36b928c67352021e" name = "go.uber.org/zap" packages = [ ".", @@ -680,26 +557,22 @@ "internal/color", "internal/exit", "zapcore", - "zapgrpc", + "zapgrpc" ] - pruneopts = "" revision = "ff33455a0e382e8a81d14dd7c922020b6b5e7982" version = "v1.9.1" [[projects]] branch = "master" - digest = "1:b2d8b39397ca07929a3de3a3fd2b6ca4c8d48e9cadaa7cf2b083e27fd9e78107" name = "golang.org/x/crypto" packages = [ "ed25519", "ed25519/internal/edwards25519", - "ssh/terminal", + "ssh/terminal" ] - pruneopts = "" revision = "0709b304e793a5edb4a2c0145f281ecdc20838a4" [[projects]] - digest = "1:782723d6fc27d202f1943219d68d58b3f6bcab6212c85294b1ddd8b586b1d356" name = "golang.org/x/net" packages = [ "bpf", @@ -715,36 +588,30 @@ "ipv4", "ipv6", "publicsuffix", - "trace", + "trace" ] - pruneopts = "" revision = "161cd47e91fd58ac17490ef4d742dc98bb4cf60e" [[projects]] - digest = "1:dad5a319c4710358be1f2bf68f9fb7f90a71d7c641221b79801d5667b28f19e3" name = "golang.org/x/oauth2" packages = [ ".", "google", "internal", "jws", - "jwt", + "jwt" ] - pruneopts = "" revision = "3c3a985cb79f52a3190fbc056984415ca6763d01" [[projects]] - digest = "1:39d88a855976e160babdd254802e1c2ae75ed380328c39742b57928852da6207" name = "golang.org/x/sys" packages = [ "unix", - "windows", + "windows" ] - pruneopts = "" revision = "13d03a9a82fba647c21a0ef8fba44a795d0f0835" [[projects]] - digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4" name = "golang.org/x/text" packages = [ "collate", @@ -760,34 +627,28 @@ "unicode/bidi", "unicode/cldr", "unicode/norm", - "unicode/rangetable", + "unicode/rangetable" ] - pruneopts = "" revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" [[projects]] branch = "master" - digest = "1:55a681cb66f28755765fa5fa5104cbd8dc85c55c02d206f9f89566451e3fe1aa" name = "golang.org/x/time" packages = ["rate"] - pruneopts = "" revision = "fbb02b2291d28baffd63558aa44b4b56f178d650" [[projects]] - digest = "1:2ad38d79865e33dde6157b7048debd6e7d47e0709df7b5e11bb888168e316908" name = "google.golang.org/api" packages = [ "dns/v1", "gensupport", "googleapi", - "googleapi/internal/uritemplates", + "googleapi/internal/uritemplates" ] - pruneopts = "" revision = "a0ff90edab763c86aa88f2b1eb4f3301b82f6336" [[projects]] - digest = "1:41e2b7e287117f6136f75286d48072ecf781ba54823ffeb2098e152e7dc45ef6" name = "google.golang.org/appengine" packages = [ ".", @@ -799,24 +660,20 @@ "internal/modules", "internal/remote_api", "internal/urlfetch", - "urlfetch", + "urlfetch" ] - pruneopts = "" revision = "4f7eeb5305a4ba1966344836ba4af9996b7b4e05" [[projects]] branch = "master" - digest = "1:e43f1cb3f488a0c2be85939c2a594636f60b442a12a196c778bd2d6c9aca3df7" name = "google.golang.org/genproto" packages = [ "googleapis/api/annotations", - "googleapis/rpc/status", + "googleapis/rpc/status" ] - pruneopts = "" revision = "11092d34479b07829b72e10713b159248caf5dad" [[projects]] - digest = "1:ca75b3775a5d4e5d1fb48f57ef0865b4aaa8b3f00e6b52be68db991c4594e0a7" name = "google.golang.org/grpc" packages = [ ".", @@ -845,31 +702,25 @@ "resolver/passthrough", "stats", "status", - "tap", + "tap" ] - pruneopts = "" revision = "32fb0ac620c32ba40a4626ddf94d90d12cce3455" version = "v1.14.0" [[projects]] - digest = "1:e5d1fb981765b6f7513f793a3fcaac7158408cca77f75f7311ac82cc88e9c445" name = "gopkg.in/inf.v0" packages = ["."] - pruneopts = "" revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4" version = "v0.9.0" [[projects]] - digest = "1:f0620375dd1f6251d9973b5f2596228cc8042e887cd7f827e4220bc1ce8c30e2" name = "gopkg.in/yaml.v2" packages = ["."] - pruneopts = "" revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" version = "v2.2.1" [[projects]] branch = "release-1.0" - digest = "1:bc43af6616d8ca12a7b8e806874387f0f1e181c08f547e9cd77f6cbac8cefd86" name = "istio.io/api" packages = [ "authentication/v1alpha1", @@ -877,13 +728,11 @@ "mixer/v1", "mixer/v1/config/client", "networking/v1alpha3", - "rbac/v1alpha1", + "rbac/v1alpha1" ] - pruneopts = "" revision = "76349c53b87f03f1e610b3aa3843dba3c38138d7" [[projects]] - digest = "1:7eb25280e1f610470bb0c43ab6c91573cfc78672a58542106b9b71705581429a" name = "istio.io/istio" packages = [ "pilot/pkg/config/kube/crd", @@ -892,14 +741,12 @@ "pilot/pkg/serviceregistry/kube", "pkg/cache", "pkg/kube", - "pkg/log", + "pkg/log" ] - pruneopts = "" revision = "42773aacced474d97159902d20579a25b1f98106" version = "1.0.1" [[projects]] - digest = "1:f420c8548c93242d8e5dcfa5b34e0243883b4e660f65076e869daafac877144d" name = "k8s.io/api" packages = [ "admissionregistration/v1alpha1", @@ -930,28 +777,24 @@ "settings/v1alpha1", "storage/v1", "storage/v1alpha1", - "storage/v1beta1", + "storage/v1beta1" ] - pruneopts = "" revision = "072894a440bdee3a891dea811fe42902311cd2a3" version = "kubernetes-1.11.0" [[projects]] - digest = "1:66d1421ecff35bc48ee0b11a3f891f3af6f775ed6bb1d3e0deeaba221bf42490" name = "k8s.io/apiextensions-apiserver" packages = [ "pkg/apis/apiextensions", "pkg/apis/apiextensions/v1beta1", "pkg/client/clientset/clientset", "pkg/client/clientset/clientset/scheme", - "pkg/client/clientset/clientset/typed/apiextensions/v1beta1", + "pkg/client/clientset/clientset/typed/apiextensions/v1beta1" ] - pruneopts = "" revision = "8e7f43002fec5394a8d96ebca781aa9d4b37aaef" version = "kubernetes-1.10.4" [[projects]] - digest = "1:b6b2fb7b4da1ac973b64534ace2299a02504f16bc7820cb48edb8ca4077183e1" name = "k8s.io/apimachinery" packages = [ "pkg/api/errors", @@ -994,14 +837,12 @@ "pkg/version", "pkg/watch", "third_party/forked/golang/json", - "third_party/forked/golang/reflect", + "third_party/forked/golang/reflect" ] - pruneopts = "" revision = "103fd098999dc9c0c88536f5c9ad2e5da39373ae" version = "kubernetes-1.11.0" [[projects]] - digest = "1:d04779a8de7d5465e0463bd986506348de5e89677c74777f695d3145a7a8d15e" name = "k8s.io/client-go" packages = [ "discovery", @@ -1099,18 +940,15 @@ "util/homedir", "util/integer", "util/jsonpath", - "util/retry", + "util/retry" ] - pruneopts = "" revision = "7d04d0e2a0a1a4d4a1cd6baa432a2301492e4e65" version = "v8.0.0" [[projects]] branch = "master" - digest = "1:526095379da1098c3f191a0008cc59c9bf9927492e63da7689e5de424219c162" name = "k8s.io/kube-openapi" packages = ["pkg/util/proto"] - pruneopts = "" revision = "d8ea2fe547a448256204cfc68dfee7b26c720acb" [solve-meta] diff --git a/Gopkg.toml b/Gopkg.toml index 6590be2e0..f2d17cb9d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -116,6 +116,10 @@ ignored = ["github.com/kubernetes/repo-infra/kazel"] name = "github.com/miekg/dns" version = "1.0.8" +[[constraint]] + name = "github.com/google/go-cmp" + version = "0.2.0" + [[constraint]] name = "github.com/sanyu/dynectsoap" branch = "master" diff --git a/SECURITY_CONTACTS b/SECURITY_CONTACTS index 16ccfb1fe..0811c743f 100644 --- a/SECURITY_CONTACTS +++ b/SECURITY_CONTACTS @@ -10,7 +10,6 @@ # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE # INSTRUCTIONS AT https://kubernetes.io/security/ -linki njuettner hjacobs raffo diff --git a/docs/contributing/crd-source.md b/docs/contributing/crd-source.md index 06fbea80a..ca43503d3 100644 --- a/docs/contributing/crd-source.md +++ b/docs/contributing/crd-source.md @@ -14,7 +14,11 @@ Here is typical example of [CRD API type](https://github.com/kubernetes-incubato ```go type TTL int64 type Targets []string -type ProviderSpecific map[string]string +type ProviderSpecificProperty struct { + Name string + Value string +} +type ProviderSpecific []ProviderSpecificProperty type Endpoint struct { // The hostname of the DNS record diff --git a/docs/contributing/crd-source/crd-manifest.yaml b/docs/contributing/crd-source/crd-manifest.yaml index 258404575..00b52f34c 100644 --- a/docs/contributing/crd-source/crd-manifest.yaml +++ b/docs/contributing/crd-source/crd-manifest.yaml @@ -33,7 +33,14 @@ spec: labels: type: object providerSpecific: - type: object + items: + properties: + name: + type: string + value: + type: string + type: object + type: array recordTTL: format: int64 type: integer diff --git a/docs/faq.md b/docs/faq.md index 447cf561d..bcd2240f0 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -243,3 +243,11 @@ To do this with ExternalDNS you can use the `--annotation-filter` to specificall an instance of a ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external` 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`. + +### Can external-dns manage(add/remove) records in a hosted zone which is setup in different aws account. + +yes, give it the correct cross-account/assume-role permissions and use the `--aws-assume-role` flag https://github.com/kubernetes-incubator/external-dns/pull/524#issue-181256561 + +### how do I provide multiple values to the annotation `external-dns.alpha.kubernetes.io/hostname` + +separate them by `,` diff --git a/docs/tutorials/aws.md b/docs/tutorials/aws.md index 5ab4050de..7056de801 100644 --- a/docs/tutorials/aws.md +++ b/docs/tutorials/aws.md @@ -47,14 +47,14 @@ $ aws route53 create-hosted-zone --name "external-dns-test.my-org.com." --caller Make a note of the ID of the hosted zone you just created. ```console -$ aws route53 list-hosted-zones-by-name --dns-name "external-dns-test.my-org.com." | jq -r '.HostedZones[0].Id' +$ aws route53 list-hosted-zones-by-name --output json --dns-name "external-dns-test.my-org.com." | jq -r '.HostedZones[0].Id' /hostedzone/ZEWFWZ4R16P7IB ``` Make a note of the nameservers that were assigned to your new zone. ```console -$ aws route53 list-resource-record-sets --hosted-zone-id "/hostedzone/ZEWFWZ4R16P7IB" \ +$ aws route53 list-resource-record-sets --output json --hosted-zone-id "/hostedzone/ZEWFWZ4R16P7IB" \ --query "ResourceRecordSets[?Type == 'NS']" | jq -r '.[0].ResourceRecords[].Value' ns-5514.awsdns-53.org. ... @@ -177,7 +177,7 @@ Annotations which are specific to AWS. ### alias -`external-dns.alpha.kubernetes.io/alias` if set to `true` on an ingress, it will create an ALIAS record when the target is an ALIAS as well. +`external-dns.alpha.kubernetes.io/alias` if set to `true` on an ingress, it will create an ALIAS record when the target is an ALIAS as well. To make the target an alias, the ingress needs to be configured correctly as described in [the docs](./nginx-ingress.md#with-a-separate-tcp-load-balancer). ## Verify ExternalDNS works (Ingress example) @@ -247,7 +247,7 @@ spec: After roughly two minutes check that a corresponding DNS record for your service was created. ```console -$ aws route53 list-resource-record-sets --hosted-zone-id "/hostedzone/ZEWFWZ4R16P7IB" \ +$ aws route53 list-resource-record-sets --output json --hosted-zone-id "/hostedzone/ZEWFWZ4R16P7IB" \ --query "ResourceRecordSets[?Name == 'nginx.external-dns-test.my-org.com.']|[?Type == 'A']" [ { diff --git a/docs/tutorials/azure.md b/docs/tutorials/azure.md index cdd45e7f6..51f6d77fc 100644 --- a/docs/tutorials/azure.md +++ b/docs/tutorials/azure.md @@ -61,13 +61,18 @@ The `resourceGroup` is the Resource Group created in a previous step. The `aadClientID` and `aaClientSecret` are assoiated with the Service Principal, that you need to create next. ### Creating service principal -A Service Principal with a minimum access level of contribute to the resource group containing the Azure DNS zone(s) is necessary for ExternalDNS to be able to edit DNS records. This is an Azure CLI example on how to query the Azure API for the information required for the Resource Group and DNS zone you would have already created in previous steps. +A Service Principal with a minimum access level of `contributor` to the DNS zone(s) and `reader` to the resource group containing the Azure DNS zone(s) is necessary for ExternalDNS to be able to edit DNS records. However, other more permissive access levels will work too (e.g. `contributor` to the resource group or the whole subscription). +This is an Azure CLI example on how to query the Azure API for the information required for the Resource Group and DNS zone you would have already created in previous steps. + +``` bash +> az login ``` ->az login -... -# find the relevant subscription and set the az context. id = subscriptionId value in the azure.json. ->az account list + +Find the relevant subscription and make sure it is selected (the same subscriptionId should be set into azure.json) + +``` bash +> az account list { "cloudName": "AzureCloud", "id": "", @@ -79,16 +84,15 @@ A Service Principal with a minimum access level of contribute to the resource gr "name": "name", "type": "user" } ->az account set -s id -... ->az group show --name externaldns -{ - "id": "/subscriptions/id/resourceGroups/externaldns", - ... -} -# use the id from the previous step in the scopes argument ->az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/id/resourceGroups/externaldns" -n ExternalDnsServicePrincipal +# select the subscription +> az account set -s +... +``` +Create the service principal + +``` bash +> az ad sp create-for-rbac -n ExternalDnsServicePrincipal { "appId": "appId GUID", <-- aadClientId value ... @@ -97,6 +101,33 @@ A Service Principal with a minimum access level of contribute to the resource gr } ``` +Assign the rights for the service principal + +``` +# find out the resource ids of the resource group where the dns zone is deployed, and the dns zone itself +> az group show --name externaldns +{ + "id": "/subscriptions/id/resourceGroups/externaldns", + ... +} + +> az network dns zone show --name example.com -g externaldns +{ + "id": "/subscriptions/.../resourceGroups/externaldns/providers/Microsoft.Network/dnszones/example.com", + ... +} +``` +``` +# assign the rights to the created service principal, using the resource ids from previous step + +# 1. as a reader to the resource group +> az role assignment create --role "Reader" --assignee --scope + +# 2. as a contributor to DNS Zone itself +> az role assignment create --role "Contributor" --assignee --scope + +``` + Now you can create a file named 'azure.json' with values gathered above and with the structure of the example above. Use this file to create a Kubernetes secret: ``` diff --git a/docs/tutorials/cloudflare.md b/docs/tutorials/cloudflare.md index 1e654a56a..e4c191690 100644 --- a/docs/tutorials/cloudflare.md +++ b/docs/tutorials/cloudflare.md @@ -196,3 +196,7 @@ Now that we have verified that ExternalDNS will automatically manage Cloudflare $ kubectl delete -f nginx.yaml $ kubectl delete -f externaldns.yaml ``` + +## Setting cloudflare-proxied on a per-ingress basis + +Using the `external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"` annotation on your ingress, you can specify if the proxy feature of Cloudflare should be enabled for that record. This setting will override the global `--cloudflare-proxied` setting. diff --git a/endpoint/endpoint.go b/endpoint/endpoint.go index ca77f36e2..2d292fb0d 100644 --- a/endpoint/endpoint.go +++ b/endpoint/endpoint.go @@ -109,8 +109,14 @@ func (t Targets) IsLess(o Targets) bool { return false } +// ProviderSpecificProperty holds the name and value of a configuration which is specific to individual DNS providers +type ProviderSpecificProperty struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + // ProviderSpecific holds configuration which is specific to individual DNS providers -type ProviderSpecific map[string]string +type ProviderSpecific []ProviderSpecificProperty // Endpoint is a high-level way of a connection between a service and an IP type Endpoint struct { @@ -160,10 +166,21 @@ func (e *Endpoint) WithProviderSpecific(key, value string) *Endpoint { if e.ProviderSpecific == nil { e.ProviderSpecific = ProviderSpecific{} } - e.ProviderSpecific[key] = value + + e.ProviderSpecific = append(e.ProviderSpecific, ProviderSpecificProperty{Name: key, Value: value}) return e } +// GetProviderSpecificProperty returns a ProviderSpecificProperty if the property exists. +func (e *Endpoint) GetProviderSpecificProperty(key string) (ProviderSpecificProperty, bool) { + for _, providerSpecific := range e.ProviderSpecific { + if providerSpecific.Name == key { + return providerSpecific, true + } + } + return ProviderSpecificProperty{}, false +} + func (e *Endpoint) String() string { return fmt.Sprintf("%s %d IN %s %s %s", e.DNSName, e.RecordTTL, e.RecordType, e.Targets, e.ProviderSpecific) } diff --git a/internal/testutils/endpoint.go b/internal/testutils/endpoint.go index f13a6b4c7..d804c75ea 100644 --- a/internal/testutils/endpoint.go +++ b/internal/testutils/endpoint.go @@ -17,6 +17,7 @@ limitations under the License. package testutils import ( + "reflect" "sort" "github.com/kubernetes-incubator/external-dns/endpoint" @@ -49,7 +50,7 @@ func SameEndpoint(a, b *endpoint.Endpoint) bool { return a.DNSName == b.DNSName && a.Targets.Same(b.Targets) && a.RecordType == b.RecordType && a.Labels[endpoint.OwnerLabelKey] == b.Labels[endpoint.OwnerLabelKey] && a.RecordTTL == b.RecordTTL && a.Labels[endpoint.ResourceLabelKey] == b.Labels[endpoint.ResourceLabelKey] && - SameMap(a.ProviderSpecific, b.ProviderSpecific) + SameProverSpecific(a.ProviderSpecific, b.ProviderSpecific) } // SameEndpoints compares two slices of endpoints regardless of order @@ -81,17 +82,7 @@ func SamePlanChanges(a, b map[string][]*endpoint.Endpoint) bool { SameEndpoints(a["UpdateOld"], b["UpdateOld"]) && SameEndpoints(a["UpdateNew"], b["UpdateNew"]) } -// SameMap verifies that two maps contain the same string/string key/value pairs -func SameMap(a, b map[string]string) bool { - if len(a) != len(b) { - return false - } - - for k, v := range a { - if v != b[k] { - return false - } - } - - return true +// SameProverSpecific verifies that two maps contain the same string/string key/value pairs +func SameProverSpecific(a, b endpoint.ProviderSpecific) bool { + return reflect.DeepEqual(a, b) } diff --git a/internal/testutils/endpoint_test.go b/internal/testutils/endpoint_test.go index f14ae655c..2f1204fd6 100644 --- a/internal/testutils/endpoint_test.go +++ b/internal/testutils/endpoint_test.go @@ -56,9 +56,11 @@ func ExampleSameEndpoints() { RecordTTL: endpoint.TTL(60), }, { - DNSName: "example.org", - Targets: endpoint.Targets{"load-balancer.org"}, - ProviderSpecific: endpoint.ProviderSpecific{"foo": "bar"}, + DNSName: "example.org", + Targets: endpoint.Targets{"load-balancer.org"}, + ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{Name: "foo", Value: "bar"}, + }, }, } sort.Sort(byAllFields(eps)) @@ -66,11 +68,11 @@ func ExampleSameEndpoints() { fmt.Println(ep) } // Output: - // abc.com 0 IN A 1.2.3.4 map[] - // abc.com 0 IN TXT something map[] - // bbc.com 0 IN CNAME foo.com map[] - // cbc.com 60 IN CNAME foo.com map[] - // example.org 0 IN load-balancer.org map[] - // example.org 0 IN load-balancer.org map[foo:bar] - // example.org 0 IN TXT load-balancer.org map[] + // abc.com 0 IN A 1.2.3.4 [] + // abc.com 0 IN TXT something [] + // bbc.com 0 IN CNAME foo.com [] + // cbc.com 60 IN CNAME foo.com [] + // example.org 0 IN load-balancer.org [] + // example.org 0 IN load-balancer.org [{foo bar}] + // example.org 0 IN TXT load-balancer.org [] } diff --git a/main.go b/main.go index 7c95bf790..547bdc0a5 100644 --- a/main.go +++ b/main.go @@ -116,6 +116,7 @@ func main() { BatchChangeInterval: cfg.AWSBatchChangeInterval, EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth, AssumeRole: cfg.AWSAssumeRole, + APIRetries: cfg.AWSAPIRetries, DryRun: cfg.DryRun, }, ) diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 6d079e054..a7b82b224 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -61,6 +61,7 @@ type Config struct { AWSBatchChangeSize int AWSBatchChangeInterval time.Duration AWSEvaluateTargetHealth bool + AWSAPIRetries int AzureConfigFile string AzureResourceGroup string CloudflareProxied bool @@ -134,6 +135,7 @@ var defaultConfig = &Config{ AWSBatchChangeSize: 1000, AWSBatchChangeInterval: time.Second, AWSEvaluateTargetHealth: true, + AWSAPIRetries: 3, AzureConfigFile: "/etc/kubernetes/azure.json", AzureResourceGroup: "", CloudflareProxied: false, @@ -250,6 +252,7 @@ func (cfg *Config) ParseFlags(args []string) error { app.Flag("aws-batch-change-size", "When using the AWS provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.AWSBatchChangeSize)).IntVar(&cfg.AWSBatchChangeSize) app.Flag("aws-batch-change-interval", "When using the AWS provider, set the interval between batch changes.").Default(defaultConfig.AWSBatchChangeInterval.String()).DurationVar(&cfg.AWSBatchChangeInterval) app.Flag("aws-evaluate-target-health", "When using the AWS provider, set whether to evaluate the health of a DNS target (default: enabled, disable with --no-aws-evaluate-target-health)").Default(strconv.FormatBool(defaultConfig.AWSEvaluateTargetHealth)).BoolVar(&cfg.AWSEvaluateTargetHealth) + app.Flag("aws-api-retries", "When using the AWS provider, set the maximum number of retries for API calls before giving up.").Default(strconv.Itoa(defaultConfig.AWSAPIRetries)).IntVar(&cfg.AWSAPIRetries) app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile) app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup) app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index 0d5f6f30e..143ddb75b 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -48,6 +48,7 @@ var ( AWSBatchChangeSize: 1000, AWSBatchChangeInterval: time.Second, AWSEvaluateTargetHealth: true, + AWSAPIRetries: 3, AzureConfigFile: "/etc/kubernetes/azure.json", AzureResourceGroup: "", CloudflareProxied: false, @@ -101,6 +102,7 @@ var ( AWSBatchChangeSize: 100, AWSBatchChangeInterval: time.Second * 2, AWSEvaluateTargetHealth: false, + AWSAPIRetries: 13, AzureConfigFile: "azure.json", AzureResourceGroup: "arg", CloudflareProxied: true, @@ -198,6 +200,7 @@ func TestParseFlags(t *testing.T) { "--aws-assume-role=some-other-role", "--aws-batch-change-size=100", "--aws-batch-change-interval=2s", + "--aws-api-retries=13", "--no-aws-evaluate-target-health", "--policy=upsert-only", "--registry=noop", @@ -260,6 +263,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_AWS_BATCH_CHANGE_SIZE": "100", "EXTERNAL_DNS_AWS_BATCH_CHANGE_INTERVAL": "2s", "EXTERNAL_DNS_AWS_EVALUATE_TARGET_HEALTH": "0", + "EXTERNAL_DNS_AWS_API_RETRIES": "13", "EXTERNAL_DNS_POLICY": "upsert-only", "EXTERNAL_DNS_REGISTRY": "noop", "EXTERNAL_DNS_TXT_OWNER_ID": "owner-1", diff --git a/plan/plan.go b/plan/plan.go index cdbdf1837..9f575451c 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -20,6 +20,7 @@ import ( "fmt" "strings" + "github.com/google/go-cmp/cmp" "github.com/kubernetes-incubator/external-dns/endpoint" ) @@ -84,7 +85,7 @@ func (t planTableRow) String() string { } func (t planTable) addCurrent(e *endpoint.Endpoint) { - dnsName := sanitizeDNSName(e.DNSName) + dnsName := normalizeDNSName(e.DNSName) if _, ok := t.rows[dnsName]; !ok { t.rows[dnsName] = &planTableRow{} } @@ -92,7 +93,7 @@ func (t planTable) addCurrent(e *endpoint.Endpoint) { } func (t planTable) addCandidate(e *endpoint.Endpoint) { - dnsName := sanitizeDNSName(e.DNSName) + dnsName := normalizeDNSName(e.DNSName) if _, ok := t.rows[dnsName]; !ok { t.rows[dnsName] = &planTableRow{} } @@ -105,7 +106,7 @@ func (t planTable) getUpdates() (updateNew []*endpoint.Endpoint, updateOld []*en if row.current != nil && len(row.candidates) > 0 { //dns name is taken update := t.resolver.ResolveUpdate(row.current, row.candidates) // compare "update" to "current" to figure out if actual update is required - if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) { + if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || shouldUpdateProviderSpecific(update, row.current) { inheritOwner(row.current, update) updateNew = append(updateNew, update) updateOld = append(updateOld, row.current) @@ -185,6 +186,10 @@ func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool { return desired.RecordTTL != current.RecordTTL } +func shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint) bool { + return !cmp.Equal(desired.ProviderSpecific, current.ProviderSpecific) +} + // filterRecordsForPlan removes records that are not relevant to the planner. // Currently this just removes TXT records to prevent them from being // deleted erroneously by the planner (only the TXT registry should do this.) @@ -209,8 +214,12 @@ func filterRecordsForPlan(records []*endpoint.Endpoint) []*endpoint.Endpoint { return filtered } -// sanitizeDNSName checks if the DNS name is correct -// for now it only removes space and lower case -func sanitizeDNSName(dnsName string) string { - return strings.TrimSpace(strings.ToLower(dnsName)) +// normalizeDNSName converts a DNS name to a canonical form, so that we can use string equality +// it: removes space, converts to lower case, ensures there is a trailing dot +func normalizeDNSName(dnsName string) string { + s := strings.TrimSpace(strings.ToLower(dnsName)) + if !strings.HasSuffix(s, ".") { + s += "." + } + return s } diff --git a/plan/plan_test.go b/plan/plan_test.go index e36b343a6..eb742e11a 100644 --- a/plan/plan_test.go +++ b/plan/plan_test.go @@ -27,15 +27,17 @@ import ( type PlanTestSuite struct { suite.Suite - fooV1Cname *endpoint.Endpoint - fooV2Cname *endpoint.Endpoint - fooV2TXT *endpoint.Endpoint - fooV2CnameNoLabel *endpoint.Endpoint - fooV3CnameSameResource *endpoint.Endpoint - fooA5 *endpoint.Endpoint - bar127A *endpoint.Endpoint - bar127AWithTTL *endpoint.Endpoint - bar192A *endpoint.Endpoint + fooV1Cname *endpoint.Endpoint + fooV2Cname *endpoint.Endpoint + fooV2TXT *endpoint.Endpoint + fooV2CnameNoLabel *endpoint.Endpoint + fooV3CnameSameResource *endpoint.Endpoint + fooA5 *endpoint.Endpoint + bar127A *endpoint.Endpoint + bar127AWithTTL *endpoint.Endpoint + bar127AWithProviderSpecificTrue *endpoint.Endpoint + bar127AWithProviderSpecificFalse *endpoint.Endpoint + bar192A *endpoint.Endpoint } func (suite *PlanTestSuite) SetupTest() { @@ -100,6 +102,34 @@ func (suite *PlanTestSuite) SetupTest() { endpoint.ResourceLabelKey: "ingress/default/bar-127", }, } + suite.bar127AWithProviderSpecificTrue = &endpoint.Endpoint{ + DNSName: "bar", + Targets: endpoint.Targets{"127.0.0.1"}, + RecordType: "A", + Labels: map[string]string{ + endpoint.ResourceLabelKey: "ingress/default/bar-127", + }, + ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Value: "true", + }, + }, + } + suite.bar127AWithProviderSpecificFalse = &endpoint.Endpoint{ + DNSName: "bar", + Targets: endpoint.Targets{"127.0.0.1"}, + RecordType: "A", + Labels: map[string]string{ + endpoint.ResourceLabelKey: "ingress/default/bar-127", + }, + ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Value: "false", + }, + }, + } suite.bar192A = &endpoint.Endpoint{ DNSName: "bar", Targets: endpoint.Targets{"192.168.0.1"}, @@ -108,6 +138,7 @@ func (suite *PlanTestSuite) SetupTest() { endpoint.ResourceLabelKey: "ingress/default/bar-192", }, } + } func (suite *PlanTestSuite) TestSyncFirstRound() { @@ -194,6 +225,27 @@ func (suite *PlanTestSuite) TestSyncSecondRoundWithTTLChange() { validateEntries(suite.T(), changes.Delete, expectedDelete) } +func (suite *PlanTestSuite) TestSyncSecondRoundWithProviderSpecificChange() { + current := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue} + desired := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse} + expectedCreate := []*endpoint.Endpoint{} + expectedUpdateOld := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificTrue} + expectedUpdateNew := []*endpoint.Endpoint{suite.bar127AWithProviderSpecificFalse} + expectedDelete := []*endpoint.Endpoint{} + + p := &Plan{ + Policies: []Policy{&SyncPolicy{}}, + Current: current, + Desired: desired, + } + + changes := p.Calculate().Changes + validateEntries(suite.T(), changes.Create, expectedCreate) + validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew) + validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld) + validateEntries(suite.T(), changes.Delete, expectedDelete) +} + func (suite *PlanTestSuite) TestSyncSecondRoundWithOwnerInherited() { current := []*endpoint.Endpoint{suite.fooV1Cname} desired := []*endpoint.Endpoint{suite.fooV2Cname} @@ -354,6 +406,7 @@ func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceReplace() { //TODO: remove once multiple-target per endpoint is supported func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceRetain() { + current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A} desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV3CnameSameResource} expectedCreate := []*endpoint.Endpoint{} @@ -385,54 +438,58 @@ func validateEntries(t *testing.T, entries, expected []*endpoint.Endpoint) { } } -func TestSanitizeDNSName(t *testing.T) { +func TestNormalizeDNSName(t *testing.T) { records := []struct { dnsName string expect string }{ { "3AAAA.FOO.BAR.COM ", - "3aaaa.foo.bar.com", + "3aaaa.foo.bar.com.", }, { - " example.foo.com", - "example.foo.com", + " example.foo.com.", + "example.foo.com.", }, { "example123.foo.com ", - "example123.foo.com", + "example123.foo.com.", }, { "foo", - "foo", + "foo.", }, { "123foo.bar", - "123foo.bar", + "123foo.bar.", }, { "foo.com", - "foo.com", + "foo.com.", + }, + { + "foo.com.", + "foo.com.", }, { "foo123.COM", - "foo123.com", + "foo123.com.", }, { "my-exaMple3.FOO.BAR.COM", - "my-example3.foo.bar.com", + "my-example3.foo.bar.com.", }, { " my-example1214.FOO-1235.BAR-foo.COM ", - "my-example1214.foo-1235.bar-foo.com", + "my-example1214.foo-1235.bar-foo.com.", }, { "my-example-my-example-1214.FOO-1235.BAR-foo.COM", - "my-example-my-example-1214.foo-1235.bar-foo.com", + "my-example-my-example-1214.foo-1235.bar-foo.com.", }, } for _, r := range records { - gotName := sanitizeDNSName(r.dnsName) + gotName := normalizeDNSName(r.dnsName) assert.Equal(t, r.expect, gotName) } } diff --git a/provider/aws.go b/provider/aws.go index fb328cbd2..36b9ab772 100644 --- a/provider/aws.go +++ b/provider/aws.go @@ -117,12 +117,13 @@ type AWSConfig struct { BatchChangeInterval time.Duration EvaluateTargetHealth bool AssumeRole string + APIRetries int DryRun bool } // NewAWSProvider initializes a new AWS Route53 based Provider. func NewAWSProvider(awsConfig AWSConfig) (*AWSProvider, error) { - config := aws.NewConfig() + config := aws.NewConfig().WithMaxRetries(awsConfig.APIRetries) config.WithHTTPClient( instrumented_http.NewClient(config.HTTPClient, &instrumented_http.Callbacks{ @@ -394,8 +395,8 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou if isAWSLoadBalancer(endpoint) { evalTargetHealth := p.evaluateTargetHealth - if _, ok := endpoint.ProviderSpecific[providerSpecificEvaluateTargetHealth]; ok { - evalTargetHealth = endpoint.ProviderSpecific[providerSpecificEvaluateTargetHealth] == "true" + if prop, ok := endpoint.GetProviderSpecificProperty(providerSpecificEvaluateTargetHealth); ok { + evalTargetHealth = prop.Value == "true" } change.ResourceRecordSet.Type = aws.String(route53.RRTypeA) @@ -587,7 +588,7 @@ func isAWSLoadBalancer(ep *endpoint.Endpoint) bool { // isAWSAlias determines if a given hostname belongs to an AWS Alias record by doing an reverse lookup. func isAWSAlias(ep *endpoint.Endpoint, addrs []*endpoint.Endpoint) string { - if val, exists := ep.ProviderSpecific["alias"]; ep.RecordType == endpoint.RecordTypeCNAME && exists && val == "true" { + if prop, exists := ep.GetProviderSpecificProperty("alias"); ep.RecordType == endpoint.RecordTypeCNAME && exists && prop.Value == "true" { for _, addr := range addrs { if addr.DNSName == ep.Targets[0] { if hostedZone := canonicalHostedZone(addr.Targets[0]); hostedZone != "" { diff --git a/provider/aws_test.go b/provider/aws_test.go index 8ffbc51ba..f4ca41f6e 100644 --- a/provider/aws_test.go +++ b/provider/aws_test.go @@ -799,7 +799,10 @@ func TestAWSCreateRecordsWithALIAS(t *testing.T) { Targets: endpoint.Targets{"foo.eu-central-1.elb.amazonaws.com"}, RecordType: endpoint.RecordTypeCNAME, ProviderSpecific: endpoint.ProviderSpecific{ - providerSpecificEvaluateTargetHealth: key, + endpoint.ProviderSpecificProperty{ + Name: providerSpecificEvaluateTargetHealth, + Value: key, + }, }, }, } @@ -850,9 +853,14 @@ func TestAWSisAWSAlias(t *testing.T) { {"foo.example.org", endpoint.RecordTypeCNAME, "true", ""}, } { ep := &endpoint.Endpoint{ - Targets: endpoint.Targets{tc.target}, - RecordType: tc.recordType, - ProviderSpecific: map[string]string{"alias": tc.alias}, + Targets: endpoint.Targets{tc.target}, + RecordType: tc.recordType, + ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "alias", + Value: tc.alias, + }, + }, } addrs := []*endpoint.Endpoint{ { diff --git a/provider/cloudflare_test.go b/provider/cloudflare_test.go index 68c4d8853..dbad258de 100644 --- a/provider/cloudflare_test.go +++ b/provider/cloudflare_test.go @@ -410,6 +410,36 @@ func TestNewCloudFlareChangeNoProxied(t *testing.T) { assert.False(t, change.ResourceRecordSet.Proxied) } +func TestNewCloudFlareProxiedAnnotationTrue(t *testing.T) { + change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}, ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Value: "true", + }, + }}, false) + assert.True(t, change.ResourceRecordSet.Proxied) +} + +func TestNewCloudFlareProxiedAnnotationFalse(t *testing.T) { + change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}, ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Value: "false", + }, + }}, true) + assert.False(t, change.ResourceRecordSet.Proxied) +} + +func TestNewCloudFlareProxiedAnnotationIllegalValue(t *testing.T) { + change := newCloudFlareChange(cloudFlareCreate, &endpoint.Endpoint{DNSName: "new", RecordType: "A", Targets: endpoint.Targets{"target"}, ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied", + Value: "asdaslkjndaslkdjals", + }, + }}, false) + assert.False(t, change.ResourceRecordSet.Proxied) +} + func TestNewCloudFlareChangeProxiable(t *testing.T) { var cloudFlareTypes = []struct { recordType string diff --git a/source/crd.go b/source/crd.go index 79b308ad4..4c5a2a698 100644 --- a/source/crd.go +++ b/source/crd.go @@ -118,6 +118,11 @@ func (cs *crdSource) Endpoints() ([]*endpoint.Endpoint, error) { for _, dnsEndpoint := range result.Items { endpoints = append(endpoints, dnsEndpoint.Spec.Endpoints...) + + if dnsEndpoint.Status.ObservedGeneration == dnsEndpoint.Generation { + continue + } + dnsEndpoint.Status.ObservedGeneration = dnsEndpoint.Generation // Update the ObservedGeneration _, err = cs.UpdateStatus(&dnsEndpoint) diff --git a/source/crd_test.go b/source/crd_test.go index a069f2114..928a35685 100644 --- a/source/crd_test.go +++ b/source/crd_test.go @@ -18,6 +18,7 @@ package source import ( "bytes" + "encoding/json" "fmt" "io" "io/ioutil" @@ -54,20 +55,21 @@ func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) } -func startCRDServerToServeTargets(endpoints []*endpoint.Endpoint, apiVersion, kind, namespace, name string) rest.Interface { +func startCRDServerToServeTargets(endpoints []*endpoint.Endpoint, apiVersion, kind, namespace, name string, t *testing.T) rest.Interface { groupVersion, _ := schema.ParseGroupVersion(apiVersion) scheme := runtime.NewScheme() addKnownTypes(scheme, groupVersion) dnsEndpointList := endpoint.DNSEndpointList{} - dnsEndpoint := endpoint.DNSEndpoint{ + dnsEndpoint := &endpoint.DNSEndpoint{ TypeMeta: metav1.TypeMeta{ APIVersion: apiVersion, Kind: kind, }, ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, + Name: name, + Namespace: namespace, + Generation: 1, }, Spec: endpoint.DNSEndpointSpec{ Endpoints: endpoints, @@ -88,10 +90,18 @@ func startCRDServerToServeTargets(endpoints []*endpoint.Endpoint, apiVersion, ki case p == "/apis/"+apiVersion+"/"+strings.ToLower(kind)+"s" && m == http.MethodGet: fallthrough case p == "/apis/"+apiVersion+"/namespaces/"+namespace+"/"+strings.ToLower(kind)+"s" && m == http.MethodGet: - dnsEndpointList.Items = append(dnsEndpointList.Items, dnsEndpoint) + dnsEndpointList.Items = dnsEndpointList.Items[:0] + dnsEndpointList.Items = append(dnsEndpointList.Items, *dnsEndpoint) return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &dnsEndpointList)}, nil case strings.HasPrefix(p, "/apis/"+apiVersion+"/namespaces/") && strings.HasSuffix(p, strings.ToLower(kind)+"s") && m == http.MethodGet: return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &dnsEndpointList)}, nil + case p == "/apis/"+apiVersion+"/namespaces/"+namespace+"/"+strings.ToLower(kind)+"s/"+name+"/status" && m == http.MethodPut: + decoder := json.NewDecoder(req.Body) + + var body endpoint.DNSEndpoint + decoder.Decode(&body) + dnsEndpoint.Status.ObservedGeneration = body.Status.ObservedGeneration + return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, dnsEndpoint)}, nil default: return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req) } @@ -200,6 +210,8 @@ func testCRDSourceEndpoints(t *testing.T) { apiVersion: "test.k8s.io/v1alpha1", registeredKind: "DNSEndpoint", kind: "DNSEndpoint", + namespace: "foo", + registeredNamespace: "foo", endpoints: []*endpoint.Endpoint{ {DNSName: "abc.example.org", Targets: endpoint.Targets{"1.2.3.4"}, @@ -216,6 +228,8 @@ func testCRDSourceEndpoints(t *testing.T) { apiVersion: "test.k8s.io/v1alpha1", registeredKind: "DNSEndpoint", kind: "DNSEndpoint", + namespace: "foo", + registeredNamespace: "foo", endpoints: []*endpoint.Endpoint{ {DNSName: "abc.example.org", Targets: endpoint.Targets{"1.2.3.4"}, @@ -233,7 +247,7 @@ func testCRDSourceEndpoints(t *testing.T) { }, } { t.Run(ti.title, func(t *testing.T) { - restClient := startCRDServerToServeTargets(ti.endpoints, ti.registeredAPIVersion, ti.registeredKind, ti.registeredNamespace, "") + restClient := startCRDServerToServeTargets(ti.endpoints, ti.registeredAPIVersion, ti.registeredKind, ti.registeredNamespace, "test", t) groupVersion, err := schema.ParseGroupVersion(ti.apiVersion) require.NoError(t, err) @@ -253,8 +267,28 @@ func testCRDSourceEndpoints(t *testing.T) { return } + if err == nil { + validateCRDResource(t, cs, ti.expectError) + } + // Validate received endpoints against expected endpoints. validateEndpoints(t, receivedEndpoints, ti.endpoints) }) } } + +func validateCRDResource(t *testing.T, src Source, expectError bool) { + cs := src.(*crdSource) + result, err := cs.List(&metav1.ListOptions{}) + if expectError { + require.Errorf(t, err, "Received err %v", err) + } else { + require.NoErrorf(t, err, "Received err %v", err) + } + + for _, dnsEndpoint := range result.Items { + if dnsEndpoint.Status.ObservedGeneration != dnsEndpoint.Generation { + require.Errorf(t, err, "Unexpected CRD resource result: ObservedGenerations <%v> is not equal to Generation<%v>", dnsEndpoint.Status.ObservedGeneration, dnsEndpoint.Generation) + } + } +} diff --git a/source/ingress.go b/source/ingress.go index cbda3cd92..056b8dd6f 100644 --- a/source/ingress.go +++ b/source/ingress.go @@ -232,7 +232,6 @@ func endpointsFromIngress(ing *v1beta1.Ingress) []*endpoint.Endpoint { for _, hostname := range hostnameList { endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...) } - return endpoints } diff --git a/source/service.go b/source/service.go index 28cb1b9cd..e88e31d8e 100644 --- a/source/service.go +++ b/source/service.go @@ -171,6 +171,7 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri return endpoints } + targetsByHeadlessDomain := make(map[string][]string) for _, v := range pods.Items { headlessDomain := hostname if v.Spec.Hostname != "" { @@ -181,11 +182,7 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri 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 { - if ttl.IsConfigured() { - endpoints = append(endpoints, endpoint.NewEndpointWithTTL(headlessDomain, endpoint.RecordTypeA, ttl, v.Status.HostIP)) - } else { - endpoints = append(endpoints, endpoint.NewEndpoint(headlessDomain, endpoint.RecordTypeA, v.Status.HostIP)) - } + targetsByHeadlessDomain[headlessDomain] = append(targetsByHeadlessDomain[headlessDomain], v.Status.HostIP) } else { log.Debugf("Pod %s is not in running phase", v.Spec.Hostname) } @@ -193,11 +190,7 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri log.Debugf("Generating matching endpoint %s with PodIP %s", headlessDomain, v.Status.PodIP) // To reduce traffice on the DNS API only add record for running Pods. Good Idea? if v.Status.Phase == v1.PodRunning { - if ttl.IsConfigured() { - endpoints = append(endpoints, endpoint.NewEndpointWithTTL(headlessDomain, endpoint.RecordTypeA, ttl, v.Status.PodIP)) - } else { - endpoints = append(endpoints, endpoint.NewEndpoint(headlessDomain, endpoint.RecordTypeA, v.Status.PodIP)) - } + targetsByHeadlessDomain[headlessDomain] = append(targetsByHeadlessDomain[headlessDomain], v.Status.PodIP) } else { log.Debugf("Pod %s is not in running phase", v.Spec.Hostname) } @@ -205,6 +198,20 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri } + headlessDomains := []string{} + for headlessDomain := range targetsByHeadlessDomain { + headlessDomains = append(headlessDomains, headlessDomain) + } + sort.Strings(headlessDomains) + for _, headlessDomain := range headlessDomains { + targets := targetsByHeadlessDomain[headlessDomain] + if ttl.IsConfigured() { + endpoints = append(endpoints, endpoint.NewEndpointWithTTL(headlessDomain, endpoint.RecordTypeA, ttl, targets...)) + } else { + endpoints = append(endpoints, endpoint.NewEndpoint(headlessDomain, endpoint.RecordTypeA, targets...)) + } + } + return endpoints } @@ -218,9 +225,10 @@ func (sc *serviceSource) endpointsFromTemplate(svc *v1.Service, nodeTargets endp return nil, fmt.Errorf("failed to apply template on service %s: %v", svc.String(), err) } + providerSpecific := getProviderSpecificAnnotations(svc.Annotations) hostnameList := strings.Split(strings.Replace(buf.String(), " ", "", -1), ",") for _, hostname := range hostnameList { - endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, nodeTargets)...) + endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, nodeTargets, providerSpecific)...) } return endpoints, nil @@ -230,9 +238,10 @@ func (sc *serviceSource) endpointsFromTemplate(svc *v1.Service, nodeTargets endp func (sc *serviceSource) endpoints(svc *v1.Service, nodeTargets endpoint.Targets) []*endpoint.Endpoint { var endpoints []*endpoint.Endpoint + providerSpecific := getProviderSpecificAnnotations(svc.Annotations) hostnameList := getHostnamesFromAnnotations(svc.Annotations) for _, hostname := range hostnameList { - endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, nodeTargets)...) + endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, nodeTargets, providerSpecific)...) } return endpoints @@ -288,7 +297,7 @@ func (sc *serviceSource) setResourceLabel(service v1.Service, endpoints []*endpo } } -func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, nodeTargets endpoint.Targets) []*endpoint.Endpoint { +func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, nodeTargets endpoint.Targets, providerSpecific endpoint.ProviderSpecific) []*endpoint.Endpoint { hostname = strings.TrimSuffix(hostname, ".") ttl, err := getTTLFromAnnotations(svc.Annotations) if err != nil { @@ -296,19 +305,21 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, nod } epA := &endpoint.Endpoint{ - RecordTTL: ttl, - RecordType: endpoint.RecordTypeA, - Labels: endpoint.NewLabels(), - Targets: make(endpoint.Targets, 0, defaultTargetsCapacity), - DNSName: hostname, + RecordTTL: ttl, + RecordType: endpoint.RecordTypeA, + Labels: endpoint.NewLabels(), + Targets: make(endpoint.Targets, 0, defaultTargetsCapacity), + DNSName: hostname, + ProviderSpecific: providerSpecific, } epCNAME := &endpoint.Endpoint{ - RecordTTL: ttl, - RecordType: endpoint.RecordTypeCNAME, - Labels: endpoint.NewLabels(), - Targets: make(endpoint.Targets, 0, defaultTargetsCapacity), - DNSName: hostname, + RecordTTL: ttl, + RecordType: endpoint.RecordTypeCNAME, + Labels: endpoint.NewLabels(), + Targets: make(endpoint.Targets, 0, defaultTargetsCapacity), + DNSName: hostname, + ProviderSpecific: providerSpecific, } var endpoints []*endpoint.Endpoint diff --git a/source/service_test.go b/source/service_test.go index edc4666ab..01fef8b93 100644 --- a/source/service_test.go +++ b/source/service_test.go @@ -1321,7 +1321,7 @@ func TestHeadlessServices(t *testing.T) { labels map[string]string annotations map[string]string clusterIP string - podIP string + podIPs []string selector map[string]string lbs []string podnames []string @@ -1343,7 +1343,7 @@ func TestHeadlessServices(t *testing.T) { hostnameAnnotationKey: "service.example.org", }, v1.ClusterIPNone, - "1.1.1.1", + []string{"1.1.1.1", "1.1.1.2"}, map[string]string{ "component": "foo", }, @@ -1353,7 +1353,7 @@ func TestHeadlessServices(t *testing.T) { []v1.PodPhase{v1.PodRunning, v1.PodRunning}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, - {DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, + {DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}}, }, false, }, @@ -1371,7 +1371,7 @@ func TestHeadlessServices(t *testing.T) { ttlAnnotationKey: "1", }, v1.ClusterIPNone, - "1.1.1.1", + []string{"1.1.1.1", "1.1.1.2"}, map[string]string{ "component": "foo", }, @@ -1381,7 +1381,7 @@ func TestHeadlessServices(t *testing.T) { []v1.PodPhase{v1.PodRunning, v1.PodRunning}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)}, - {DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)}, + {DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)}, }, false, }, @@ -1398,7 +1398,7 @@ func TestHeadlessServices(t *testing.T) { hostnameAnnotationKey: "service.example.org", }, v1.ClusterIPNone, - "1.1.1.1", + []string{"1.1.1.1", "1.1.1.2"}, map[string]string{ "component": "foo", }, @@ -1424,7 +1424,7 @@ func TestHeadlessServices(t *testing.T) { hostnameAnnotationKey: "service.example.org", }, v1.ClusterIPNone, - "1.1.1.1", + []string{"1.1.1.1", "1.1.1.2"}, map[string]string{ "component": "foo", }, @@ -1433,8 +1433,7 @@ func TestHeadlessServices(t *testing.T) { []string{"", ""}, []v1.PodPhase{v1.PodRunning, v1.PodRunning}, []*endpoint.Endpoint{ - {DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, - {DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, + {DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, }, false, }, @@ -1473,7 +1472,7 @@ func TestHeadlessServices(t *testing.T) { Annotations: tc.annotations, }, Status: v1.PodStatus{ - PodIP: tc.podIP, + PodIP: tc.podIPs[i], Phase: tc.phases[i], }, } @@ -1522,7 +1521,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { labels map[string]string annotations map[string]string clusterIP string - hostIP string + hostIPs []string selector map[string]string lbs []string podnames []string @@ -1544,7 +1543,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { hostnameAnnotationKey: "service.example.org", }, v1.ClusterIPNone, - "1.1.1.1", + []string{"1.1.1.1", "1.1.1.2"}, map[string]string{ "component": "foo", }, @@ -1554,7 +1553,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { []v1.PodPhase{v1.PodRunning, v1.PodRunning}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, - {DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, + {DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}}, }, false, }, @@ -1572,7 +1571,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { ttlAnnotationKey: "1", }, v1.ClusterIPNone, - "1.1.1.1", + []string{"1.1.1.1", "1.1.1.2"}, map[string]string{ "component": "foo", }, @@ -1582,7 +1581,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { []v1.PodPhase{v1.PodRunning, v1.PodRunning}, []*endpoint.Endpoint{ {DNSName: "foo-0.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)}, - {DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.1"}, RecordTTL: endpoint.TTL(1)}, + {DNSName: "foo-1.service.example.org", Targets: endpoint.Targets{"1.1.1.2"}, RecordTTL: endpoint.TTL(1)}, }, false, }, @@ -1599,7 +1598,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { hostnameAnnotationKey: "service.example.org", }, v1.ClusterIPNone, - "1.1.1.1", + []string{"1.1.1.1", "1.1.1.2"}, map[string]string{ "component": "foo", }, @@ -1625,7 +1624,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { hostnameAnnotationKey: "service.example.org", }, v1.ClusterIPNone, - "1.1.1.1", + []string{"1.1.1.1", "1.1.1.2"}, map[string]string{ "component": "foo", }, @@ -1634,8 +1633,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { []string{"", ""}, []v1.PodPhase{v1.PodRunning, v1.PodRunning}, []*endpoint.Endpoint{ - {DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, - {DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1"}}, + {DNSName: "service.example.org", Targets: endpoint.Targets{"1.1.1.1", "1.1.1.2"}}, }, false, }, @@ -1674,7 +1672,7 @@ func TestHeadlessServicesHostIP(t *testing.T) { Annotations: tc.annotations, }, Status: v1.PodStatus{ - HostIP: tc.hostIP, + HostIP: tc.hostIPs[i], Phase: tc.phases[i], }, } diff --git a/source/source.go b/source/source.go index 338397dfc..c4e856fb9 100644 --- a/source/source.go +++ b/source/source.go @@ -41,6 +41,12 @@ const ( controllerAnnotationValue = "dns-controller" ) +// Provider-specific annotations +const ( + // The annotation used for determining if traffic will go through Cloudflare + CloudflareProxiedKey = "external-dns.alpha.kubernetes.io/cloudflare-proxied" +) + const ( ttlMinimum = 1 ttlMaximum = math.MaxUint32 @@ -72,7 +78,6 @@ func getHostnamesFromAnnotations(annotations map[string]string) []string { if !exists { return nil } - return strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",") } @@ -82,10 +87,22 @@ func getAliasFromAnnotations(annotations map[string]string) bool { } func getProviderSpecificAnnotations(annotations map[string]string) endpoint.ProviderSpecific { - if getAliasFromAnnotations(annotations) { - return map[string]string{"alias": "true"} + providerSpecificAnnotations := endpoint.ProviderSpecific{} + + v, exists := annotations[CloudflareProxiedKey] + if exists { + providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ + Name: CloudflareProxiedKey, + Value: v, + }) } - return map[string]string{} + if getAliasFromAnnotations(annotations) { + providerSpecificAnnotations = append(providerSpecificAnnotations, endpoint.ProviderSpecificProperty{ + Name: "alias", + Value: "true", + }) + } + return providerSpecificAnnotations } // getTargetsFromTargetAnnotation gets endpoints from optional "target" annotation.