mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 17:46:57 +02:00
Merge branch 'master' of github.com:kubernetes-incubator/external-dns into feature/ignore-annotations
This commit is contained in:
commit
230113c7b8
@ -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
158
Gopkg.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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"}},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
||||
```
|
||||
|
@ -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...
|
||||
|
@ -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:
|
||||
|
@ -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")
|
||||
|
28
plan/plan.go
28
plan/plan.go
@ -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 {
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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{
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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))
|
||||
}
|
||||
|
128
scripts/update_route53_k8s_txt_owner.py
Normal file
128
scripts/update_route53_k8s_txt_owner.py
Normal 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')
|
@ -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)...)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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: "",
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user