Merge branch 'master' of github.com:kubernetes-incubator/external-dns into feature/ignore-annotations

This commit is contained in:
Anand Patel 2018-11-16 14:21:23 -03:00
commit 230113c7b8
29 changed files with 937 additions and 260 deletions

View File

@ -23,7 +23,7 @@ RUN make build
# final image
FROM registry.opensource.zalan.do/stups/alpine:latest
MAINTAINER Team Teapot @ Zalando SE <team-teapot@zalando.de>
LABEL maintainer="Team Teapot @ Zalando SE <team-teapot@zalando.de>"
COPY --from=builder /go/src/github.com/kubernetes-incubator/external-dns/build/external-dns /bin/external-dns

158
Gopkg.lock generated
View File

@ -2,7 +2,7 @@
[[projects]]
digest = "1:e94ea655a0038d2274be202f77a2ea0eb2d3f74dfee674fd5d1f541e81008039"
digest = "1:ae9d0182a5cf7dbb025a8fc5821234cc1f26ca342fc41d951a99f71b9adc1b87"
name = "cloud.google.com/go"
packages = [
"compute/metadata",
@ -12,7 +12,7 @@
revision = "3b1ae45394a234c385be014e9a488f2bb6eef821"
[[projects]]
digest = "1:b341fb465b057e991b166d073b35a224f5a84228e5ef7e40b4da7a70c152e7ec"
digest = "1:fd38e3b8c27cab6561a7d2e8557205c3ca5c57cbb6d3a79e10f22e73e84fd776"
name = "github.com/Azure/azure-sdk-for-go"
packages = ["arm/dns"]
pruneopts = ""
@ -20,7 +20,7 @@
version = "v10.0.4-beta"
[[projects]]
digest = "1:767f5f5dd4fa8e4f7f206726361d29aa0f7622b0bb8294b73d071864368c0d6b"
digest = "1:f719ef698feb8da2923ebda9a8d553b977320b02182f3797e185202e588a94b1"
name = "github.com/Azure/go-autorest"
packages = [
"autorest",
@ -34,7 +34,7 @@
version = "v10.9.0"
[[projects]]
digest = "1:283a95024c33e84b23f24b1b47e3157ff2df2517d786a2e17bb0e6e4955e94e4"
digest = "1:7dc69d1597e4773ec5f64e5c078d55f0f011bb05ec0435346d0649ad978a23fd"
name = "github.com/alecthomas/kingpin"
packages = ["."]
pruneopts = ""
@ -43,7 +43,7 @@
[[projects]]
branch = "master"
digest = "1:1399282ad03ac819f0e8a747c888407c5c98bb497d33821a7047c7bae667ede0"
digest = "1:a74730e052a45a3fab1d310fdef2ec17ae3d6af16228421e238320846f2aaec8"
name = "github.com/alecthomas/template"
packages = [
".",
@ -61,7 +61,7 @@
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]]
digest = "1:7b6c017b0290ccf1dd98c47a51e1db8b72b0863b6c7c52ddaa5a0d894aa3c2fc"
digest = "1:d2dc5d0ccc137594ea6fb3870964967b96b43daac19b8093930c7b02873fd5ca"
name = "github.com/aliyun/alibaba-cloud-sdk-go"
packages = [
"sdk",
@ -81,7 +81,7 @@
version = "1.27.7"
[[projects]]
digest = "1:f04a72eefe1c7adec1dce30e099cec1e5fea8903a66e2db25bbbdfa66915428d"
digest = "1:1c82dd6a02941a3c4f23a32eca182717ab79691939e97d6b971466b780f06eea"
name = "github.com/aws/aws-sdk-go"
packages = [
"aws",
@ -121,14 +121,14 @@
[[projects]]
branch = "master"
digest = "1:d20bdb6bf44087574af3139835946875bb098440426785282c741865b7bc66d3"
digest = "1:0c5485088ce274fac2e931c1b979f2619345097b39d91af3239977114adf0320"
name = "github.com/beorn7/perks"
packages = ["quantile"]
pruneopts = ""
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
[[projects]]
digest = "1:d9d9c71f9776ef8f15b5c0a20246d5303071294743863ac3f4dde056f8c7b40a"
digest = "1:85fd00554a6ed5b33687684b76635d532c74141508b5bce2843d85e8a3c9dc91"
name = "github.com/cloudflare/cloudflare-go"
packages = ["."]
pruneopts = ""
@ -136,29 +136,22 @@
version = "v0.7.4"
[[projects]]
digest = "1:31259dbcb4c073aace59b951f5b471b3d5dbc4051b4a9d7e000f4392e143977e"
digest = "1:eaeede87b418b97f9dee473f8940fd9b65ca5cdac0503350c7c8f8965ea3cf4d"
name = "github.com/coreos/etcd"
packages = [
"client",
"pkg/pathutil",
"pkg/srv",
"auth/authpb",
"clientv3",
"etcdserver/api/v3rpc/rpctypes",
"etcdserver/etcdserverpb",
"mvcc/mvccpb",
"pkg/types",
"version",
]
pruneopts = ""
revision = "1b3ac99e8a431b381e633802cc42fe70e663baf5"
version = "v3.2.15"
[[projects]]
digest = "1:3c3f68ebab415344aef64363d23471e953a4715645115604aaf57923ae904f5e"
name = "github.com/coreos/go-semver"
packages = ["semver"]
pruneopts = ""
revision = "8ab6407b697782a06568d4b7f1db25550ec2e4c6"
version = "v0.2.0"
[[projects]]
digest = "1:0a39ec8bf5629610a4bc7873a92039ee509246da3cef1a0ea60f1ed7e5f9cea5"
digest = "1:56c130d885a4aacae1dd9c7b71cfe39912c7ebc1ff7d2b46083c8812996dc43b"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = ""
@ -167,7 +160,7 @@
[[projects]]
branch = "master"
digest = "1:64ee6871ef691c663f910e29bc2f7c10c8c342b06665920f1138b6aa8b11cb5a"
digest = "1:dc166ce7345c060c2153561130d6d49ac580c804226ac675e368d533b36eb169"
name = "github.com/denverdino/aliyungo"
packages = [
"metadata",
@ -177,7 +170,7 @@
revision = "69560d9530f5265ba00ffad2520d7ef01c2cddd4"
[[projects]]
digest = "1:2426da75f49e5b8507a6ed5d4c49b06b2ff795f4aec401c106b7db8fb2625cd7"
digest = "1:6098222470fe0172157ce9bbef5d2200df4edde17ee649c5d6e48330e4afa4c6"
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
pruneopts = ""
@ -185,7 +178,7 @@
version = "v3.2.0"
[[projects]]
digest = "1:3da5806ef37ea163fee80ed179d40a5e013e671ccbe321a04c47c5aee3d5080a"
digest = "1:32d1941b093bb945de75b0276348494be318d34f3df39c4413d61e002c800bc6"
name = "github.com/digitalocean/godo"
packages = [
".",
@ -196,7 +189,7 @@
version = "v1.1.1"
[[projects]]
digest = "1:ca3b228bf258217cff2070f4045e53729886c66a27bf9cce30dcbf8a575ea86a"
digest = "1:5ffd39844bdd1259a6227d544f582c6686ce43c8c44399a46052fe3bd2bed93c"
name = "github.com/dnsimple/dnsimple-go"
packages = ["dnsimple"]
pruneopts = ""
@ -204,7 +197,7 @@
version = "v0.14.0"
[[projects]]
digest = "1:bfce2cc5b829073f93962e742275d45913948e22d182fbc5464104da1c5f2f89"
digest = "1:e17d18b233f506404061c27ac4a08624dad38dcd0d49f9cfdae67a7772a4fb8c"
name = "github.com/exoscale/egoscale"
packages = ["."]
pruneopts = ""
@ -213,7 +206,7 @@
[[projects]]
branch = "master"
digest = "1:bc12846e4bae094e01a33ef98cad0a1afa35da37090e5126513be6f747e074ab"
digest = "1:ae7fb2062735e966ab69d14d2a091f3778b0d676dc8d1f01d092bcb0fb8ed45b"
name = "github.com/ffledgling/pdns-go"
packages = ["."]
pruneopts = ""
@ -228,7 +221,7 @@
version = "v1.0.0"
[[projects]]
digest = "1:bbc763f3c703dc3c6a99a22c1318760099b52bc00a47a36dc4462e88eee7846b"
digest = "1:a00483fe4106b86fb1187a92b5cf6915c85f294ed4c129ccbe7cb1f1a06abd46"
name = "github.com/go-ini/ini"
packages = ["."]
pruneopts = ""
@ -236,7 +229,7 @@
version = "v1.32.0"
[[projects]]
digest = "1:cdeb6a9eb9f2356b2987c401d013d41e018b819ee1e8d5a1b32a5b714e53c392"
digest = "1:8e67153fc0a9fb0d6c9707e36cf80e217a012364307b222eb4ba6828f7e881e6"
name = "github.com/go-resty/resty"
packages = ["."]
pruneopts = ""
@ -244,7 +237,7 @@
version = "v1.8.0"
[[projects]]
digest = "1:d7b2f8af8341e15d0239dab17cb49fbf4f01029ecf2d3b5924aa53d95c5a452d"
digest = "1:54d5c6a784a9de9c836fc070d51d0689c3e99ee6d24dba8a36f0762039dae830"
name = "github.com/gogo/googleapis"
packages = ["google/rpc"]
pruneopts = ""
@ -252,7 +245,7 @@
version = "v1.1.0"
[[projects]]
digest = "1:673df1d02ca0c6f51458fe94bbb6fae0b05e54084a31db2288f1c4321255c2da"
digest = "1:6e73003ecd35f4487a5e88270d3ca0a81bc80dc88053ac7e4dcfec5fba30d918"
name = "github.com/gogo/protobuf"
packages = [
"gogoproto",
@ -276,11 +269,12 @@
source = "github.com/kubermatic/glog-logrus"
[[projects]]
digest = "1:815d45503dceeca8ffecce0081d7edeae5e75b126107ef763d1c617154d72359"
digest = "1:3dd078fda7500c341bc26cfbc6c6a34614f295a2457149fc1045cab767cbcf18"
name = "github.com/golang/protobuf"
packages = [
"jsonpb",
"proto",
"protoc-gen-go/descriptor",
"ptypes",
"ptypes/any",
"ptypes/duration",
@ -315,7 +309,7 @@
revision = "44d81051d367757e1c7c6a5a86423ece9afcf63c"
[[projects]]
digest = "1:1962b5d00f5285d08504697049627d45ad876912894528d31cdc1c05cdc853f6"
digest = "1:16b2837c8b3cf045fa2cdc82af0cf78b19582701394484ae76b2c3bc3c99ad73"
name = "github.com/googleapis/gnostic"
packages = [
"OpenAPIv2",
@ -328,7 +322,7 @@
[[projects]]
branch = "master"
digest = "1:815036d12757902f85888f3cb0440c2e00220dd4177e4c2bb048e03259db077a"
digest = "1:54a44d48a24a104e078ef5f94d82f025a6be757e7c42b4370c621a3928d6ab7c"
name = "github.com/gophercloud/gophercloud"
packages = [
".",
@ -362,7 +356,7 @@
[[projects]]
branch = "master"
digest = "1:8c4d156acec272201ffc4d1bdb9302de1c48314e0451eb38c70150cf11bdb33a"
digest = "1:009a1928b8c096338b68b5822d838a72b4d8520715c1463614476359f3282ec8"
name = "github.com/gregjones/httpcache"
packages = [
".",
@ -422,14 +416,14 @@
revision = "61dc5f9b0a655ebf43026f0d8a837ad1e28e4b96"
[[projects]]
digest = "1:4f767a115bc8e08576f6d38ab73c376fc1b1cd3bb5041171c9e8668cc7739b52"
digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e"
name = "github.com/jmespath/go-jmespath"
packages = ["."]
pruneopts = ""
revision = "0b12d6b5"
[[projects]]
digest = "1:890dd7615573f096655600bbe7beb2f532a437f6d8ef237831894301fca31f23"
digest = "1:53ac4e911e12dde0ab68655e2006449d207a5a681f084974da2b06e5dbeaca72"
name = "github.com/json-iterator/go"
packages = ["."]
pruneopts = ""
@ -437,14 +431,14 @@
version = "1.1.4"
[[projects]]
digest = "1:def40684a573560241c8344da452fa3574dfc2c7da525903992a3790d2262625"
digest = "1:1c88ec29544b281964ed7a9a365b2802a523cd06c50cdee87eb3eec89cd864f4"
name = "github.com/kubernetes/repo-infra"
packages = ["verify/boilerplate/test"]
pruneopts = ""
revision = "c2f9667a4c29e70a39b0e89db2d4f0cab907dbee"
[[projects]]
digest = "1:c3aa5f9d5119ca1cfdaa41a5084e3deceef0460eef3e6c71b58fa50e500f01a0"
digest = "1:7c23a751ce2f84663fa411acb87eae0da2d09c39a8e99b08bd8f65fae75d8928"
name = "github.com/linki/instrumented_http"
packages = ["."]
pruneopts = ""
@ -452,7 +446,7 @@
version = "v0.2.0"
[[projects]]
digest = "1:93d29291d0c37678592d77ee847031aec2ce1631f3ce4cf975b77216e8bd4a01"
digest = "1:1c41354ef11c9dbae2fe1ceee8369fcb2634977ba07e701e19ea53e8742c5420"
name = "github.com/linode/linodego"
packages = ["."]
pruneopts = ""
@ -461,14 +455,14 @@
[[projects]]
branch = "master"
digest = "1:49a8b01a6cd6558d504b65608214ca40a78000e1b343ed0da5c6a9ccd83d6d30"
digest = "1:63722a4b1e1717be7b98fc686e0b30d5e7f734b9e93d7dee86293b6deab7ea28"
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
pruneopts = ""
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
[[projects]]
digest = "1:f0bad0fece0fb73c6ea249c18d8e80ffbe86be0457715b04463068f04686cf39"
digest = "1:4c8d8358c45ba11ab7bb15df749d4df8664ff1582daead28bae58cf8cbe49890"
name = "github.com/miekg/dns"
packages = ["."]
pruneopts = ""
@ -500,7 +494,7 @@
version = "v2.1"
[[projects]]
digest = "1:7aef6d4ad1b4a613d66ed554010c552a249e9afabcb079f54528a298474549cc"
digest = "1:d8b5d0ecca348c835914a1ed8589f17a6a7f309befab7327b0470324531f7ac4"
name = "github.com/nesv/go-dynect"
packages = ["dynect"]
pruneopts = ""
@ -508,7 +502,7 @@
version = "v0.6.0"
[[projects]]
digest = "1:2062e45c462d0327f680340dce46fe11ae2d34bf802e15e397cb1d6c4d159b39"
digest = "1:70df8e71a953626770223d4982801fa73e7e99cbfcca068b95127f72af9b9edd"
name = "github.com/oracle/oci-go-sdk"
packages = [
"common",
@ -520,14 +514,14 @@
[[projects]]
branch = "master"
digest = "1:b7be9a944fe102bf466420fa8a064534dd12547a0482f5b684d228425b559b56"
digest = "1:c24598ffeadd2762552269271b3b1510df2d83ee6696c1e543a0ff653af494bc"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
pruneopts = ""
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
[[projects]]
digest = "1:6db21ad64a13fe79220e47fcc895e13b8da923676a3a024f98210fca57a10d9a"
digest = "1:b46305723171710475f2dd37547edd57b67b9de9f2a6267cafdd98331fd6897f"
name = "github.com/peterbourgon/diskv"
packages = ["."]
pruneopts = ""
@ -550,7 +544,7 @@
version = "v1.0.0"
[[projects]]
digest = "1:7e88bda1bec34ddf3c8aded1326c652793069a673b0f751484953e7d65a2386c"
digest = "1:2f69dc6b2685b31a1a410ef697410aa8a669704fb201d45dbd8c1911728afa75"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
@ -562,7 +556,7 @@
[[projects]]
branch = "master"
digest = "1:83bf37d060fca77e959fe5ceee81e58bbd1b01836f4addc70043a948e9912547"
digest = "1:60aca47f4eeeb972f1b9da7e7db51dee15ff6c59f7b401c1588b8e6771ba15ef"
name = "github.com/prometheus/client_model"
packages = ["go"]
pruneopts = ""
@ -570,7 +564,7 @@
[[projects]]
branch = "master"
digest = "1:7221d79e41a24b2245d06f331d0825b479a9acd0bd05a8353806c7bf38395795"
digest = "1:e3aa5178be4fc4ae8cdb37d11c02f7490c00450a9f419e6aa84d02d3b47e90d2"
name = "github.com/prometheus/common"
packages = [
"expfmt",
@ -581,7 +575,7 @@
revision = "2e54d0b93cba2fd133edc32211dcc32c06ef72ca"
[[projects]]
digest = "1:91345f4cce04248cf4998c4f70a82579c1468101767636acf5af2e1556904933"
digest = "1:a6a85fc81f2a06ccac3d45005523afbeee45138d781d4f3cb7ad9889d5c65aab"
name = "github.com/prometheus/procfs"
packages = [
".",
@ -599,7 +593,7 @@
version = "v1.2.0"
[[projects]]
digest = "1:75e2c10fd48881dc9400b7b70281270923e01c44f1f5cb4bbc5ba8cac8ca3026"
digest = "1:3ac248add5bb40a3c631c5334adcd09aa72d15af2768a5bc0274084ea7b2e5ba"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = ""
@ -607,7 +601,7 @@
version = "v1.0.3"
[[projects]]
digest = "1:39c598f67d5d68846c05832bb351e897091edcbee4689c57d3697f68f25f928d"
digest = "1:a1403cc8a94b8d7956ee5e9694badef0e7b051af289caad1cf668331e3ffa4f6"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = ""
@ -623,7 +617,7 @@
version = "v1.0.2"
[[projects]]
digest = "1:ba8fed52de60135b7efd5d832b997fb5b10fa09f227fa385174faa69f4219e4e"
digest = "1:306417ea2f31ea733df356a2b895de63776b6a5107085b33458e5cd6eb1d584d"
name = "github.com/stretchr/objx"
packages = ["."]
pruneopts = ""
@ -631,7 +625,7 @@
version = "v0.1"
[[projects]]
digest = "1:a70d585d45f695f2e8e6782569bdf181419667a35e6035ceb086706b495aa21a"
digest = "1:a30066593578732a356dc7e5d7f78d69184ca65aeeff5939241a3ab10559bb06"
name = "github.com/stretchr/testify"
packages = [
"assert",
@ -652,14 +646,7 @@
revision = "ac974c61c2f990f4115b119354b5e0b47550e888"
[[projects]]
digest = "1:f98e0b7c7bd110a49d8bb56c9eefcef4f547f5d789025d3bfe9bd6b83125221b"
name = "github.com/ugorji/go"
packages = ["codec"]
pruneopts = ""
revision = "ded73eae5db7e7a0ef6f55aace87a2873c5d2b74"
[[projects]]
digest = "1:5e30725e7522642910b34208061b21bb0cd77b8ce115c3133a1431c52054e004"
digest = "1:74f86c458e82e1c4efbab95233e0cf51b7cc02dc03193be9f62cd81224e10401"
name = "go.uber.org/atomic"
packages = ["."]
pruneopts = ""
@ -675,7 +662,7 @@
version = "v1.1.0"
[[projects]]
digest = "1:2fabb14a874994210af33633091bd0eb070b50aa527767abaac1b5483db03d75"
digest = "1:246f378f80fba6fcf0f191c486b6613265abd2bc0f2fa55a36b928c67352021e"
name = "go.uber.org/zap"
packages = [
".",
@ -692,7 +679,7 @@
[[projects]]
branch = "master"
digest = "1:16db3d6f4f8bbe4b7b42cb8808e68457fea4bd7aea410b77c8c9a6dc26253a60"
digest = "1:b2d8b39397ca07929a3de3a3fd2b6ca4c8d48e9cadaa7cf2b083e27fd9e78107"
name = "golang.org/x/crypto"
packages = [
"ed25519",
@ -703,7 +690,7 @@
revision = "0709b304e793a5edb4a2c0145f281ecdc20838a4"
[[projects]]
digest = "1:02feed0dbc44ce5bef5670a6e5ac9c2c4e3b879575a9d074199b487af1b7c4f9"
digest = "1:782723d6fc27d202f1943219d68d58b3f6bcab6212c85294b1ddd8b586b1d356"
name = "golang.org/x/net"
packages = [
"bpf",
@ -725,7 +712,7 @@
revision = "161cd47e91fd58ac17490ef4d742dc98bb4cf60e"
[[projects]]
digest = "1:2fef2e19e90f29efd775d58d66b5e100139fedbe24cf749f1c085c0a5aee86d3"
digest = "1:dad5a319c4710358be1f2bf68f9fb7f90a71d7c641221b79801d5667b28f19e3"
name = "golang.org/x/oauth2"
packages = [
".",
@ -738,7 +725,7 @@
revision = "3c3a985cb79f52a3190fbc056984415ca6763d01"
[[projects]]
digest = "1:d4315e749759007a597c9ad09eef29112bea98030da19ed29c33959ad6744130"
digest = "1:39d88a855976e160babdd254802e1c2ae75ed380328c39742b57928852da6207"
name = "golang.org/x/sys"
packages = [
"unix",
@ -748,7 +735,7 @@
revision = "13d03a9a82fba647c21a0ef8fba44a795d0f0835"
[[projects]]
digest = "1:af9bfca4298ef7502c52b1459df274eed401a4f5498b900e9a92d28d3d87ac5a"
digest = "1:5acd3512b047305d49e8763eef7ba423901e85d5dd2fd1e71778a0ea8de10bd4"
name = "golang.org/x/text"
packages = [
"collate",
@ -779,7 +766,7 @@
revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
[[projects]]
digest = "1:ab84306e7e74b9f01b9f1480d46cca9325f8c512567a0e7b8888d04ff627a5ba"
digest = "1:2ad38d79865e33dde6157b7048debd6e7d47e0709df7b5e11bb888168e316908"
name = "google.golang.org/api"
packages = [
"dns/v1",
@ -791,7 +778,7 @@
revision = "a0ff90edab763c86aa88f2b1eb4f3301b82f6336"
[[projects]]
digest = "1:0b45fac4876cbd496ed7b95406b05c8c1eba559b43c82f2dda1b0e1bbe6cd1b6"
digest = "1:41e2b7e287117f6136f75286d48072ecf781ba54823ffeb2098e152e7dc45ef6"
name = "google.golang.org/appengine"
packages = [
".",
@ -810,14 +797,17 @@
[[projects]]
branch = "master"
digest = "1:7040eaf95eb09f6f69e1415074049a9a66236d59d8767f2d17b759b916f79fb1"
digest = "1:e43f1cb3f488a0c2be85939c2a594636f60b442a12a196c778bd2d6c9aca3df7"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
packages = [
"googleapis/api/annotations",
"googleapis/rpc/status",
]
pruneopts = ""
revision = "11092d34479b07829b72e10713b159248caf5dad"
[[projects]]
digest = "1:cb1330030248de97a11d9f9664f3944fce0df947e5ed94dbbd9cb6e77068bd46"
digest = "1:ca75b3775a5d4e5d1fb48f57ef0865b4aaa8b3f00e6b52be68db991c4594e0a7"
name = "google.golang.org/grpc"
packages = [
".",
@ -830,6 +820,7 @@
"encoding",
"encoding/proto",
"grpclog",
"health/grpc_health_v1",
"internal",
"internal/backoff",
"internal/channelz",
@ -869,7 +860,7 @@
[[projects]]
branch = "release-1.0"
digest = "1:159d72863d2fdc7f8a7bf5178554bad51c45b003ba27021c3481f84b3cc8e155"
digest = "1:bc43af6616d8ca12a7b8e806874387f0f1e181c08f547e9cd77f6cbac8cefd86"
name = "istio.io/api"
packages = [
"authentication/v1alpha1",
@ -883,7 +874,7 @@
revision = "76349c53b87f03f1e610b3aa3843dba3c38138d7"
[[projects]]
digest = "1:2e63e5a0a6abb75c20ea575ee82a72c117a811ab8472ed34a48a09ba25a0a7d7"
digest = "1:7eb25280e1f610470bb0c43ab6c91573cfc78672a58542106b9b71705581429a"
name = "istio.io/istio"
packages = [
"pilot/pkg/config/kube/crd",
@ -899,7 +890,7 @@
version = "1.0.1"
[[projects]]
digest = "1:d42e6aef075bfc20da9c3991adfd8b09e3e158ac619028e15271677b705be5f0"
digest = "1:f420c8548c93242d8e5dcfa5b34e0243883b4e660f65076e869daafac877144d"
name = "k8s.io/api"
packages = [
"admissionregistration/v1alpha1",
@ -937,7 +928,7 @@
version = "kubernetes-1.11.0"
[[projects]]
digest = "1:a14992570e0e2b0291594f505d2b2ed1a6ba4482d4166ace9714c2ba8cbfe252"
digest = "1:66d1421ecff35bc48ee0b11a3f891f3af6f775ed6bb1d3e0deeaba221bf42490"
name = "k8s.io/apiextensions-apiserver"
packages = [
"pkg/apis/apiextensions",
@ -951,7 +942,7 @@
version = "kubernetes-1.10.4"
[[projects]]
digest = "1:a855f74be59f83ed0950a9a2b70d8c8af01fb5782d060c7dec67ae39033f30dc"
digest = "1:b6b2fb7b4da1ac973b64534ace2299a02504f16bc7820cb48edb8ca4077183e1"
name = "k8s.io/apimachinery"
packages = [
"pkg/api/errors",
@ -1001,7 +992,7 @@
version = "kubernetes-1.11.0"
[[projects]]
digest = "1:3c4611c2b28fdc62391698bba7f212050f0f9ed75f3648f37ec3bcf8a83bf96d"
digest = "1:d04779a8de7d5465e0463bd986506348de5e89677c74777f695d3145a7a8d15e"
name = "k8s.io/client-go"
packages = [
"discovery",
@ -1107,7 +1098,7 @@
[[projects]]
branch = "master"
digest = "1:d93d8bcb5f04d6b59eafdb9fa1a80f187d2542611670bfabc0ea8e031ab874a2"
digest = "1:526095379da1098c3f191a0008cc59c9bf9927492e63da7689e5de424219c162"
name = "k8s.io/kube-openapi"
packages = ["pkg/util/proto"]
pruneopts = ""
@ -1134,7 +1125,7 @@
"github.com/aws/aws-sdk-go/service/route53",
"github.com/aws/aws-sdk-go/service/servicediscovery",
"github.com/cloudflare/cloudflare-go",
"github.com/coreos/etcd/client",
"github.com/coreos/etcd/clientv3",
"github.com/denverdino/aliyungo/metadata",
"github.com/digitalocean/godo",
"github.com/digitalocean/godo/context",
@ -1173,6 +1164,7 @@
"istio.io/istio/pilot/pkg/model",
"k8s.io/api/core/v1",
"k8s.io/api/extensions/v1beta1",
"k8s.io/apimachinery/pkg/api/errors",
"k8s.io/apimachinery/pkg/apis/meta/v1",
"k8s.io/apimachinery/pkg/labels",
"k8s.io/apimachinery/pkg/runtime",

View File

@ -24,10 +24,6 @@ ignored = ["github.com/kubernetes/repo-infra/kazel"]
name = "github.com/cloudflare/cloudflare-go"
version = "0.7.3"
[[constraint]]
name = "github.com/coreos/etcd"
version = "~3.2.15"
[[constraint]]
name = "github.com/digitalocean/godo"
version = "~1.1.0"

View File

@ -90,12 +90,14 @@ func TestRunOnce(t *testing.T) {
source := new(testutils.MockSource)
source.On("Endpoints").Return([]*endpoint.Endpoint{
{
DNSName: "create-record",
Targets: endpoint.Targets{"1.2.3.4"},
DNSName: "create-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
},
{
DNSName: "update-record",
Targets: endpoint.Targets{"8.8.4.4"},
DNSName: "update-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.4.4"},
},
}, nil)
@ -103,26 +105,28 @@ func TestRunOnce(t *testing.T) {
provider := newMockProvider(
[]*endpoint.Endpoint{
{
DNSName: "update-record",
Targets: endpoint.Targets{"8.8.8.8"},
DNSName: "update-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
{
DNSName: "delete-record",
Targets: endpoint.Targets{"4.3.2.1"},
DNSName: "delete-record",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"4.3.2.1"},
},
},
&plan.Changes{
Create: []*endpoint.Endpoint{
{DNSName: "create-record", Targets: endpoint.Targets{"1.2.3.4"}},
{DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
UpdateNew: []*endpoint.Endpoint{
{DNSName: "update-record", Targets: endpoint.Targets{"8.8.4.4"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
},
UpdateOld: []*endpoint.Endpoint{
{DNSName: "update-record", Targets: endpoint.Targets{"8.8.8.8"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
},
Delete: []*endpoint.Endpoint{
{DNSName: "delete-record", Targets: endpoint.Targets{"4.3.2.1"}},
{DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
},
},
)

View File

@ -69,6 +69,7 @@ Regarding Ingress, we'll support:
* Google's Ingress Controller on GKE that integrates with their Layer 7 load balancers (GLBC)
* nginx-ingress-controller v0.9.x with a fronting Service
* Zalando's [AWS Ingress controller](https://github.com/zalando-incubator/kube-ingress-aws-controller), based on AWS ALBs and [Skipper](https://github.com/zalando/skipper)
* [Traefik](https://github.com/containous/traefik) 1.7 and above, when [`kubernetes.ingressEndpoint`](https://docs.traefik.io/v1.7/configuration/backends/kubernetes/#ingressendpoint) is configured (`kubernetes.ingressEndpoint.useDefaultPublishedService` in the [Helm chart](https://github.com/helm/charts/tree/master/stable/traefik#configuration))
### Are other Ingress Controllers supported?

View File

@ -286,7 +286,7 @@ spec:
After roughly two minutes check that a corresponding DNS record for your service was created.
```console
$ aliyun aliyun alidns DescribeDomainRecords --DomainName=external-dns-test.com
$ aliyun alidns DescribeDomainRecords --DomainName=external-dns-test.com
{
"PageNumber": 1,
"TotalCount": 1,

View File

@ -171,6 +171,13 @@ This list is not the full list, but a few arguments that where chosen.
`aws-zone-type` allows filtering for private and public zones
## Annotations
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.
## Verify ExternalDNS works (Ingress example)

View File

@ -3,7 +3,7 @@
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster on Azure.
Make sure to use **>=0.4.2** version of ExternalDNS for this tutorial.
Make sure to use **>=0.5.7** version of ExternalDNS for this tutorial.
This tutorial uses [Azure CLI 2.0](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) for all
Azure commands and assumes that the Kubernetes cluster was created via Azure Container Services and `kubectl` commands
@ -38,22 +38,57 @@ Please consult your registrar's documentation on how to do that.
The Azure DNS provider expects, by default, that the configuration file is at `/etc/kubernetes/azure.json`. This can be overridden with
the `--azure-config-file` option when starting ExternalDNS.
### Azure Container Services
When your Kubernetes cluster is created by ACS, a file named `/etc/kubernetes/azure.json` is created to store
the Azure credentials for API access. Kubernetes uses this file for the Azure cloud provider.
### Use provisioned VM configuration file
When running within Azure (ACS or AKS), the agent and master VMs are already provisioned with the configuration file at `/etc/kubernetes/azure.json`.
For ExternalDNS to access the Azure API, it also needs access to this file. However, we will be deploying ExternalDNS inside of
the Kubernetes cluster so we will need to use a Kubernetes secret.
If you want to use the file directly, make sure that the service principal that is given there has access to contribute to the resource group containing the Azure DNS zone(s).
To use the file, replace the directive
```yaml
volumes:
- name: azure-config-file
secret:
secretName: azure-config-file
```
with
```yaml
volumes:
- name: azure-config-file
hostPath:
path: /etc/kubernetes/azure.json
type: File
```
in the manifests below.
### Use custom configuration file
If you want to customize the configuration, for example because you want to use a different service principal, you have to manually create a secret.
This is also required if the Kubernetes cluster is not hosted in Azure Container Services (ACS or AKS) and you still want to use Azure DNS.
The secret should contain an object named azure.json with content similar to this:
```json
{
"tenantId": "01234abc-de56-ff78-abc1-234567890def",
"subscriptionId": "01234abc-de56-ff78-abc1-234567890def",
"aadClientId": "01234abc-de56-ff78-abc1-234567890def",
"aadClientSecret": "uKiuXeiwui4jo9quae9o",
"resourceGroup": "MyDnsResourceGroup",
}
```
You can find the `tenantId` by running `az account show` or by selecting Azure Active Directory in the Azure Portal and checking the _Directory ID_ under Properties.
You can find the `subscriptionId` by running `az account show --query "id"` or by selecting Subscriptions in the Azure Portal.
To create the secret:
```
$ kubectl create secret generic azure-config-file --from-file=/etc/kubernetes/azure.json
$ kubectl create secret generic azure-config-file --from-file=/local/path/to/azure.json
```
### Azure Kubernetes Services (aka AKS)
When your cluster is created, unlike ACS there are no Azure credentials stored and you must create an azure.json object manually like with other hosting providers. In order to create the azure.json you must first create an Azure AD service principal in the Azure AD tenant linked to your Azure subscription that is hosting your DNS zone.
#### Create 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.
@ -89,27 +124,20 @@ A Service Principal with a minimum access level of contribute to the resource gr
"password": "password", <-- aadClientSecret value
"tenant": "AzureAD Tenant Id" <-- tenantId value
}
...
```
```
### Other hosting providers
If the Kubernetes cluster is not hosted by Azure Container Services and you still want to use Azure DNS, you need to create the secret manually. The secret should contain an object named azure.json with content similar to this:
```
#### Azure Managed Service Identity (MSI)
If [Azure Managed Service Identity (MSI)](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) is enabled for virtual machines, then there is no need to create separate service principal.
The contents of `azure.json` should be similar to this:
```json
{
"tenantId": "AzureAD tenant Id",
"subscriptionId": "Id",
"aadClientId": "Service Principal AppId",
"aadClientSecret": "Service Principal Password",
"tenantId": "01234abc-de56-ff78-abc1-234567890def",
"subscriptionId": "01234abc-de56-ff78-abc1-234567890def",
"resourceGroup": "MyDnsResourceGroup",
}
```
If [Azure Managed Service Identity (MSI)](https://docs.microsoft.com/en-us/azure/active-directory/managed-service-identity/overview) is enabled for virtual machines, then there is no need to create separate service principal. The contents of `azure.json` should be similar to this:
```
{
"tenantId": "AzureAD tenant Id",
"subscriptionId": "Id",
"resourceGroup": "MyDnsResourceGroup",
"useManagedIdentityExtension": true,
"useManagedIdentityExtension": true
}
```
@ -170,7 +198,7 @@ spec:
secretName: azure-config-file
```
### Manifest (for clusters with RBAC enabled)
### Manifest (for clusters with RBAC enabled, cluster access)
```yaml
apiVersion: v1
kind: ServiceAccount
@ -240,6 +268,76 @@ spec:
secretName: azure-config-file
```
### Manifest (for clusters with RBAC enabled, namespace access)
This configuration is the same as above, except it only requires privileges for the current namespace, not for the whole cluster.
However, access to [nodes](https://kubernetes.io/docs/concepts/architecture/nodes/) requires cluster access, so when using this manifest,
services with type `NodePort` will be skipped!
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: external-dns
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.opensource.zalan.do/teapot/external-dns:latest
args:
- --source=service
- --source=ingress
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
- --provider=azure
- --azure-resource-group=externaldns # (optional) use the DNS zones from the tutorial's resource group
volumeMounts:
- name: azure-config-file
mountPath: /etc/kubernetes
readOnly: true
volumes:
- name: azure-config-file
secret:
secretName: azure-config-file
```
Create the deployment for ExternalDNS:
```

View File

@ -22,6 +22,7 @@ auth:
region: us-phoenix-1
tenancy: ocid1.tenancy.oc1...
user: ocid1.user.oc1...
key: |
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
fingerprint: af:81:71:8e...

View File

@ -15,8 +15,7 @@ anyway.
The PDNS provider currently does not support:
1. Dry running a configuration is not supported.
2. The `--domain-filter` flag is not supported.
* Dry running a configuration is not supported
## Deployment
@ -47,10 +46,18 @@ spec:
- --pdns-server={{ pdns-api-url }}
- --pdns-api-key={{ pdns-http-api-key }}
- --txt-owner-id={{ owner-id-for-this-external-dns }}
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the zones matching provided domain; omit to process all available zones in PowerDNS
- --log-level=debug
- --interval=30s
```
#### Domain Filter (--domain-filter)
When the domain-filter argument is specified, external-dns will automatically create DNS records based on host names specified in ingress objects and services with the external-dns annotation that match the domain-filter argument in the external-dns deployment manifest.
eg. ```--domain-filter=example.org``` will allow for zone `example.org` and any zones in PowerDNS that ends in `.example.org`, including `an.example.org`, ie. the subdomains of example.org.
eg. ```--domain-filter=.example.org``` will allow *only* zones that end in `.example.org`, ie. the subdomains of example.org but not the `example.org` zone itself.
## RBAC
If your cluster is RBAC enabled, you also need to setup the following, before you can run external-dns:

View File

@ -288,7 +288,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("rfc2136-tsig-axfr", "When using the RFC2136 provider, specify the TSIG (base64) value to attached to DNS messages (required when --rfc2136-insecure=false)").BoolVar(&cfg.RFC2136TAXFR)
// Flags related to policies
app.Flag("policy", "Modify how DNS records are sychronized between sources and providers (default: sync, options: sync, upsert-only)").Default(defaultConfig.Policy).EnumVar(&cfg.Policy, "sync", "upsert-only")
app.Flag("policy", "Modify how DNS records are synchronized between sources and providers (default: sync, options: sync, upsert-only)").Default(defaultConfig.Policy).EnumVar(&cfg.Policy, "sync", "upsert-only")
// Flags related to the registry
app.Flag("registry", "The registry implementation to use to keep track of DNS record ownership (default: txt, options: txt, noop, aws-sd)").Default(defaultConfig.Registry).EnumVar(&cfg.Registry, "txt", "noop", "aws-sd")

View File

@ -135,10 +135,10 @@ func (t planTable) getDeletes() (deleteList []*endpoint.Endpoint) {
func (p *Plan) Calculate() *Plan {
t := newPlanTable()
for _, current := range p.Current {
for _, current := range filterRecordsForPlan(p.Current) {
t.addCurrent(current)
}
for _, desired := range p.Desired {
for _, desired := range filterRecordsForPlan(p.Desired) {
t.addCandidate(desired)
}
@ -180,6 +180,30 @@ func shouldUpdateTTL(desired, current *endpoint.Endpoint) bool {
return desired.RecordTTL != current.RecordTTL
}
// 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.)
//
// Per RFC 1034, CNAME records conflict with all other records - it is the
// only record with this property. The behavior of the planner may need to be
// made more sophisticated to codify this.
func filterRecordsForPlan(records []*endpoint.Endpoint) []*endpoint.Endpoint {
filtered := []*endpoint.Endpoint{}
for _, record := range records {
// Explicitly specify which records we want to use for planning.
// TODO: Add AAAA records as well when they are supported.
switch record.RecordType {
case endpoint.RecordTypeA, endpoint.RecordTypeCNAME:
filtered = append(filtered, record)
default:
continue
}
}
return filtered
}
// sanitizeDNSName checks if the DNS name is correct
// for now it only removes space and lower case
func sanitizeDNSName(dnsName string) string {

View File

@ -29,6 +29,7 @@ type PlanTestSuite struct {
suite.Suite
fooV1Cname *endpoint.Endpoint
fooV2Cname *endpoint.Endpoint
fooV2TXT *endpoint.Endpoint
fooV2CnameNoLabel *endpoint.Endpoint
fooV3CnameSameResource *endpoint.Endpoint
fooA5 *endpoint.Endpoint
@ -65,6 +66,10 @@ func (suite *PlanTestSuite) SetupTest() {
endpoint.ResourceLabelKey: "ingress/default/foo-v2",
},
}
suite.fooV2TXT = &endpoint.Endpoint{
DNSName: "foo",
RecordType: "TXT",
}
suite.fooV2CnameNoLabel = &endpoint.Endpoint{
DNSName: "foo",
Targets: endpoint.Targets{"v2"},
@ -262,6 +267,27 @@ func (suite *PlanTestSuite) TestDifferentTypes() {
validateEntries(suite.T(), changes.Delete, expectedDelete)
}
func (suite *PlanTestSuite) TestIgnoreTXT() {
current := []*endpoint.Endpoint{suite.fooV2TXT}
desired := []*endpoint.Endpoint{suite.fooV2Cname}
expectedCreate := []*endpoint.Endpoint{suite.fooV2Cname}
expectedUpdateOld := []*endpoint.Endpoint{}
expectedUpdateNew := []*endpoint.Endpoint{}
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) TestRemoveEndpoint() {
current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A}
desired := []*endpoint.Endpoint{suite.fooV1Cname}

View File

@ -684,18 +684,24 @@ func (p *AlibabaCloudProvider) splitDNSName(endpoint *endpoint.Endpoint) (rr str
}
if !found {
idx := strings.Index(name, ".")
if idx >= 0 {
rr = name[0:idx]
domain = name[idx+1:]
} else {
parts := strings.Split(name, ".")
if len(parts) < 2 {
rr = name
domain = ""
} else {
domain = parts[len(parts)-2] + "." + parts[len(parts)-1]
rrIndex := strings.Index(name, domain)
if rrIndex < 1 {
rrIndex = 1
}
rr = name[0 : rrIndex-1]
}
}
if rr == "" {
rr = nullHostAlibabaCloud
}
return rr, domain
}

View File

@ -404,6 +404,16 @@ func TestAlibabaCloudProvider_splitDNSName(t *testing.T) {
if rr != "@" || domain != "container-service.top" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
endpoint.DNSName = "a.b.container-service.top"
rr, domain = p.splitDNSName(endpoint)
if rr != "a.b" || domain != "container-service.top" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
endpoint.DNSName = "a.b.c.container-service.top"
rr, domain = p.splitDNSName(endpoint)
if rr != "a.b.c" || domain != "container-service.top" {
t.Errorf("Failed to splitDNSName for %s: rr=%s, domain=%s", endpoint.DNSName, rr, domain)
}
}
func TestAlibabaCloudProvider_TXTEndpoint(t *testing.T) {

View File

@ -193,7 +193,7 @@ func (p *AWSProvider) Zones() (map[string]*route53.HostedZone, error) {
// wildcardUnescape converts \\052.abc back to *.abc
// Route53 stores wildcards escaped: http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DomainNameFormat.html?shortFooter=true#domain-name-format-asterisk
func wildcardUnescape(s string) string {
if strings.HasPrefix(s, "\\052") {
if strings.Contains(s, "\\052") {
s = strings.Replace(s, "\\052", "*", 1)
}
return s
@ -364,6 +364,11 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou
},
}
rec, err := p.Records()
if err != nil {
log.Infof("getting records failed: %v", err)
}
if isAWSLoadBalancer(endpoint) {
evalTargetHealth := p.evaluateTargetHealth
if _, ok := endpoint.ProviderSpecific[providerSpecificEvaluateTargetHealth]; ok {
@ -376,6 +381,19 @@ func (p *AWSProvider) newChange(action string, endpoint *endpoint.Endpoint) *rou
HostedZoneId: aws.String(canonicalHostedZone(endpoint.Targets[0])),
EvaluateTargetHealth: aws.Bool(evalTargetHealth),
}
} else if hostedZone := isAWSAlias(endpoint, rec); hostedZone != "" {
zones, err := p.Zones()
if err != nil {
log.Errorf("getting zones failed: %v", err)
}
for _, zone := range zones {
change.ResourceRecordSet.Type = aws.String(route53.RRTypeA)
change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{
DNSName: aws.String(endpoint.Targets[0]),
HostedZoneId: aws.String(cleanZoneID(*zone.Id)),
EvaluateTargetHealth: aws.Bool(p.evaluateTargetHealth),
}
}
} else {
change.ResourceRecordSet.Type = aws.String(endpoint.RecordType)
if !endpoint.RecordTTL.IsConfigured() {
@ -529,6 +547,21 @@ func isAWSLoadBalancer(ep *endpoint.Endpoint) bool {
return false
}
// 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" {
for _, addr := range addrs {
if addr.DNSName == ep.Targets[0] {
if hostedZone := canonicalHostedZone(addr.Targets[0]); hostedZone != "" {
return hostedZone
}
}
}
}
return ""
}
// canonicalHostedZone returns the matching canonical zone for a given hostname.
func canonicalHostedZone(hostname string) string {
for suffix, zone := range canonicalHostedZones {
@ -539,3 +572,11 @@ func canonicalHostedZone(hostname string) string {
return ""
}
// cleanZoneID removes the "/hostedzone/" prefix
func cleanZoneID(ID string) string {
if strings.HasPrefix(ID, "/hostedzone/") {
ID = strings.TrimPrefix(ID, "/hostedzone/")
}
return ID
}

View File

@ -89,7 +89,7 @@ func (r *Route53APIStub) ListResourceRecordSetsPages(input *route53.ListResource
// Route53 stores wildcards escaped: http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DomainNameFormat.html?shortFooter=true#domain-name-format-asterisk
func wildcardEscape(s string) string {
if strings.HasPrefix(s, "*") {
if strings.Contains(s, "*") {
s = strings.Replace(s, "*", "\\052", 1)
}
return s
@ -257,6 +257,7 @@ func TestAWSRecords(t *testing.T) {
endpoint.NewEndpoint("*.wildcard-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "false"),
endpoint.NewEndpoint("list-test-alias-evaluate.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "true"),
endpoint.NewEndpointWithTTL("list-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
endpoint.NewEndpointWithTTL("prefix-*.wildcard.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "random"),
})
records, err := provider.Records()
@ -270,6 +271,7 @@ func TestAWSRecords(t *testing.T) {
endpoint.NewEndpoint("*.wildcard-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "false"),
endpoint.NewEndpoint("list-test-alias-evaluate.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "true"),
endpoint.NewEndpointWithTTL("list-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
endpoint.NewEndpointWithTTL("prefix-*.wildcard.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeTXT, endpoint.TTL(recordTTL), "random"),
})
}
@ -819,6 +821,35 @@ func TestAWSisLoadBalancer(t *testing.T) {
}
}
func TestAWSisAWSAlias(t *testing.T) {
for _, tc := range []struct {
target string
recordType string
alias string
expected string
}{
{"bar.example.org", endpoint.RecordTypeCNAME, "true", "Z215JYRZR1TBD5"},
{"foo.example.org", endpoint.RecordTypeCNAME, "true", ""},
} {
ep := &endpoint.Endpoint{
Targets: endpoint.Targets{tc.target},
RecordType: tc.recordType,
ProviderSpecific: map[string]string{"alias": tc.alias},
}
addrs := []*endpoint.Endpoint{
{
DNSName: "foo.example.org",
Targets: endpoint.Targets{"foobar.example.org"},
},
{
DNSName: "bar.example.org",
Targets: endpoint.Targets{"bar.eu-central-1.elb.amazonaws.com"},
},
}
assert.Equal(t, tc.expected, isAWSAlias(ep, addrs))
}
}
func TestAWSCanonicalHostedZone(t *testing.T) {
for _, tc := range []struct {
hostname string
@ -929,10 +960,13 @@ func setupAWSRecords(t *testing.T, provider *AWSProvider, endpoints []*endpoint.
require.NoError(t, provider.CreateRecords(endpoints))
escapeAWSRecords(t, provider, "/hostedzone/zone-1.ext-dns-test-2.teapot.zalan.do.")
escapeAWSRecords(t, provider, "/hostedzone/zone-2.ext-dns-test-2.teapot.zalan.do.")
escapeAWSRecords(t, provider, "/hostedzone/zone-3.ext-dns-test-2.teapot.zalan.do.")
records, err = provider.Records()
require.NoError(t, err)
validateEndpoints(t, records, endpoints)
}
func listAWSRecords(t *testing.T, client Route53API, zone string) []*route53.ResourceRecordSet {
@ -941,10 +975,7 @@ func listAWSRecords(t *testing.T, client Route53API, zone string) []*route53.Res
HostedZoneId: aws.String(zone),
}, func(resp *route53.ListResourceRecordSetsOutput, _ bool) bool {
for _, recordSet := range resp.ResourceRecordSets {
switch aws.StringValue(recordSet.Type) {
case endpoint.RecordTypeA, endpoint.RecordTypeCNAME:
recordSets = append(recordSets, recordSet)
}
recordSets = append(recordSets, recordSet)
}
return true
}))
@ -974,6 +1005,29 @@ func clearAWSRecords(t *testing.T, provider *AWSProvider, zone string) {
}
}
// Route53 stores wildcards escaped: http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DomainNameFormat.html?shortFooter=true#domain-name-format-asterisk
func escapeAWSRecords(t *testing.T, provider *AWSProvider, zone string) {
recordSets := listAWSRecords(t, provider.client, zone)
changes := make([]*route53.Change, 0, len(recordSets))
for _, recordSet := range recordSets {
changes = append(changes, &route53.Change{
Action: aws.String(route53.ChangeActionUpsert),
ResourceRecordSet: recordSet,
})
}
if len(changes) != 0 {
_, err := provider.client.ChangeResourceRecordSets(&route53.ChangeResourceRecordSetsInput{
HostedZoneId: aws.String(zone),
ChangeBatch: &route53.ChangeBatch{
Changes: changes,
},
})
require.NoError(t, err)
}
}
func newAWSProvider(t *testing.T, domainFilter DomainFilter, zoneIDFilter ZoneIDFilter, zoneTypeFilter ZoneTypeFilter, evaluateTargetHealth, dryRun bool, records []*endpoint.Endpoint) (*AWSProvider, *Route53APIStub) {
client := NewRoute53APIStub()

View File

@ -48,7 +48,7 @@ func createMockZone(zone string, id string) dns.Zone {
}
func (client *mockZonesClient) ListByResourceGroup(resourceGroupName string, top *int32) (dns.ZoneListResult, error) {
// Don't bother filtering by resouce group or implementing paging since that's the responsibility
// Don't bother filtering by resource group or implementing paging since that's the responsibility
// of the Azure DNS service
return *client.mockZoneListResult, nil
}

View File

@ -17,7 +17,7 @@ limitations under the License.
package provider
import (
"container/list"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
@ -26,14 +26,12 @@ import (
"io/ioutil"
"math/rand"
"net"
"net/http"
"os"
"strings"
"time"
etcd "github.com/coreos/etcd/client"
etcdcv3 "github.com/coreos/etcd/clientv3"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/kubernetes-incubator/external-dns/plan"
@ -43,8 +41,17 @@ func init() {
rand.Seed(time.Now().UnixNano())
}
// skyDNSClient is an interface to work with SkyDNS service records in etcd
type skyDNSClient interface {
const (
priority = 10 // default priority when nothing is set
etcdTimeout = 5 * time.Second
coreDNSPrefix = "/skydns/"
randomPrefixLabel = "prefix"
)
// coreDNSClient is an interface to work with CoreDNS service records in etcd
type coreDNSClient interface {
GetServices(prefix string) ([]*Service, error)
SaveService(value *Service) error
DeleteService(key string) error
@ -53,10 +60,10 @@ type skyDNSClient interface {
type coreDNSProvider struct {
dryRun bool
domainFilter DomainFilter
client skyDNSClient
client coreDNSClient
}
// Service represents SkyDNS/CoreDNS etcd record
// Service represents CoreDNS etcd record
type Service struct {
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
@ -83,52 +90,58 @@ type Service struct {
}
type etcdClient struct {
api etcd.KeysAPI
client *etcdcv3.Client
ctx context.Context
}
var _ skyDNSClient = etcdClient{}
var _ coreDNSClient = etcdClient{}
// GetService return all Service records stored in etcd stored anywhere under the given key (recursively)
func (c etcdClient) GetServices(prefix string) ([]*Service, error) {
var result []*Service
opts := &etcd.GetOptions{Recursive: true}
data, err := c.api.Get(context.Background(), prefix, opts)
ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
defer cancel()
path := prefix
r, err := c.client.Get(ctx, path, etcdcv3.WithPrefix())
if err != nil {
if etcd.IsKeyNotFound(err) {
return nil, nil
}
return nil, err
}
queue := list.New()
queue.PushFront(data.Node)
for queueNode := queue.Front(); queueNode != nil; queueNode = queueNode.Next() {
node := queueNode.Value.(*etcd.Node)
if node.Dir {
for _, childNode := range node.Nodes {
queue.PushBack(childNode)
}
var svcs []*Service
bx := make(map[Service]bool)
for _, n := range r.Kvs {
svc := new(Service)
if err := json.Unmarshal(n.Value, svc); err != nil {
return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
}
b := Service{Host: svc.Host, Port: svc.Port, Priority: svc.Priority, Weight: svc.Weight, Text: svc.Text, Key: string(n.Key)}
if _, ok := bx[b]; ok {
// skip the service if already added to service list.
// the same service might be found in multiple etcd nodes.
continue
}
service := &Service{}
err = json.Unmarshal([]byte(node.Value), service)
if err != nil {
log.Error("Cannot parse JSON value ", node.Value)
continue
bx[b] = true
svc.Key = string(n.Key)
if svc.Priority == 0 {
svc.Priority = priority
}
service.Key = node.Key
result = append(result, service)
svcs = append(svcs, svc)
}
return result, nil
return svcs, nil
}
// SaveService persists service data into etcd
func (c etcdClient) SaveService(service *Service) error {
ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
defer cancel()
value, err := json.Marshal(&service)
if err != nil {
return err
}
_, err = c.api.Set(context.Background(), service.Key, string(value), nil)
_, err = c.client.Put(ctx, service.Key, string(value))
if err != nil {
return err
}
@ -137,9 +150,11 @@ func (c etcdClient) SaveService(service *Service) error {
// DeleteService deletes service record from etcd
func (c etcdClient) DeleteService(key string) error {
_, err := c.api.Delete(context.Background(), key, nil)
return err
ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
defer cancel()
_, err := c.client.Delete(ctx, key)
return err
}
// loads TLS artifacts and builds tls.Clonfig object
@ -186,21 +201,8 @@ func loadRoots(caPath string) (*x509.CertPool, error) {
return roots, nil
}
// constructs http.Transport object for https protocol
func newHTTPSTransport(cc *tls.Config) *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: cc,
}
}
// builds etcd client config depending on connection scheme and TLS parameters
func getETCDConfig() (*etcd.Config, error) {
func getETCDConfig() (*etcdcv3.Config, error) {
etcdURLsStr := os.Getenv("ETCD_URLS")
if etcdURLsStr == "" {
etcdURLsStr = "http://localhost:2379"
@ -208,7 +210,7 @@ func getETCDConfig() (*etcd.Config, error) {
etcdURLs := strings.Split(etcdURLsStr, ",")
firstURL := strings.ToLower(etcdURLs[0])
if strings.HasPrefix(firstURL, "http://") {
return &etcd.Config{Endpoints: etcdURLs}, nil
return &etcdcv3.Config{Endpoints: etcdURLs}, nil
} else if strings.HasPrefix(firstURL, "https://") {
caFile := os.Getenv("ETCD_CA_FILE")
certFile := os.Getenv("ETCD_CERT_FILE")
@ -220,9 +222,9 @@ func getETCDConfig() (*etcd.Config, error) {
if err != nil {
return nil, err
}
return &etcd.Config{
return &etcdcv3.Config{
Endpoints: etcdURLs,
Transport: newHTTPSTransport(tlsConfig),
TLS: tlsConfig,
}, nil
} else {
return nil, errors.New("etcd URLs must start with either http:// or https://")
@ -230,16 +232,16 @@ func getETCDConfig() (*etcd.Config, error) {
}
//newETCDClient is an etcd client constructor
func newETCDClient() (skyDNSClient, error) {
func newETCDClient() (coreDNSClient, error) {
cfg, err := getETCDConfig()
if err != nil {
return nil, err
}
c, err := etcd.New(*cfg)
c, err := etcdcv3.New(*cfg)
if err != nil {
return nil, err
}
return etcdClient{etcd.NewKeysAPI(c)}, nil
return etcdClient{c, context.Background()}, nil
}
// NewCoreDNSProvider is a CoreDNS provider constructor
@ -255,16 +257,16 @@ func NewCoreDNSProvider(domainFilter DomainFilter, dryRun bool) (Provider, error
}, nil
}
// Records returns all DNS records found in SkyDNS/CoreDNS etcd backend. Depending on the record fields
// Records returns all DNS records found in CoreDNS etcd backend. Depending on the record fields
// it may be mapped to one or two records of type A, CNAME, TXT, A+TXT, CNAME+TXT
func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) {
var result []*endpoint.Endpoint
services, err := p.client.GetServices("/skydns")
services, err := p.client.GetServices(coreDNSPrefix)
if err != nil {
return nil, err
}
for _, service := range services {
domains := strings.Split(strings.TrimPrefix(service.Key, "/skydns/"), "/")
domains := strings.Split(strings.TrimPrefix(service.Key, coreDNSPrefix), "/")
reverse(domains)
dnsName := strings.Join(domains[service.TargetStrip:], ".")
if !p.domainFilter.Match(dnsName) {
@ -272,13 +274,14 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) {
}
prefix := strings.Join(domains[:service.TargetStrip], ".")
if service.Host != "" {
ep := endpoint.NewEndpoint(
ep := endpoint.NewEndpointWithTTL(
dnsName,
guessRecordType(service.Host),
endpoint.TTL(service.TTL),
service.Host,
)
ep.Labels["originalText"] = service.Text
ep.Labels["prefix"] = prefix
ep.Labels[randomPrefixLabel] = prefix
result = append(result, ep)
}
if service.Text != "" {
@ -287,20 +290,21 @@ func (p coreDNSProvider) Records() ([]*endpoint.Endpoint, error) {
endpoint.RecordTypeTXT,
service.Text,
)
ep.Labels["prefix"] = prefix
ep.Labels[randomPrefixLabel] = prefix
result = append(result, ep)
}
}
return result, nil
}
// ApplyChanges stores changes back to etcd converting them to SkyDNS format and aggregating A/CNAME and TXT records
// ApplyChanges stores changes back to etcd converting them to CoreDNS format and aggregating A/CNAME and TXT records
func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error {
grouped := map[string][]*endpoint.Endpoint{}
for _, ep := range changes.Create {
grouped[ep.DNSName] = append(grouped[ep.DNSName], ep)
}
for _, ep := range changes.UpdateNew {
for i, ep := range changes.UpdateNew {
ep.Labels[randomPrefixLabel] = changes.UpdateOld[i].Labels[randomPrefixLabel]
grouped[ep.DNSName] = append(grouped[ep.DNSName], ep)
}
for dnsName, group := range grouped {
@ -313,7 +317,7 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error {
if ep.RecordType == endpoint.RecordTypeTXT {
continue
}
prefix := ep.Labels["prefix"]
prefix := ep.Labels[randomPrefixLabel]
if prefix == "" {
prefix = fmt.Sprintf("%08x", rand.Int31())
}
@ -322,6 +326,7 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error {
Text: ep.Labels["originalText"],
Key: etcdKeyFor(prefix + "." + dnsName),
TargetStrip: strings.Count(prefix, ".") + 1,
TTL: uint32(ep.RecordTTL),
}
services = append(services, service)
}
@ -331,13 +336,14 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error {
continue
}
if index >= len(services) {
prefix := ep.Labels["prefix"]
prefix := ep.Labels[randomPrefixLabel]
if prefix == "" {
prefix = fmt.Sprintf("%08x", rand.Int31())
}
services = append(services, Service{
Key: etcdKeyFor(prefix + "." + dnsName),
TargetStrip: strings.Count(prefix, ".") + 1,
TTL: uint32(ep.RecordTTL),
})
}
services[index].Text = ep.Targets[0]
@ -349,7 +355,7 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error {
}
for _, service := range services {
log.Infof("Add/set key %s to Host=%s, Text=%s", service.Key, service.Host, service.Text)
log.Infof("Add/set key %s to Host=%s, Text=%s, TTL=%d", service.Key, service.Host, service.Text, service.TTL)
if !p.dryRun {
err := p.client.SaveService(&service)
if err != nil {
@ -361,8 +367,8 @@ func (p coreDNSProvider) ApplyChanges(changes *plan.Changes) error {
for _, ep := range changes.Delete {
dnsName := ep.DNSName
if ep.Labels["prefix"] != "" {
dnsName = ep.Labels["prefix"] + "." + dnsName
if ep.Labels[randomPrefixLabel] != "" {
dnsName = ep.Labels[randomPrefixLabel] + "." + dnsName
}
key := etcdKeyFor(dnsName)
log.Infof("Delete key %s", key)
@ -387,7 +393,7 @@ func guessRecordType(target string) string {
func etcdKeyFor(dnsName string) string {
domains := strings.Split(dnsName, ".")
reverse(domains)
return "/skydns/" + strings.Join(domains, "/")
return coreDNSPrefix + strings.Join(domains, "/")
}
func reverse(slice []string) {

View File

@ -235,8 +235,6 @@ func TestCoreDNSApplyChanges(t *testing.T) {
}
validateServices(client.services, expectedServices1, t, 1)
updatedEp := endpoint.NewEndpoint("domain1.local", endpoint.RecordTypeA, "6.6.6.6")
updatedEp.Labels["originalText"] = "string1"
changes2 := &plan.Changes{
Create: []*endpoint.Endpoint{
endpoint.NewEndpoint("domain3.local", endpoint.RecordTypeA, "7.7.7.7"),
@ -245,6 +243,12 @@ func TestCoreDNSApplyChanges(t *testing.T) {
endpoint.NewEndpoint("domain1.local", "A", "6.6.6.6"),
},
}
records, _ := coredns.Records()
for _, ep := range records {
if ep.DNSName == "domain1.local" {
changes2.UpdateOld = append(changes2.UpdateOld, ep)
}
}
applyServiceChanges(coredns, changes2)
expectedServices2 := map[string]*Service{

View File

@ -564,7 +564,7 @@ func (d *dynProviderState) commit(client *dynect.Client) error {
err = apiRetryLoop(func() error {
return client.Do("PUT", fmt.Sprintf("Zone/%s/", zone), &zonePublish, &response)
})
log.Infof("Commiting changes for zone %s: %+v", zone, errorOrValue(err, &response))
log.Infof("Committing changes for zone %s: %+v", zone, errorOrValue(err, &response))
}
switch len(errs) {
@ -597,7 +597,7 @@ func (d *dynProviderState) Records() ([]*endpoint.Endpoint, error) {
serial, err := d.fetchZoneSerial(client, zone)
if err != nil {
if strings.Index(err.Error(), "404 Not Found") >= 0 {
log.Infof("Ignore zone %s as it does not exists", zone)
log.Infof("Ignore zone %s as it does not exist", zone)
continue
}

View File

@ -290,7 +290,7 @@ func operationsByZone(zones map[string]*dns.ZoneSummary, ops []dns.RecordOperati
}
}
// Remove zones that don't have have any changes.
// Remove zones that don't have any changes.
for zone, ops := range changes {
if len(ops) == 0 {
delete(changes, zone)

View File

@ -132,15 +132,17 @@ func stringifyHTTPResponseBody(r *http.Response) (body string) {
// well as mock APIClients used in testing
type PDNSAPIProvider interface {
ListZones() ([]pgo.Zone, *http.Response, error)
PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone)
ListZone(zoneID string) (pgo.Zone, *http.Response, error)
PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error)
}
// PDNSAPIClient : Struct that encapsulates all the PowerDNS specific implementation details
type PDNSAPIClient struct {
dryRun bool
authCtx context.Context
client *pgo.APIClient
dryRun bool
authCtx context.Context
client *pgo.APIClient
domainFilter DomainFilter
}
// ListZones : Method returns all enabled zones from PowerDNS
@ -153,7 +155,6 @@ func (c *PDNSAPIClient) ListZones() (zones []pgo.Zone, resp *http.Response, err
log.Debugf("Retrying ListZones() ... %d", i)
time.Sleep(retryAfterTime * (1 << uint(i)))
continue
}
return zones, resp, err
}
@ -163,6 +164,22 @@ func (c *PDNSAPIClient) ListZones() (zones []pgo.Zone, resp *http.Response, err
}
// PartitionZones : Method returns a slice of zones that adhere to the domain filter and a slice of ones that does not adhere to the filter
func (c *PDNSAPIClient) PartitionZones(zones []pgo.Zone) (filteredZones []pgo.Zone, residualZones []pgo.Zone) {
if c.domainFilter.IsConfigured() {
for _, zone := range zones {
if c.domainFilter.Match(zone.Name) {
filteredZones = append(filteredZones, zone)
} else {
residualZones = append(residualZones, zone)
}
}
} else {
residualZones = zones
}
return filteredZones, residualZones
}
// ListZone : Method returns the details of a specific zone from PowerDNS
// ref: https://doc.powerdns.com/authoritative/http-api/zone.html#get--servers-server_id-zones-zone_id
func (c *PDNSAPIClient) ListZone(zoneID string) (zone pgo.Zone, resp *http.Response, err error) {
@ -216,10 +233,6 @@ func NewPDNSProvider(config PDNSConfig) (*PDNSProvider, error) {
return nil, errors.New("Missing API Key for PDNS. Specify using --pdns-api-key=")
}
// The default for when no --domain-filter is passed is [""], instead of [], so we check accordingly.
if len(config.DomainFilter.filters) != 1 && config.DomainFilter.filters[0] != "" {
return nil, errors.New("PDNS Provider does not support domain filter")
}
// We do not support dry running, exit safely instead of surprising the user
// TODO: Add Dry Run support
if config.DryRun {
@ -238,9 +251,10 @@ func NewPDNSProvider(config PDNSConfig) (*PDNSProvider, error) {
provider := &PDNSProvider{
client: &PDNSAPIClient{
dryRun: config.DryRun,
authCtx: context.WithValue(context.TODO(), pgo.ContextAPIKey, pgo.APIKey{Key: config.APIKey}),
client: pgo.NewAPIClient(pdnsClientConfig),
dryRun: config.DryRun,
authCtx: context.WithValue(context.TODO(), pgo.ContextAPIKey, pgo.APIKey{Key: config.APIKey}),
client: pgo.NewAPIClient(pdnsClientConfig),
domainFilter: config.DomainFilter,
},
}
@ -281,22 +295,23 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
if err != nil {
return nil, err
}
filteredZones, residualZones := p.client.PartitionZones(zones)
// Sort the zone by length of the name in descending order, we use this
// property later to ensure we add a record to the longest matching zone
sort.SliceStable(zones, func(i, j int) bool { return len(zones[i].Name) > len(zones[j].Name) })
sort.SliceStable(filteredZones, func(i, j int) bool { return len(filteredZones[i].Name) > len(filteredZones[j].Name) })
// NOTE: Complexity of this loop is O(Zones*Endpoints).
// NOTE: Complexity of this loop is O(FilteredZones*Endpoints).
// A possibly faster implementation would be a search of the reversed
// DNSName in a trie of Zone names, which should be O(Endpoints), but at this point it's not
// necessary.
for _, zone := range zones {
for _, zone := range filteredZones {
zone.Rrsets = []pgo.RrSet{}
for i := 0; i < len(endpoints); {
ep := endpoints[i]
dnsname := ensureTrailingDot(ep.DNSName)
if strings.HasSuffix(dnsname, zone.Name) {
if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) {
// The assumption here is that there will only ever be one target
// per (ep.DNSName, ep.RecordType) tuple, which holds true for
// external-dns v5.0.0-alpha onwards
@ -321,7 +336,7 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
return nil, errors.New("Value of record TTL overflows, limited to int32")
}
if ep.RecordTTL == 0 {
// No TTL was sepecified for the record, we use the default
// No TTL was specified for the record, we use the default
rrset.Ttl = int32(defaultTTL)
} else {
rrset.Ttl = int32(ep.RecordTTL)
@ -345,7 +360,23 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
}
// If we still have some endpoints left, it means we couldn't find a matching zone for them
// residualZones is unsorted by name length like its counterpart
// since we only care to remove endpoints that do not match domain filter
for _, zone := range residualZones {
for i := 0; i < len(endpoints); {
ep := endpoints[i]
dnsname := ensureTrailingDot(ep.DNSName)
if dnsname == zone.Name || strings.HasSuffix(dnsname, "."+zone.Name) {
// "pop" endpoint if it's matched to a residual zone... essentially a no-op
log.Debugf("Ignoring Endpoint because it was matched to a zone that was not specified within Domain Filter(s): %s", dnsname)
endpoints = append(endpoints[0:i], endpoints[i+1:]...)
} else {
i++
}
}
}
// If we still have some endpoints left, it means we couldn't find a matching zone (filtered or residual) for them
// We warn instead of hard fail here because we don't want a misconfig to cause everything to go down
if len(endpoints) > 0 {
log.Warnf("No matching zones were found for the following endpoints: %+v", endpoints)
@ -387,8 +418,9 @@ func (p *PDNSProvider) Records() (endpoints []*endpoint.Endpoint, _ error) {
if err != nil {
return nil, err
}
filteredZones, _ := p.client.PartitionZones(zones)
for _, zone := range zones {
for _, zone := range filteredZones {
z, _, err := p.client.ListZone(zone.Id)
if err != nil {
log.Warnf("Unable to fetch Records")

View File

@ -158,6 +158,18 @@ var (
endpoint.NewEndpointWithTTL("abcd.mock.noexist", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"),
endpoint.NewEndpointWithTTL("abcd.mock.noexist", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
}
endpointsMultipleZonesWithLongRecordNotInDomainFilter = []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"),
endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
}
endpointsMultipleZonesWithSimilarRecordNotInDomainFilter = []*endpoint.Endpoint{
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
endpoint.NewEndpointWithTTL("test.simexample.com", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"),
endpoint.NewEndpointWithTTL("test.simexample.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""),
}
ZoneEmpty = pgo.Zone{
// Opaque zone id (string), assigned by the server, should not be interpreted by the application. Guaranteed to be safe for embedding in URLs.
@ -174,6 +186,15 @@ var (
Rrsets: []pgo.RrSet{},
}
ZoneEmptySimilar = pgo.Zone{
Id: "simexample.com.",
Name: "simexample.com.",
Type_: "Zone",
Url: "/api/v1/servers/localhost/zones/simexample.com.",
Kind: "Native",
Rrsets: []pgo.RrSet{},
}
ZoneEmptyLong = pgo.Zone{
Id: "long.domainname.example.com.",
Name: "long.domainname.example.com.",
@ -239,6 +260,72 @@ var (
},
}
ZoneEmptyToSimplePatchLongRecordIgnoredInDomainFilter = pgo.Zone{
Id: "example.com.",
Name: "example.com.",
Type_: "Zone",
Url: "/api/v1/servers/localhost/zones/example.com.",
Kind: "Native",
Rrsets: []pgo.RrSet{
{
Name: "a.very.long.domainname.example.com.",
Type_: "A",
Ttl: 300,
Changetype: "REPLACE",
Records: []pgo.Record{
{
Content: "9.9.9.9",
Disabled: false,
SetPtr: false,
},
},
Comments: []pgo.Comment(nil),
},
{
Name: "a.very.long.domainname.example.com.",
Type_: "TXT",
Ttl: 300,
Changetype: "REPLACE",
Records: []pgo.Record{
{
Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"",
Disabled: false,
SetPtr: false,
},
},
Comments: []pgo.Comment(nil),
},
{
Name: "example.com.",
Type_: "A",
Ttl: 300,
Changetype: "REPLACE",
Records: []pgo.Record{
{
Content: "8.8.8.8",
Disabled: false,
SetPtr: false,
},
},
Comments: []pgo.Comment(nil),
},
{
Name: "example.com.",
Type_: "TXT",
Ttl: 300,
Changetype: "REPLACE",
Records: []pgo.Record{
{
Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"",
Disabled: false,
SetPtr: false,
},
},
Comments: []pgo.Comment(nil),
},
},
}
ZoneEmptyToLongPatch = pgo.Zone{
Id: "long.domainname.example.com.",
Name: "long.domainname.example.com.",
@ -398,6 +485,9 @@ type PDNSAPIClientStub struct {
func (c *PDNSAPIClientStub) ListZones() ([]pgo.Zone, *http.Response, error) {
return []pgo.Zone{ZoneMixed}, nil, nil
}
func (c *PDNSAPIClientStub) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) {
return zones, nil
}
func (c *PDNSAPIClientStub) ListZone(zoneID string) (pgo.Zone, *http.Response, error) {
return ZoneMixed, nil, nil
}
@ -415,6 +505,9 @@ type PDNSAPIClientStubEmptyZones struct {
func (c *PDNSAPIClientStubEmptyZones) ListZones() ([]pgo.Zone, *http.Response, error) {
return []pgo.Zone{ZoneEmpty, ZoneEmptyLong, ZoneEmpty2}, nil, nil
}
func (c *PDNSAPIClientStubEmptyZones) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) {
return zones, nil
}
func (c *PDNSAPIClientStubEmptyZones) ListZone(zoneID string) (pgo.Zone, *http.Response, error) {
if strings.Contains(zoneID, "example.com") {
@ -422,7 +515,7 @@ func (c *PDNSAPIClientStubEmptyZones) ListZone(zoneID string) (pgo.Zone, *http.R
} else if strings.Contains(zoneID, "mock.test") {
return ZoneEmpty2, nil, nil
} else if strings.Contains(zoneID, "long.domainname.example.com") {
return ZoneEmpty2, nil, nil
return ZoneEmptyLong, nil, nil
}
return pgo.Zone{}, nil, nil
@ -469,6 +562,37 @@ func (c *PDNSAPIClientStubListZonesFailure) ListZones() ([]pgo.Zone, *http.Respo
return []pgo.Zone{}, nil, errors.New("Generic PDNS Error")
}
/******************************************************************************/
// API that returns zone partitions given DomainFilter(s)
type PDNSAPIClientStubPartitionZones struct {
// Anonymous struct for composition
PDNSAPIClientStubEmptyZones
}
func (c *PDNSAPIClientStubPartitionZones) ListZones() ([]pgo.Zone, *http.Response, error) {
return []pgo.Zone{ZoneEmpty, ZoneEmptyLong, ZoneEmpty2, ZoneEmptySimilar}, nil, nil
}
func (c *PDNSAPIClientStubPartitionZones) ListZone(zoneID string) (pgo.Zone, *http.Response, error) {
if strings.Contains(zoneID, "example.com") {
return ZoneEmpty, nil, nil
} else if strings.Contains(zoneID, "mock.test") {
return ZoneEmpty2, nil, nil
} else if strings.Contains(zoneID, "long.domainname.example.com") {
return ZoneEmptyLong, nil, nil
} else if strings.Contains(zoneID, "simexample.com") {
return ZoneEmptySimilar, nil, nil
}
return pgo.Zone{}, nil, nil
}
// Just overwrite the ListZones method to introduce a failure
func (c *PDNSAPIClientStubPartitionZones) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) {
return []pgo.Zone{ZoneEmpty}, []pgo.Zone{ZoneEmptyLong, ZoneEmpty2}
}
/******************************************************************************/
type NewPDNSProviderTestSuite struct {
@ -488,7 +612,7 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreate() {
APIKey: "foo",
DomainFilter: NewDomainFilter([]string{"example.com", "example.org"}),
})
assert.Error(suite.T(), err, "--domainfilter should raise an error")
assert.Nil(suite.T(), err, "--domain-filter should raise no error")
_, err = NewPDNSProvider(PDNSConfig{
Server: "http://localhost:8081",
@ -711,6 +835,51 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZones() {
}
}
func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZonesPartitionZones() {
// Test DomainFilters
p := &PDNSProvider{
client: &PDNSAPIClientStubPartitionZones{},
}
// Check inserting endpoints from a single zone which is specified in DomainFilter
zlist, err := p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsReplace)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist)
// Check deleting endpoints from a single zone which is specified in DomainFilter
zlist, err = p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsDelete)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimpleDelete}, zlist)
// Check endpoints from multiple zones # which one is specified in DomainFilter and one is not
zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZones, PdnsReplace)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist)
// Check endpoints from multiple zones where some endpoints which don't exist and one that does
// and is part of DomainFilter
zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithNoExist, PdnsReplace)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist)
// Check endpoints from a zone that does not exist
zlist, err = p.ConvertEndpointsToZones(endpointsNonexistantZone, PdnsReplace)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), []pgo.Zone{}, zlist)
// Check endpoints that match multiple zones (one longer than other), is assigned to the right zone when the longer
// zone is not part of the DomainFilter
zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithLongRecordNotInDomainFilter, PdnsReplace)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatchLongRecordIgnoredInDomainFilter}, zlist)
// Check endpoints that match multiple zones (one longer than other and one is very similar)
// is assigned to the right zone when the similar zone is not part of the DomainFilter
zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithSimilarRecordNotInDomainFilter, PdnsReplace)
assert.Nil(suite.T(), err)
assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist)
}
func (suite *NewPDNSProviderTestSuite) TestPDNSmutateRecords() {
// Function definition: mutateRecords(endpoints []*endpoint.Endpoint, changetype pdnsChangeType) error
@ -742,6 +911,7 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSmutateRecords() {
assert.NotNil(suite.T(), err)
}
func TestNewPDNSProviderTestSuite(t *testing.T) {
suite.Run(t, new(NewPDNSProviderTestSuite))
}

View File

@ -0,0 +1,128 @@
#!/usr/bin/env python
# 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.
# This is a script that we wrote to try to help the migration over to using external-dns.
# This script looks at kubernetes ingresses and services (which are the two things we have
# external-dns looking at) and compares them to existing TXT and A records in route53 to
# find out where there are gaps. It then assigns the heritage and owner TXT records where
# needed so external-dns can take over managing those resources. You can modify the script
# to only look at one or the other if needed.
#
# pip install kubernetes boto3
import boto3
from kubernetes import client, config
# replace with your hosted zone id
hosted_zone_id = ''
# replace with your txt-owner-id you are using
# inside of your external-dns controller
txt_owner_id = ''
# change to false if you have external-dns not looking at services
external_dns_manages_services = True
# change to false if you have external-dns not looking at ingresses
external_dns_manages_ingresses = True
config.load_kube_config()
# grab all the domains that k8s thinks it is going to
# manage (services with domainName specified and
# ingress hosts)
k8s_domains = []
if external_dns_manages_services:
v1 = client.CoreV1Api()
svcs = v1.list_service_for_all_namespaces()
for i in svcs.items:
annotations = i.metadata.annotations
if annotations is not None and 'domainName' in annotations:
k8s_domains.extend(annotations['domainName'].split(','))
if external_dns_manages_ingresses:
ev1 = client.ExtensionsV1beta1Api()
ings = ev1.list_ingress_for_all_namespaces()
for i in ings.items:
for r in i.spec.rules:
if r.host not in k8s_domains:
k8s_domains.append(r.host)
r53client = boto3.client('route53')
# grab the existing route53 domains and identify gaps where a domain may be
# missing a txt record pair
existing_r53_txt_domains=[]
existing_r53_domains=[]
has_next = True
next_record_name, next_record_type='',''
while has_next:
if next_record_name is not '' and next_record_type is not '':
resource_records = r53client.list_resource_record_sets(HostedZoneId=hosted_zone_id,
StartRecordName=next_record_name,
StartRecordType=next_record_type)
else:
resource_records = r53client.list_resource_record_sets(HostedZoneId=hosted_zone_id)
for r in resource_records['ResourceRecordSets']:
if r['Type'] == 'TXT':
existing_r53_txt_domains.append(r['Name'][:-1])
elif r['Type'] == 'A':
existing_r53_domains.append(r['Name'][:-1])
has_next = resource_records['IsTruncated']
if has_next:
next_record_name, next_record_type = resource_records['NextRecordName'], resource_records['NextRecordType']
# grab only the domains in route53 that kubernetes is managing
r53_k8s_domains = [r for r in k8s_domains if r in existing_r53_domains]
# from those find the ones that do not have matching txt entries
missing_k8s_txt = [r for r in r53_k8s_domains if r not in existing_r53_txt_domains]
# make the change batch for the route53 call, modify this as needed
change_batch=[]
for r in missing_k8s_txt:
change_batch.append(
{
'Action': 'CREATE',
'ResourceRecordSet': {
'Name': r,
'Type': 'TXT',
'TTL': 300,
'ResourceRecords': [
{
'Value': '\heritage=external-dns,owner="' + txt_owner_id + '\"'
},
]
}
})
print('This will create the following resources')
print(change_batch)
response = input("Good to go? ")
if response.lower() in ['y', 'yes', 'yup', 'ok', 'sure', 'why not', 'why not?']:
print('Updating route53')
change_response = r53client.change_resource_record_sets(
HostedZoneId=hosted_zone_id,
ChangeBatch={
'Changes': change_batch
})
print('Submitted change request to route53. Details below.')
print(change_response)
else:
print('No changes were made')

View File

@ -175,12 +175,14 @@ func (sc *gatewaySource) endpointsFromTemplate(config *istiomodel.Config) ([]*en
}
}
providerSpecific := getProviderSpecificAnnotations(config.Annotations)
var endpoints []*endpoint.Endpoint
// splits the FQDN template and removes the trailing periods
hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",")
for _, hostname := range hostnameList {
hostname = strings.TrimSuffix(hostname, ".")
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...)
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
}
return endpoints, nil
}
@ -259,12 +261,14 @@ func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([
gateway := config.Spec.(*istionetworking.Gateway)
providerSpecific := getProviderSpecificAnnotations(config.Annotations)
for _, server := range gateway.Servers {
for _, host := range server.Hosts {
if host == "" {
continue
}
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl)...)
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...)
}
}
@ -272,7 +276,7 @@ func (sc *gatewaySource) endpointsFromGatewayConfig(config istiomodel.Config) ([
if !sc.ignoreHostnameAnnotation {
hostnameList := getHostnamesFromAnnotations(config.Annotations)
for _, hostname := range hostnameList {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...)
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
}
}

View File

@ -148,12 +148,14 @@ func (sc *ingressSource) endpointsFromTemplate(ing *v1beta1.Ingress) ([]*endpoin
targets = targetsFromIngressStatus(ing.Status)
}
providerSpecific := getProviderSpecificAnnotations(ing.Annotations)
var endpoints []*endpoint.Endpoint
// splits the FQDN template and removes the trailing periods
hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",")
for _, hostname := range hostnameList {
hostname = strings.TrimSuffix(hostname, ".")
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...)
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
}
return endpoints, nil
}
@ -210,11 +212,13 @@ func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool) [
targets = targetsFromIngressStatus(ing.Status)
}
providerSpecific := getProviderSpecificAnnotations(ing.Annotations)
for _, rule := range ing.Spec.Rules {
if rule.Host == "" {
continue
}
endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl)...)
endpoints = append(endpoints, endpointsForHostname(rule.Host, targets, ttl, providerSpecific)...)
}
for _, tls := range ing.Spec.TLS {
@ -222,7 +226,7 @@ func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool) [
if host == "" {
continue
}
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl)...)
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...)
}
}
@ -230,7 +234,7 @@ func endpointsFromIngress(ing *v1beta1.Ingress, ignoreHostnameAnnotation bool) [
if !ignoreHostnameAnnotation {
hostnameList := getHostnamesFromAnnotations(ing.Annotations)
for _, hostname := range hostnameList {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl)...)
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
}
}
return endpoints

View File

@ -820,6 +820,52 @@ func testIngressEndpoints(t *testing.T) {
},
},
},
{
title: "ingress rules with alias and target annotation",
targetNamespace: "",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
targetAnnotationKey: "ingress-target.com",
aliasAnnotationKey: "true",
},
dnsnames: []string{"example.org"},
ips: []string{},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Targets: endpoint.Targets{"ingress-target.com"},
RecordType: endpoint.RecordTypeCNAME,
},
},
},
{
title: "ingress rules with alias set false and target annotation",
targetNamespace: "",
ingressItems: []fakeIngress{
{
name: "fake1",
namespace: namespace,
annotations: map[string]string{
targetAnnotationKey: "ingress-target.com",
aliasAnnotationKey: "false",
},
dnsnames: []string{"example.org"},
ips: []string{},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "example.org",
Targets: endpoint.Targets{"ingress-target.com"},
RecordType: endpoint.RecordTypeCNAME,
},
},
},
{
title: "template for ingress with annotation",
targetNamespace: "",

View File

@ -35,6 +35,8 @@ const (
targetAnnotationKey = "external-dns.alpha.kubernetes.io/target"
// The annotation used for defining the desired DNS record TTL
ttlAnnotationKey = "external-dns.alpha.kubernetes.io/ttl"
// The annotation used for switching to the alias record types e. g. AWS Alias records instead of a normal CNAME
aliasAnnotationKey = "external-dns.alpha.kubernetes.io/alias"
// The value of the controller annotation so that we feel responsible
controllerAnnotationValue = "dns-controller"
)
@ -74,6 +76,18 @@ func getHostnamesFromAnnotations(annotations map[string]string) []string {
return strings.Split(strings.Replace(hostnameAnnotation, " ", "", -1), ",")
}
func getAliasFromAnnotations(annotations map[string]string) bool {
aliasAnnotation, exists := annotations[aliasAnnotationKey]
return exists && aliasAnnotation == "true"
}
func getProviderSpecificAnnotations(annotations map[string]string) endpoint.ProviderSpecific {
if getAliasFromAnnotations(annotations) {
return map[string]string{"alias": "true"}
}
return map[string]string{}
}
// getTargetsFromTargetAnnotation gets endpoints from optional "target" annotation.
// Returns empty endpoints array if none are found.
func getTargetsFromTargetAnnotation(annotations map[string]string) endpoint.Targets {
@ -102,7 +116,7 @@ func suitableType(target string) string {
}
// endpointsForHostname returns the endpoint objects for each host-target combination.
func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL) []*endpoint.Endpoint {
func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoint.TTL, providerSpecific endpoint.ProviderSpecific) []*endpoint.Endpoint {
var endpoints []*endpoint.Endpoint
var aTargets endpoint.Targets
@ -119,22 +133,24 @@ func endpointsForHostname(hostname string, targets endpoint.Targets, ttl endpoin
if len(aTargets) > 0 {
epA := &endpoint.Endpoint{
DNSName: strings.TrimSuffix(hostname, "."),
Targets: aTargets,
RecordTTL: ttl,
RecordType: endpoint.RecordTypeA,
Labels: endpoint.NewLabels(),
DNSName: strings.TrimSuffix(hostname, "."),
Targets: aTargets,
RecordTTL: ttl,
RecordType: endpoint.RecordTypeA,
Labels: endpoint.NewLabels(),
ProviderSpecific: providerSpecific,
}
endpoints = append(endpoints, epA)
}
if len(cnameTargets) > 0 {
epCNAME := &endpoint.Endpoint{
DNSName: strings.TrimSuffix(hostname, "."),
Targets: cnameTargets,
RecordTTL: ttl,
RecordType: endpoint.RecordTypeCNAME,
Labels: endpoint.NewLabels(),
DNSName: strings.TrimSuffix(hostname, "."),
Targets: cnameTargets,
RecordTTL: ttl,
RecordType: endpoint.RecordTypeCNAME,
Labels: endpoint.NewLabels(),
ProviderSpecific: providerSpecific,
}
endpoints = append(endpoints, epCNAME)
}