mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
Merge branch 'master' into master
This commit is contained in:
commit
3b085c5fcc
@ -39,6 +39,7 @@ Providers
|
||||
- [ ] Azure
|
||||
- [ ] Cloudflare
|
||||
- [x] DigitalOcean
|
||||
- [x] DNSimple
|
||||
- [x] Google
|
||||
- [ ] InMemory
|
||||
- [x] Linode
|
||||
@ -61,6 +62,9 @@ This value is a constant in the provider code.
|
||||
The DigitalOcean Provider overrides the value to 300s when the TTL is 0.
|
||||
This value is a constant in the provider code.
|
||||
|
||||
### DNSimple Provider
|
||||
The DNSimple Provider default TTL is used when the TTL is 0. The default TTL is 3600s.
|
||||
|
||||
### Google Provider
|
||||
Previously with the Google Provider, TTL's were hard-coded to 300s.
|
||||
For safety, the Google Provider overrides the value to 300s when the TTL is 0.
|
||||
|
@ -139,12 +139,12 @@ The following are relevant snippets from that tutorial.
|
||||
#### Install a sample service
|
||||
With automatic sidecar injection:
|
||||
```bash
|
||||
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.0/samples/httpbin/httpbin.yaml
|
||||
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.6/samples/httpbin/httpbin.yaml
|
||||
```
|
||||
|
||||
Otherwise:
|
||||
```bash
|
||||
$ kubectl apply -f <(istioctl kube-inject -f https://raw.githubusercontent.com/istio/istio/release-1.0/samples/httpbin/httpbin.yaml)
|
||||
$ kubectl apply -f <(istioctl kube-inject -f https://raw.githubusercontent.com/istio/istio/release-1.6/samples/httpbin/httpbin.yaml)
|
||||
```
|
||||
|
||||
#### Create an Istio Gateway:
|
||||
|
9
go.mod
9
go.mod
@ -20,13 +20,14 @@ require (
|
||||
github.com/cloudflare/cloudflare-go v0.10.1
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba
|
||||
github.com/digitalocean/godo v1.34.0
|
||||
github.com/digitalocean/godo v1.36.0
|
||||
github.com/dnsimple/dnsimple-go v0.60.0
|
||||
github.com/exoscale/egoscale v0.18.1
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
|
||||
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b // indirect
|
||||
github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||
github.com/google/go-cmp v0.4.1
|
||||
github.com/gophercloud/gophercloud v0.1.0
|
||||
github.com/gorilla/mux v1.7.4 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
@ -57,11 +58,11 @@ require (
|
||||
github.com/transip/gotransip v5.8.2+incompatible
|
||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92
|
||||
github.com/vultr/govultr v0.3.2
|
||||
github.com/vultr/govultr v0.4.2
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875
|
||||
go.uber.org/ratelimit v0.1.0
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
google.golang.org/api v0.15.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
|
25
go.sum
25
go.sum
@ -108,7 +108,6 @@ github.com/cloudflare/cloudflare-go v0.10.1 h1:d2CL6F9k2O0Ux0w27LgogJ5UOzZRj6a/h
|
||||
github.com/cloudflare/cloudflare-go v0.10.1/go.mod h1:C0Y6eWnTJPMK2ceuOxx2pjh78UUHihcXeTTHb8r7QjU=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14=
|
||||
github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533 h1:8wZizuKuZVu5COB7EsBYxBQz8nRcXXn5d4Gt91eJLvU=
|
||||
github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
@ -140,8 +139,8 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/digitalocean/godo v1.34.0 h1:OXJhLLJS2VTB5SziTyCq8valKVZ0uBHCFQsDW3/HF78=
|
||||
github.com/digitalocean/godo v1.34.0/go.mod h1:gfLm3JSupWD9V/ibQygXWW3IVz7hranzckH5UimhZsI=
|
||||
github.com/digitalocean/godo v1.36.0 h1:eRF8wNzHZyU7/wI3De/MQgiVSWdseDaf27bXj2gnOO0=
|
||||
github.com/digitalocean/godo v1.36.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU=
|
||||
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
|
||||
@ -163,7 +162,6 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT
|
||||
github.com/envoyproxy/go-control-plane v0.8.2/go.mod h1:EWRTAFN6uuDZIa6KOuUfrOMJ7ySgXZ44rVKiTWjKe34=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0 h1:67WMNTvGrl7V1dWdKCeTwxDr7nio9clKoTlLhwIPnT4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.5 h1:lRJIqDD8yjV1YyPRqecMdytjDLs2fTXq363aCib5xPU=
|
||||
github.com/envoyproxy/go-control-plane v0.9.5/go.mod h1:OXl5to++W0ctG+EHWTFUjiypVxC/Y4VLc/KFU+al13s=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.0.0-20190405222122-d6164de49109/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
@ -273,6 +271,8 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f h1:kSqKc8ouCLIBHqdj9a9xxhtxlZhNqbePClixA4HoM44=
|
||||
github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:YCHYtYb9c8Q7XgYVYjmJBPtFPKx5QvOcPxHZWjldabE=
|
||||
github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@ -286,6 +286,8 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-github v15.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
@ -336,8 +338,8 @@ github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVo
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.4 h1:BbgctKO892xEyOXnGiaAwIoSq1QZ/SS4AhjoAh9DnfY=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
|
||||
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
|
||||
@ -600,8 +602,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
|
||||
github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVUZZQ=
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92 h1:Q76MzqJu++vAfhj0mVf7t0F4xHUbg+V/d/Uk5PBQjRU=
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20190611170422-7119fe55ed92/go.mod h1:AZuEfReFWdvtU0LatbLpo70t3lqdLvph2D5mqFP0bkA=
|
||||
github.com/vultr/govultr v0.3.2 h1:1tV/88jkm+4Y345qAXBe3peNbnmvCY/VAIZApklbKkI=
|
||||
github.com/vultr/govultr v0.3.2/go.mod h1:81RwK1wAmb08alkFDJiZmu9gdv+IO+UamzaF0+PIieE=
|
||||
github.com/vultr/govultr v0.4.2 h1:9i8xKZ+xp6vwZ9raqHoBLzhB4wCnMj7nOQTj5YIRLWY=
|
||||
github.com/vultr/govultr v0.4.2/go.mod h1:TUuUizMOFc7z+PNMssb6iGjKjQfpw5arIaOLfocVudQ=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
@ -703,12 +705,15 @@ golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8ou
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -750,6 +755,8 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd h1:3x5uuvBgE6oaXJjCOvpCC1IpgJogqQ+PqGGU3ZxAgII=
|
||||
golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -15,3 +15,6 @@ rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["endpoints"]
|
||||
verbs: ["get","watch","list"]
|
||||
|
1
main.go
1
main.go
@ -112,7 +112,6 @@ func main() {
|
||||
KubeConfig: cfg.KubeConfig,
|
||||
KubeMaster: cfg.Master,
|
||||
ServiceTypeFilter: cfg.ServiceTypeFilter,
|
||||
IstioIngressGatewayServices: cfg.IstioIngressGatewayServices,
|
||||
CFAPIEndpoint: cfg.CFAPIEndpoint,
|
||||
CFUsername: cfg.CFUsername,
|
||||
CFPassword: cfg.CFPassword,
|
||||
|
@ -41,7 +41,6 @@ type Config struct {
|
||||
Master string
|
||||
KubeConfig string
|
||||
RequestTimeout time.Duration
|
||||
IstioIngressGatewayServices []string
|
||||
ContourLoadBalancerService string
|
||||
SkipperRouteGroupVersion string
|
||||
Sources []string
|
||||
@ -148,7 +147,6 @@ var defaultConfig = &Config{
|
||||
Master: "",
|
||||
KubeConfig: "",
|
||||
RequestTimeout: time.Second * 30,
|
||||
IstioIngressGatewayServices: []string{"istio-system/istio-ingressgateway"},
|
||||
ContourLoadBalancerService: "heptio-contour/contour",
|
||||
SkipperRouteGroupVersion: "zalando.org/v1",
|
||||
Sources: nil,
|
||||
|
@ -32,13 +32,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// DigitalOceanCreate is a ChangeAction enum value
|
||||
DigitalOceanCreate = "CREATE"
|
||||
// DigitalOceanDelete is a ChangeAction enum value
|
||||
DigitalOceanDelete = "DELETE"
|
||||
// DigitalOceanUpdate is a ChangeAction enum value
|
||||
DigitalOceanUpdate = "UPDATE"
|
||||
|
||||
// digitalOceanRecordTTL is the default TTL value
|
||||
digitalOceanRecordTTL = 300
|
||||
)
|
||||
@ -54,10 +47,31 @@ type DigitalOceanProvider struct {
|
||||
DryRun bool
|
||||
}
|
||||
|
||||
// DigitalOceanChange differentiates between ChangActions
|
||||
type DigitalOceanChange struct {
|
||||
Action string
|
||||
ResourceRecordSet godo.DomainRecord
|
||||
type digitalOceanChangeCreate struct {
|
||||
Domain string
|
||||
Options *godo.DomainRecordEditRequest
|
||||
}
|
||||
|
||||
type digitalOceanChangeUpdate struct {
|
||||
Domain string
|
||||
DomainRecord godo.DomainRecord
|
||||
Options *godo.DomainRecordEditRequest
|
||||
}
|
||||
|
||||
type digitalOceanChangeDelete struct {
|
||||
Domain string
|
||||
RecordID int
|
||||
}
|
||||
|
||||
// DigitalOceanChange contains all changes to apply to DNS
|
||||
type digitalOceanChanges struct {
|
||||
Creates []*digitalOceanChangeCreate
|
||||
Updates []*digitalOceanChangeUpdate
|
||||
Deletes []*digitalOceanChangeDelete
|
||||
}
|
||||
|
||||
func (c *digitalOceanChanges) Empty() bool {
|
||||
return len(c.Creates) == 0 && len(c.Updates) == 0 && len(c.Deletes) == 0
|
||||
}
|
||||
|
||||
// NewDigitalOceanProvider initializes a new DigitalOcean DNS based Provider.
|
||||
@ -71,13 +85,13 @@ func NewDigitalOceanProvider(ctx context.Context, domainFilter endpoint.DomainFi
|
||||
}))
|
||||
client := godo.NewClient(oauthClient)
|
||||
|
||||
provider := &DigitalOceanProvider{
|
||||
p := &DigitalOceanProvider{
|
||||
Client: client.Domains,
|
||||
domainFilter: domainFilter,
|
||||
apiPageSize: apiPageSize,
|
||||
DryRun: dryRun,
|
||||
}
|
||||
return provider, nil
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Zones returns the list of hosted zones.
|
||||
@ -98,12 +112,45 @@ func (p *DigitalOceanProvider) Zones(ctx context.Context) ([]godo.Domain, error)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Merge Endpoints with the same Name and Type into a single endpoint with multiple Targets.
|
||||
func mergeEndpointsByNameType(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
endpointsByNameType := map[string][]*endpoint.Endpoint{}
|
||||
|
||||
for _, e := range endpoints {
|
||||
key := fmt.Sprintf("%s-%s", e.DNSName, e.RecordType)
|
||||
endpointsByNameType[key] = append(endpointsByNameType[key], e)
|
||||
}
|
||||
|
||||
// If no merge occurred, just return the existing endpoints.
|
||||
if len(endpointsByNameType) == len(endpoints) {
|
||||
return endpoints
|
||||
}
|
||||
|
||||
// Otherwise, construct a new list of endpoints with the endpoints merged.
|
||||
var result []*endpoint.Endpoint
|
||||
for _, endpoints := range endpointsByNameType {
|
||||
dnsName := endpoints[0].DNSName
|
||||
recordType := endpoints[0].RecordType
|
||||
|
||||
targets := make([]string, len(endpoints))
|
||||
for i, e := range endpoints {
|
||||
targets[i] = e.Targets[0]
|
||||
}
|
||||
|
||||
e := endpoint.NewEndpoint(dnsName, recordType, targets...)
|
||||
result = append(result, e)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Records returns the list of records in a given zone.
|
||||
func (p *DigitalOceanProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoints := []*endpoint.Endpoint{}
|
||||
for _, zone := range zones {
|
||||
records, err := p.fetchRecords(ctx, zone.Name)
|
||||
@ -121,11 +168,21 @@ func (p *DigitalOceanProvider) Records(ctx context.Context) ([]*endpoint.Endpoin
|
||||
name = zone.Name
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(name, r.Type, r.Data))
|
||||
ep := endpoint.NewEndpoint(name, r.Type, r.Data)
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge endpoints with the same name and type (e.g., multiple A records for a single
|
||||
// DNS name) into one endpoint with multiple targets.
|
||||
endpoints = mergeEndpointsByNameType(endpoints)
|
||||
|
||||
// Log the endpoints that were found.
|
||||
log.WithFields(log.Fields{
|
||||
"endpoints": endpoints,
|
||||
}).Debug("Endpoints generated from DigitalOcean DNS")
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
@ -179,160 +236,379 @@ func (p *DigitalOceanProvider) fetchZones(ctx context.Context) ([]godo.Domain, e
|
||||
return allZones, nil
|
||||
}
|
||||
|
||||
// submitChanges takes a zone and a collection of Changes and sends them as a single transaction.
|
||||
func (p *DigitalOceanProvider) submitChanges(ctx context.Context, changes []*DigitalOceanChange) error {
|
||||
func (p *DigitalOceanProvider) getRecordsByDomain(ctx context.Context) (map[string][]godo.DomainRecord, provider.ZoneIDName, error) {
|
||||
recordsByDomain := map[string][]godo.DomainRecord{}
|
||||
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
zonesByDomain := make(map[string]godo.Domain)
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper.Add(z.Name, z.Name)
|
||||
zonesByDomain[z.Name] = z
|
||||
}
|
||||
|
||||
// Fetch records for each zone
|
||||
for _, zone := range zones {
|
||||
records, err := p.fetchRecords(ctx, zone.Name)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
recordsByDomain[zone.Name] = append(recordsByDomain[zone.Name], records...)
|
||||
}
|
||||
|
||||
return recordsByDomain, zoneNameIDMapper, nil
|
||||
}
|
||||
|
||||
// Make a DomainRecordEditRequest that conforms to DigitalOcean API requirements:
|
||||
// - Records at root of the zone have `@` as the name
|
||||
// - CNAME records must end in a `.`
|
||||
func makeDomainEditRequest(domain, name, recordType, data string, ttl int) *godo.DomainRecordEditRequest {
|
||||
// Trim the domain off the name if present.
|
||||
adjustedName := strings.TrimSuffix(name, "."+domain)
|
||||
|
||||
// Record at the root should be defined as @ instead of the full domain name.
|
||||
if adjustedName == domain {
|
||||
adjustedName = "@"
|
||||
}
|
||||
|
||||
// For some reason the DO API requires the '.' at the end of "data" in case of CNAME request.
|
||||
// Example: {"type":"CNAME","name":"hello","data":"www.example.com."}
|
||||
if recordType == endpoint.RecordTypeCNAME && !strings.HasSuffix(data, ".") {
|
||||
data += "."
|
||||
}
|
||||
|
||||
return &godo.DomainRecordEditRequest{
|
||||
Name: adjustedName,
|
||||
Type: recordType,
|
||||
Data: data,
|
||||
TTL: ttl,
|
||||
}
|
||||
}
|
||||
|
||||
// submitChanges applies an instance of `digitalOceanChanges` to the DigitalOcean API.
|
||||
func (p *DigitalOceanProvider) submitChanges(ctx context.Context, changes *digitalOceanChanges) error {
|
||||
// return early if there is nothing to change
|
||||
if len(changes) == 0 {
|
||||
if changes.Empty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
zones, err := p.Zones(ctx)
|
||||
for _, c := range changes.Creates {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": c.Domain,
|
||||
"dnsName": c.Options.Name,
|
||||
"recordType": c.Options.Type,
|
||||
"data": c.Options.Data,
|
||||
"ttl": c.Options.TTL,
|
||||
}).Debug("Creating domain record")
|
||||
|
||||
if p.DryRun {
|
||||
continue
|
||||
}
|
||||
|
||||
_, _, err := p.Client.CreateRecord(ctx, c.Domain, c.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range changes.Updates {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": u.Domain,
|
||||
"dnsName": u.Options.Name,
|
||||
"recordType": u.Options.Type,
|
||||
"data": u.Options.Data,
|
||||
"ttl": u.Options.TTL,
|
||||
}).Debug("Updating domain record")
|
||||
|
||||
if p.DryRun {
|
||||
continue
|
||||
}
|
||||
|
||||
_, _, err := p.Client.EditRecord(ctx, u.Domain, u.DomainRecord.ID, u.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range changes.Deletes {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": d.Domain,
|
||||
"recordId": d.RecordID,
|
||||
}).Debug("Deleting domain record")
|
||||
|
||||
if p.DryRun {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := p.Client.DeleteRecord(ctx, d.Domain, d.RecordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTTLFromEndpoint(ep *endpoint.Endpoint) int {
|
||||
if ep.RecordTTL.IsConfigured() {
|
||||
return int(ep.RecordTTL)
|
||||
}
|
||||
return digitalOceanRecordTTL
|
||||
}
|
||||
|
||||
func endpointsByZone(zoneNameIDMapper provider.ZoneIDName, endpoints []*endpoint.Endpoint) map[string][]*endpoint.Endpoint {
|
||||
endpointsByZone := make(map[string][]*endpoint.Endpoint)
|
||||
|
||||
for _, ep := range endpoints {
|
||||
zoneID, _ := zoneNameIDMapper.FindZone(ep.DNSName)
|
||||
if zoneID == "" {
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", ep.DNSName)
|
||||
continue
|
||||
}
|
||||
endpointsByZone[zoneID] = append(endpointsByZone[zoneID], ep)
|
||||
}
|
||||
|
||||
return endpointsByZone
|
||||
}
|
||||
|
||||
func getMatchingDomainRecords(records []godo.DomainRecord, domain string, ep *endpoint.Endpoint) []godo.DomainRecord {
|
||||
var name string
|
||||
if ep.DNSName != domain {
|
||||
name = strings.TrimSuffix(ep.DNSName, "."+domain)
|
||||
} else {
|
||||
name = "@"
|
||||
}
|
||||
|
||||
var result []godo.DomainRecord
|
||||
for _, r := range records {
|
||||
if r.Name == name && r.Type == ep.RecordType {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func processCreateActions(
|
||||
recordsByDomain map[string][]godo.DomainRecord,
|
||||
createsByDomain map[string][]*endpoint.Endpoint,
|
||||
changes *digitalOceanChanges,
|
||||
) error {
|
||||
// Process endpoints that need to be created.
|
||||
for domain, endpoints := range createsByDomain {
|
||||
if len(endpoints) == 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
}).Debug("Skipping domain, no creates found.")
|
||||
continue
|
||||
}
|
||||
|
||||
records := recordsByDomain[domain]
|
||||
|
||||
for _, ep := range endpoints {
|
||||
// Warn if there are existing records since we expect to create only new records.
|
||||
matchingRecords := getMatchingDomainRecords(records, domain, ep)
|
||||
if len(matchingRecords) > 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"dnsName": ep.DNSName,
|
||||
"recordType": ep.RecordType,
|
||||
}).Warn("Preexisting records exist which should not exist for creation actions.")
|
||||
}
|
||||
|
||||
ttl := getTTLFromEndpoint(ep)
|
||||
|
||||
for _, target := range ep.Targets {
|
||||
changes.Creates = append(changes.Creates, &digitalOceanChangeCreate{
|
||||
Domain: domain,
|
||||
Options: makeDomainEditRequest(domain, ep.DNSName, ep.RecordType, target, ttl),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processUpdateActions(
|
||||
recordsByDomain map[string][]godo.DomainRecord,
|
||||
updatesByDomain map[string][]*endpoint.Endpoint,
|
||||
changes *digitalOceanChanges,
|
||||
) error {
|
||||
// Generate creates and updates based on existing
|
||||
for domain, updates := range updatesByDomain {
|
||||
if len(updates) == 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
}).Debug("Skipping Zone, no updates found.")
|
||||
continue
|
||||
}
|
||||
|
||||
records := recordsByDomain[domain]
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"records": records,
|
||||
}).Debug("Records for domain")
|
||||
|
||||
for _, ep := range updates {
|
||||
matchingRecords := getMatchingDomainRecords(records, domain, ep)
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"endpoint": ep,
|
||||
"matchingRecords": matchingRecords,
|
||||
}).Debug("matching records")
|
||||
|
||||
if len(matchingRecords) == 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"dnsName": ep.DNSName,
|
||||
"recordType": ep.RecordType,
|
||||
}).Warn("Planning an update but no existing records found.")
|
||||
}
|
||||
|
||||
matchingRecordsByTarget := map[string]godo.DomainRecord{}
|
||||
for _, r := range matchingRecords {
|
||||
matchingRecordsByTarget[r.Data] = r
|
||||
}
|
||||
|
||||
ttl := getTTLFromEndpoint(ep)
|
||||
|
||||
// Generate create and delete actions based on existence of a record for each target.
|
||||
for _, target := range ep.Targets {
|
||||
if record, ok := matchingRecordsByTarget[target]; ok {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"dnsName": ep.DNSName,
|
||||
"recordType": ep.RecordType,
|
||||
"target": target,
|
||||
}).Warn("Updating existing target")
|
||||
|
||||
changes.Updates = append(changes.Updates, &digitalOceanChangeUpdate{
|
||||
Domain: domain,
|
||||
DomainRecord: record,
|
||||
Options: makeDomainEditRequest(domain, ep.DNSName, ep.RecordType, target, ttl),
|
||||
})
|
||||
|
||||
delete(matchingRecordsByTarget, target)
|
||||
} else {
|
||||
// Record did not previously exist, create new 'target'
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"dnsName": ep.DNSName,
|
||||
"recordType": ep.RecordType,
|
||||
"target": target,
|
||||
}).Warn("Creating new target")
|
||||
|
||||
changes.Creates = append(changes.Creates, &digitalOceanChangeCreate{
|
||||
Domain: domain,
|
||||
Options: makeDomainEditRequest(domain, ep.DNSName, ep.RecordType, target, ttl),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Any remaining records have been removed, delete them
|
||||
for _, record := range matchingRecordsByTarget {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"dnsName": ep.DNSName,
|
||||
"recordType": ep.RecordType,
|
||||
"target": record.Data,
|
||||
}).Warn("Deleting target")
|
||||
|
||||
changes.Deletes = append(changes.Deletes, &digitalOceanChangeDelete{
|
||||
Domain: domain,
|
||||
RecordID: record.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func processDeleteActions(
|
||||
recordsByDomain map[string][]godo.DomainRecord,
|
||||
deletesByDomain map[string][]*endpoint.Endpoint,
|
||||
changes *digitalOceanChanges,
|
||||
) error {
|
||||
// Generate delete actions for each deleted endpoint.
|
||||
for domain, deletes := range deletesByDomain {
|
||||
if len(deletes) == 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
}).Debug("Skipping Zone, no deletes found.")
|
||||
continue
|
||||
}
|
||||
|
||||
records := recordsByDomain[domain]
|
||||
|
||||
for _, ep := range deletes {
|
||||
matchingRecords := getMatchingDomainRecords(records, domain, ep)
|
||||
|
||||
if len(matchingRecords) == 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"dnsName": ep.DNSName,
|
||||
"recordType": ep.RecordType,
|
||||
}).Warn("Records to delete not found.")
|
||||
}
|
||||
|
||||
for _, record := range matchingRecords {
|
||||
doDelete := false
|
||||
for _, t := range ep.Targets {
|
||||
v1 := t
|
||||
v2 := record.Data
|
||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
v1 = strings.TrimSuffix(t, ".")
|
||||
v2 = strings.TrimSuffix(t, ".")
|
||||
}
|
||||
if v1 == v2 {
|
||||
doDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
if doDelete {
|
||||
changes.Deletes = append(changes.Deletes, &digitalOceanChangeDelete{
|
||||
Domain: domain,
|
||||
RecordID: record.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyChanges applies the given set of generic changes to the provider.
|
||||
func (p *DigitalOceanProvider) ApplyChanges(ctx context.Context, planChanges *plan.Changes) error {
|
||||
// TODO: This should only retrieve zones affected by the given `planChanges`.
|
||||
recordsByDomain, zoneNameIDMapper, err := p.getRecordsByDomain(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// separate into per-zone change sets to be passed to the API.
|
||||
changesByZone := digitalOceanChangesByZone(zones, changes)
|
||||
for zoneName, changes := range changesByZone {
|
||||
records, err := p.fetchRecords(ctx, zoneName)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to list records in the zone: %s", zoneName)
|
||||
continue
|
||||
}
|
||||
for _, change := range changes {
|
||||
logFields := log.Fields{
|
||||
"record": change.ResourceRecordSet.Name,
|
||||
"type": change.ResourceRecordSet.Type,
|
||||
"ttl": change.ResourceRecordSet.TTL,
|
||||
"action": change.Action,
|
||||
"zone": zoneName,
|
||||
}
|
||||
createsByDomain := endpointsByZone(zoneNameIDMapper, planChanges.Create)
|
||||
updatesByDomain := endpointsByZone(zoneNameIDMapper, planChanges.UpdateNew)
|
||||
deletesByDomain := endpointsByZone(zoneNameIDMapper, planChanges.Delete)
|
||||
|
||||
log.WithFields(logFields).Info("Changing record.")
|
||||
var changes digitalOceanChanges
|
||||
|
||||
if p.DryRun {
|
||||
continue
|
||||
}
|
||||
|
||||
change.ResourceRecordSet.Name = strings.TrimSuffix(change.ResourceRecordSet.Name, "."+zoneName)
|
||||
|
||||
// record at the root should be defined as @ instead of
|
||||
// the full domain name
|
||||
if change.ResourceRecordSet.Name == zoneName {
|
||||
change.ResourceRecordSet.Name = "@"
|
||||
}
|
||||
|
||||
// for some reason the DO API requires the '.' at the end of "data" in case of CNAME request
|
||||
// Example: {"type":"CNAME","name":"hello","data":"www.example.com."}
|
||||
if change.ResourceRecordSet.Type == endpoint.RecordTypeCNAME {
|
||||
change.ResourceRecordSet.Data += "."
|
||||
}
|
||||
|
||||
switch change.Action {
|
||||
case DigitalOceanCreate:
|
||||
_, _, err = p.Client.CreateRecord(ctx, zoneName,
|
||||
&godo.DomainRecordEditRequest{
|
||||
Data: change.ResourceRecordSet.Data,
|
||||
Name: change.ResourceRecordSet.Name,
|
||||
Type: change.ResourceRecordSet.Type,
|
||||
TTL: change.ResourceRecordSet.TTL,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case DigitalOceanDelete:
|
||||
recordID := p.getRecordID(records, change.ResourceRecordSet)
|
||||
_, err = p.Client.DeleteRecord(ctx, zoneName, recordID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case DigitalOceanUpdate:
|
||||
recordID := p.getRecordID(records, change.ResourceRecordSet)
|
||||
_, _, err = p.Client.EditRecord(ctx, zoneName, recordID,
|
||||
&godo.DomainRecordEditRequest{
|
||||
Data: change.ResourceRecordSet.Data,
|
||||
Name: change.ResourceRecordSet.Name,
|
||||
Type: change.ResourceRecordSet.Type,
|
||||
TTL: change.ResourceRecordSet.TTL,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := processCreateActions(recordsByDomain, createsByDomain, &changes); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *DigitalOceanProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
combinedChanges := make([]*DigitalOceanChange, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
||||
|
||||
combinedChanges = append(combinedChanges, newDigitalOceanChanges(DigitalOceanCreate, changes.Create)...)
|
||||
combinedChanges = append(combinedChanges, newDigitalOceanChanges(DigitalOceanUpdate, changes.UpdateNew)...)
|
||||
combinedChanges = append(combinedChanges, newDigitalOceanChanges(DigitalOceanDelete, changes.Delete)...)
|
||||
|
||||
return p.submitChanges(ctx, combinedChanges)
|
||||
}
|
||||
|
||||
// newDigitalOceanChanges returns a collection of Changes based on the given records and action.
|
||||
func newDigitalOceanChanges(action string, endpoints []*endpoint.Endpoint) []*DigitalOceanChange {
|
||||
changes := make([]*DigitalOceanChange, 0, len(endpoints))
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
changes = append(changes, newDigitalOceanChange(action, endpoint))
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
func newDigitalOceanChange(action string, endpoint *endpoint.Endpoint) *DigitalOceanChange {
|
||||
// no annotation results in a TTL of 0, default to 300 for consistency with other providers
|
||||
var ttl = digitalOceanRecordTTL
|
||||
if endpoint.RecordTTL.IsConfigured() {
|
||||
ttl = int(endpoint.RecordTTL)
|
||||
}
|
||||
|
||||
change := &DigitalOceanChange{
|
||||
Action: action,
|
||||
ResourceRecordSet: godo.DomainRecord{
|
||||
Name: endpoint.DNSName,
|
||||
Type: endpoint.RecordType,
|
||||
Data: endpoint.Targets[0],
|
||||
TTL: ttl,
|
||||
},
|
||||
}
|
||||
return change
|
||||
}
|
||||
|
||||
// getRecordID returns the ID from a record.
|
||||
// the ID is mandatory to update and delete records
|
||||
func (p *DigitalOceanProvider) getRecordID(records []godo.DomainRecord, record godo.DomainRecord) int {
|
||||
for _, zoneRecord := range records {
|
||||
if zoneRecord.Name == record.Name && zoneRecord.Type == record.Type {
|
||||
return zoneRecord.ID
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// digitalOceanchangesByZone separates a multi-zone change into a single change per zone.
|
||||
func digitalOceanChangesByZone(zones []godo.Domain, changeSet []*DigitalOceanChange) map[string][]*DigitalOceanChange {
|
||||
changes := make(map[string][]*DigitalOceanChange)
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
for _, z := range zones {
|
||||
zoneNameIDMapper.Add(z.Name, z.Name)
|
||||
changes[z.Name] = []*DigitalOceanChange{}
|
||||
}
|
||||
|
||||
for _, c := range changeSet {
|
||||
zone, _ := zoneNameIDMapper.FindZone(c.ResourceRecordSet.Name)
|
||||
if zone == "" {
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecordSet.Name)
|
||||
continue
|
||||
}
|
||||
changes[zone] = append(changes[zone], c)
|
||||
}
|
||||
|
||||
return changes
|
||||
|
||||
if err := processUpdateActions(recordsByDomain, updatesByDomain, &changes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := processDeleteActions(recordsByDomain, deletesByDomain, &changes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.submitChanges(ctx, &changes)
|
||||
}
|
||||
|
@ -20,9 +20,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -143,10 +146,111 @@ func (m *mockDigitalOceanRecordsFail) Records(ctx context.Context, domain string
|
||||
return []godo.DomainRecord{}, nil, fmt.Errorf("Failed to get records")
|
||||
}
|
||||
|
||||
func TestNewDigitalOceanChanges(t *testing.T) {
|
||||
action := DigitalOceanCreate
|
||||
endpoints := []*endpoint.Endpoint{{DNSName: "new", Targets: endpoint.Targets{"target"}}}
|
||||
_ = newDigitalOceanChanges(action, endpoints)
|
||||
func isEmpty(xs interface{}) bool {
|
||||
if xs != nil {
|
||||
objValue := reflect.ValueOf(xs)
|
||||
return objValue.Len() == 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// This function is an adapted copy of the testify package's ElementsMatch function with the
|
||||
// call to ObjectsAreEqual replaced with cmp.Equal which better handles struct's with pointers to
|
||||
// other structs. It also ignores ordering when comparing unlike cmp.Equal.
|
||||
func elementsMatch(t *testing.T, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) {
|
||||
if listA == nil && listB == nil {
|
||||
return true
|
||||
} else if listA == nil {
|
||||
return isEmpty(listB)
|
||||
} else if listB == nil {
|
||||
return isEmpty(listA)
|
||||
}
|
||||
|
||||
aKind := reflect.TypeOf(listA).Kind()
|
||||
bKind := reflect.TypeOf(listB).Kind()
|
||||
|
||||
if aKind != reflect.Array && aKind != reflect.Slice {
|
||||
return assert.Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
if bKind != reflect.Array && bKind != reflect.Slice {
|
||||
return assert.Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...)
|
||||
}
|
||||
|
||||
aValue := reflect.ValueOf(listA)
|
||||
bValue := reflect.ValueOf(listB)
|
||||
|
||||
aLen := aValue.Len()
|
||||
bLen := bValue.Len()
|
||||
|
||||
if aLen != bLen {
|
||||
return assert.Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...)
|
||||
}
|
||||
|
||||
// Mark indexes in bValue that we already used
|
||||
visited := make([]bool, bLen)
|
||||
for i := 0; i < aLen; i++ {
|
||||
element := aValue.Index(i).Interface()
|
||||
found := false
|
||||
for j := 0; j < bLen; j++ {
|
||||
if visited[j] {
|
||||
continue
|
||||
}
|
||||
if cmp.Equal(bValue.Index(j).Interface(), element) {
|
||||
visited[j] = true
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return assert.Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Test adapted from test in testify library.
|
||||
// https://github.com/stretchr/testify/blob/b8f7d52a4a7c581d5ed42333572e7fb857c687c2/assert/assertions_test.go#L768-L796
|
||||
func TestElementsMatch(t *testing.T) {
|
||||
mockT := new(testing.T)
|
||||
|
||||
cases := []struct {
|
||||
expected interface{}
|
||||
actual interface{}
|
||||
result bool
|
||||
}{
|
||||
// matching
|
||||
{nil, nil, true},
|
||||
|
||||
{nil, nil, true},
|
||||
{[]int{}, []int{}, true},
|
||||
{[]int{1}, []int{1}, true},
|
||||
{[]int{1, 1}, []int{1, 1}, true},
|
||||
{[]int{1, 2}, []int{1, 2}, true},
|
||||
{[]int{1, 2}, []int{2, 1}, true},
|
||||
{[2]int{1, 2}, [2]int{2, 1}, true},
|
||||
{[]string{"hello", "world"}, []string{"world", "hello"}, true},
|
||||
{[]string{"hello", "hello"}, []string{"hello", "hello"}, true},
|
||||
{[]string{"hello", "hello", "world"}, []string{"hello", "world", "hello"}, true},
|
||||
{[3]string{"hello", "hello", "world"}, [3]string{"hello", "world", "hello"}, true},
|
||||
{[]int{}, nil, true},
|
||||
|
||||
// not matching
|
||||
{[]int{1}, []int{1, 1}, false},
|
||||
{[]int{1, 2}, []int{2, 2}, false},
|
||||
{[]string{"hello", "hello"}, []string{"hello"}, false},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(fmt.Sprintf("ElementsMatch(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
|
||||
res := elementsMatch(mockT, c.actual, c.expected)
|
||||
|
||||
if res != c.result {
|
||||
t.Errorf("elementsMatch(%#v, %#v) should return %v", c.actual, c.expected, c.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanZones(t *testing.T) {
|
||||
@ -165,6 +269,38 @@ func TestDigitalOceanZones(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDigitalOceanMakeDomainEditRequest(t *testing.T) {
|
||||
// Ensure that records at the root of the zone get `@` as the name.
|
||||
r1 := makeDomainEditRequest("example.com", "example.com", endpoint.RecordTypeA,
|
||||
"1.2.3.4", digitalOceanRecordTTL)
|
||||
assert.Equal(t, &godo.DomainRecordEditRequest{
|
||||
Type: endpoint.RecordTypeA,
|
||||
Name: "@",
|
||||
Data: "1.2.3.4",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
}, r1)
|
||||
|
||||
// Ensure the CNAME records have a `.` appended.
|
||||
r2 := makeDomainEditRequest("example.com", "foo.example.com", endpoint.RecordTypeCNAME,
|
||||
"bar.example.com", digitalOceanRecordTTL)
|
||||
assert.Equal(t, &godo.DomainRecordEditRequest{
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Name: "foo",
|
||||
Data: "bar.example.com.",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
}, r2)
|
||||
|
||||
// Ensure that CNAME records do not have an extra `.` appended if they already have a `.`
|
||||
r3 := makeDomainEditRequest("example.com", "foo.example.com", endpoint.RecordTypeCNAME,
|
||||
"bar.example.com.", digitalOceanRecordTTL)
|
||||
assert.Equal(t, &godo.DomainRecordEditRequest{
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Name: "foo",
|
||||
Data: "bar.example.com.",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
}, r3)
|
||||
}
|
||||
|
||||
func TestDigitalOceanApplyChanges(t *testing.T) {
|
||||
changes := &plan.Changes{}
|
||||
provider := &DigitalOceanProvider{
|
||||
@ -185,6 +321,198 @@ func TestDigitalOceanApplyChanges(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanProcessCreateActions(t *testing.T) {
|
||||
recordsByDomain := map[string][]godo.DomainRecord{
|
||||
"example.com": nil,
|
||||
}
|
||||
|
||||
createsByDomain := map[string][]*endpoint.Endpoint{
|
||||
"example.com": {
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeCNAME, "foo.example.com"),
|
||||
},
|
||||
}
|
||||
|
||||
var changes digitalOceanChanges
|
||||
err := processCreateActions(recordsByDomain, createsByDomain, &changes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(changes.Creates))
|
||||
assert.Equal(t, 0, len(changes.Updates))
|
||||
assert.Equal(t, 0, len(changes.Deletes))
|
||||
|
||||
expectedCreates := []*digitalOceanChangeCreate{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Options: &godo.DomainRecordEditRequest{
|
||||
Name: "foo",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "1.2.3.4",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
Options: &godo.DomainRecordEditRequest{
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Data: "foo.example.com.",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !elementsMatch(t, expectedCreates, changes.Creates) {
|
||||
assert.Failf(t, "diff: %s", cmp.Diff(expectedCreates, changes.Creates))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanProcessUpdateActions(t *testing.T) {
|
||||
recordsByDomain := map[string][]godo.DomainRecord{
|
||||
"example.com": {
|
||||
{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "1.2.3.4",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "foo",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "5.6.7.8",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Data: "foo.example.com.",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
updatesByDomain := map[string][]*endpoint.Endpoint{
|
||||
"example.com": {
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "10.11.12.13"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeCNAME, "bar.example.com"),
|
||||
},
|
||||
}
|
||||
|
||||
var changes digitalOceanChanges
|
||||
err := processUpdateActions(recordsByDomain, updatesByDomain, &changes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(changes.Creates))
|
||||
assert.Equal(t, 0, len(changes.Updates))
|
||||
assert.Equal(t, 3, len(changes.Deletes))
|
||||
|
||||
expectedCreates := []*digitalOceanChangeCreate{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Options: &godo.DomainRecordEditRequest{
|
||||
Name: "foo",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "10.11.12.13",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
Options: &godo.DomainRecordEditRequest{
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Data: "bar.example.com.",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !elementsMatch(t, expectedCreates, changes.Creates) {
|
||||
assert.Failf(t, "diff: %s", cmp.Diff(expectedCreates, changes.Creates))
|
||||
}
|
||||
|
||||
expectedDeletes := []*digitalOceanChangeDelete{
|
||||
{
|
||||
Domain: "example.com",
|
||||
RecordID: 1,
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
RecordID: 2,
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
RecordID: 3,
|
||||
},
|
||||
}
|
||||
|
||||
if !elementsMatch(t, expectedDeletes, changes.Deletes) {
|
||||
assert.Failf(t, "diff: %s", cmp.Diff(expectedDeletes, changes.Deletes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanProcessDeleteActions(t *testing.T) {
|
||||
recordsByDomain := map[string][]godo.DomainRecord{
|
||||
"example.com": {
|
||||
{
|
||||
ID: 1,
|
||||
Name: "foo",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "1.2.3.4",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
// This record will not be deleted because it represents a target not specified to be deleted.
|
||||
{
|
||||
ID: 2,
|
||||
Name: "foo",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "5.6.7.8",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Data: "foo.example.com.",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
deletesByDomain := map[string][]*endpoint.Endpoint{
|
||||
"example.com": {
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeCNAME, "foo.example.com"),
|
||||
},
|
||||
}
|
||||
|
||||
var changes digitalOceanChanges
|
||||
err := processDeleteActions(recordsByDomain, deletesByDomain, &changes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, len(changes.Creates))
|
||||
assert.Equal(t, 0, len(changes.Updates))
|
||||
assert.Equal(t, 2, len(changes.Deletes))
|
||||
|
||||
expectedDeletes := []*digitalOceanChangeDelete{
|
||||
{
|
||||
Domain: "example.com",
|
||||
RecordID: 1,
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
RecordID: 3,
|
||||
},
|
||||
}
|
||||
|
||||
if !elementsMatch(t, expectedDeletes, changes.Deletes) {
|
||||
assert.Failf(t, "diff: %s", cmp.Diff(expectedDeletes, changes.Deletes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDigitalOceanProvider(t *testing.T) {
|
||||
_ = os.Setenv("DO_TOKEN", "xxxxxxxxxxxxxxxxx")
|
||||
_, err := NewDigitalOceanProvider(context.Background(), endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true, 50)
|
||||
@ -198,29 +526,62 @@ func TestNewDigitalOceanProvider(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanGetRecordID(t *testing.T) {
|
||||
p := &DigitalOceanProvider{}
|
||||
func TestDigitalOceanGetMatchingDomainRecords(t *testing.T) {
|
||||
records := []godo.DomainRecord{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "foo.com",
|
||||
Name: "foo",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
Data: "baz.org.",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Name: "baz.de",
|
||||
Name: "baz",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "1.2.3.4",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Name: "baz",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "5.6.7.8",
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "9.10.11.12",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, 1, p.getRecordID(records, godo.DomainRecord{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeCNAME,
|
||||
}))
|
||||
|
||||
assert.Equal(t, 0, p.getRecordID(records, godo.DomainRecord{
|
||||
Name: "foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
}))
|
||||
ep1 := endpoint.NewEndpoint("foo.com", endpoint.RecordTypeCNAME)
|
||||
assert.Equal(t, 1, len(getMatchingDomainRecords(records, "com", ep1)))
|
||||
|
||||
ep2 := endpoint.NewEndpoint("foo.com", endpoint.RecordTypeA)
|
||||
assert.Equal(t, 0, len(getMatchingDomainRecords(records, "com", ep2)))
|
||||
|
||||
ep3 := endpoint.NewEndpoint("baz.org", endpoint.RecordTypeA)
|
||||
r := getMatchingDomainRecords(records, "org", ep3)
|
||||
assert.Equal(t, 2, len(r))
|
||||
assert.ElementsMatch(t, r, []godo.DomainRecord{
|
||||
{
|
||||
ID: 2,
|
||||
Name: "baz",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "1.2.3.4",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Name: "baz",
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "5.6.7.8",
|
||||
},
|
||||
})
|
||||
|
||||
ep4 := endpoint.NewEndpoint("example.com", endpoint.RecordTypeA)
|
||||
r2 := getMatchingDomainRecords(records, "example.com", ep4)
|
||||
assert.Equal(t, 1, len(r2))
|
||||
assert.Equal(t, "9.10.11.12", r2[0].Data)
|
||||
}
|
||||
|
||||
func validateDigitalOceanZones(t *testing.T, zones []godo.Domain, expected []godo.Domain) {
|
||||
@ -265,3 +626,36 @@ func TestDigitalOceanAllRecords(t *testing.T) {
|
||||
t.Errorf("expected to fail, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDigitalOceanMergeRecordsByNameType(t *testing.T) {
|
||||
xs := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("foo.example.com", "A", "1.2.3.4"),
|
||||
endpoint.NewEndpoint("bar.example.com", "A", "1.2.3.4"),
|
||||
endpoint.NewEndpoint("foo.example.com", "A", "5.6.7.8"),
|
||||
endpoint.NewEndpoint("foo.example.com", "CNAME", "somewhere.out.there.com"),
|
||||
}
|
||||
|
||||
merged := mergeEndpointsByNameType(xs)
|
||||
|
||||
assert.Equal(t, 3, len(merged))
|
||||
sort.SliceStable(merged, func(i, j int) bool {
|
||||
if merged[i].DNSName != merged[j].DNSName {
|
||||
return merged[i].DNSName < merged[j].DNSName
|
||||
}
|
||||
return merged[i].RecordType < merged[j].RecordType
|
||||
})
|
||||
assert.Equal(t, "bar.example.com", merged[0].DNSName)
|
||||
assert.Equal(t, "A", merged[0].RecordType)
|
||||
assert.Equal(t, 1, len(merged[0].Targets))
|
||||
assert.Equal(t, "1.2.3.4", merged[0].Targets[0])
|
||||
|
||||
assert.Equal(t, "foo.example.com", merged[1].DNSName)
|
||||
assert.Equal(t, "A", merged[1].RecordType)
|
||||
assert.Equal(t, 2, len(merged[1].Targets))
|
||||
assert.ElementsMatch(t, []string{"1.2.3.4", "5.6.7.8"}, merged[1].Targets)
|
||||
|
||||
assert.Equal(t, "foo.example.com", merged[2].DNSName)
|
||||
assert.Equal(t, "CNAME", merged[2].RecordType)
|
||||
assert.Equal(t, 1, len(merged[2].Targets))
|
||||
assert.Equal(t, "somewhere.out.there.com", merged[2].Targets[0])
|
||||
}
|
||||
|
@ -157,16 +157,18 @@ func (p *VultrProvider) submitChanges(ctx context.Context, changes []*VultrChang
|
||||
for zoneName, changes := range zoneChanges {
|
||||
for _, change := range changes {
|
||||
log.WithFields(log.Fields{
|
||||
"record": change.ResourceRecordSet.Name,
|
||||
"type": change.ResourceRecordSet.Type,
|
||||
"ttl": change.ResourceRecordSet.TTL,
|
||||
"action": change.Action,
|
||||
"zone": zoneName,
|
||||
"record": change.ResourceRecordSet.Name,
|
||||
"type": change.ResourceRecordSet.Type,
|
||||
"ttl": change.ResourceRecordSet.TTL,
|
||||
"priority": change.ResourceRecordSet.Priority,
|
||||
"action": change.Action,
|
||||
"zone": zoneName,
|
||||
}).Info("Changing record.")
|
||||
|
||||
switch change.Action {
|
||||
case vultrCreate:
|
||||
err = p.client.DNSRecord.Create(ctx, zoneName, change.ResourceRecordSet.Type, change.ResourceRecordSet.Name, change.ResourceRecordSet.Data, change.ResourceRecordSet.TTL, change.ResourceRecordSet.Priority)
|
||||
priority := getPriority(change.ResourceRecordSet.Priority)
|
||||
err = p.client.DNSRecord.Create(ctx, zoneName, change.ResourceRecordSet.Type, change.ResourceRecordSet.Name, change.ResourceRecordSet.Data, change.ResourceRecordSet.TTL, priority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -276,3 +278,11 @@ func (p *VultrProvider) getRecordID(ctx context.Context, zone string, record gov
|
||||
|
||||
return 0, fmt.Errorf("no record was found")
|
||||
}
|
||||
|
||||
func getPriority(priority *int) int {
|
||||
p := 0
|
||||
if priority != nil {
|
||||
p = *priority
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
@ -55,7 +55,6 @@ type Config struct {
|
||||
KubeConfig string
|
||||
KubeMaster string
|
||||
ServiceTypeFilter []string
|
||||
IstioIngressGatewayServices []string
|
||||
CFAPIEndpoint string
|
||||
CFUsername string
|
||||
CFPassword string
|
||||
|
@ -155,6 +155,5 @@ func TestByNames(t *testing.T) {
|
||||
}
|
||||
|
||||
var minimalConfig = &Config{
|
||||
IstioIngressGatewayServices: []string{"istio-system/istio-ingressgateway"},
|
||||
ContourLoadBalancerService: "heptio-contour/contour",
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user