mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-05 17:16:59 +02:00
Merge remote-tracking branch 'origin/master' into ingress-class-filtering
This commit is contained in:
commit
1ceec80ec5
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
go-version: ^1.17
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
# builder image
|
||||
ARG ARCH
|
||||
FROM golang:1.16 as builder
|
||||
FROM golang:1.17 as builder
|
||||
ARG ARCH
|
||||
|
||||
WORKDIR /sigs.k8s.io/external-dns
|
||||
@ -27,7 +27,7 @@ COPY . .
|
||||
RUN make test build.$ARCH
|
||||
|
||||
# final image
|
||||
FROM $ARCH/alpine:3.14
|
||||
FROM $ARCH/alpine:3.15
|
||||
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||
COPY --from=builder /sigs.k8s.io/external-dns/build/external-dns /bin/external-dns
|
||||
|
@ -12,7 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
FROM golang:1.16 as builder
|
||||
FROM golang:1.17 as builder
|
||||
|
||||
WORKDIR /sigs.k8s.io/external-dns
|
||||
|
||||
|
2
Makefile
2
Makefile
@ -105,7 +105,7 @@ build.push/multiarch:
|
||||
image="$(IMAGE):$(VERSION)-$${arch}" ;\
|
||||
# pre-pull due to https://github.com/kubernetes-sigs/cluster-addons/pull/84/files ;\
|
||||
docker pull $${arch}/alpine:3.14 ;\
|
||||
docker pull golang:1.16 ;\
|
||||
docker pull golang:1.17 ;\
|
||||
DOCKER_BUILDKIT=1 docker build --rm --tag $${image} --build-arg VERSION="$(VERSION)" --build-arg ARCH="$${arch}" . ;\
|
||||
docker push $${image} ;\
|
||||
arch_specific_tags+=( "--amend $${image}" ) ;\
|
||||
|
15
README.md
15
README.md
@ -52,6 +52,7 @@ ExternalDNS' allows you to keep selected zones (via `--domain-filter`) synchroni
|
||||
* [Akamai Edge DNS](https://learn.akamai.com/en-us/products/cloud_security/edge_dns.html)
|
||||
* [GoDaddy](https://www.godaddy.com)
|
||||
* [Gandi](https://www.gandi.net)
|
||||
* [UKFast SafeDNS](https://my.ukfast.co.uk/safedns/)
|
||||
|
||||
From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.
|
||||
|
||||
@ -109,13 +110,17 @@ The following table clarifies the current status of the providers according to t
|
||||
| UltraDNS | Alpha | |
|
||||
| GoDaddy | Alpha | |
|
||||
| Gandi | Alpha | @packi |
|
||||
| SafeDNS | Alpha | @assureddt |
|
||||
|
||||
## Kubernetes version compatibility
|
||||
|
||||
| ExternalDNS | <= 0.9.x | >= 0.10.0 |
|
||||
| ------------------ | :----------------: | :----------------: |
|
||||
| Kubernetes <= 1.18 | :white_check_mark: | :x: |
|
||||
| Kubernetes >= 1.19 | :x: | :white_check_mark: |
|
||||
A [breaking change](https://github.com/kubernetes-sigs/external-dns/pull/2281) was added in external-dns v0.10.0.
|
||||
|
||||
| ExternalDNS | <= 0.9.x | >= 0.10.0 |
|
||||
| ------------------------------ | :----------------: | :----------------: |
|
||||
| Kubernetes <= 1.18 | :white_check_mark: | :x: |
|
||||
| Kubernetes >= 1.19 and <= 1.21 | :white_check_mark: | :white_check_mark: |
|
||||
| Kubernetes >= 1.22 | :x: | :white_check_mark: |
|
||||
|
||||
## Running ExternalDNS:
|
||||
|
||||
@ -128,6 +133,7 @@ The are two ways of running ExternalDNS:
|
||||
|
||||
The following tutorials are provided:
|
||||
|
||||
* [Akamai Edge DNS](docs/tutorials/akamai-edgedns.md)
|
||||
* [Alibaba Cloud](docs/tutorials/alibabacloud.md)
|
||||
* AWS
|
||||
* [ALB Ingress Controller](docs/tutorials/alb-ingress.md)
|
||||
@ -171,6 +177,7 @@ The following tutorials are provided:
|
||||
* [UltraDNS](docs/tutorials/ultradns.md)
|
||||
* [GoDaddy](docs/tutorials/godaddy.md)
|
||||
* [Gandi](docs/tutorials/gandi.md)
|
||||
* [SafeDNS](docs/tutorials/safedns.md)
|
||||
|
||||
### Running Locally
|
||||
|
||||
|
@ -2,7 +2,7 @@ apiVersion: v2
|
||||
name: external-dns
|
||||
description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
|
||||
type: application
|
||||
version: 1.7.0
|
||||
version: 1.7.1
|
||||
appVersion: 0.10.2
|
||||
keywords:
|
||||
- kubernetes
|
||||
|
@ -33,6 +33,7 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
|
||||
| `serviceAccount.name` | Service account to be used. If not set and `serviceAccount.create` is `true`, a name is generated using the full name template. | `""` |
|
||||
| `rbac.create` | If `true`, create the RBAC resources. | `true` |
|
||||
| `rbac.additionalPermissions` | Additional permissions to be added to the cluster role. | `{}` |
|
||||
| `deploymentAnnotations` | Annotations to add to the Deployment. | `{}` |
|
||||
| `podLabels` | Labels to add to the pod. | `{}` |
|
||||
| `podAnnotations` | Annotations to add to the pod. | `{}` |
|
||||
| `podSecurityContext` | Security context for the pod, this supports the full [PodSecurityContext](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#podsecuritycontext-v1-core) API. | _see values.yaml_ |
|
||||
@ -54,6 +55,7 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
|
||||
| `nodeSelector` | Node labels for pod assignment. | `{}` |
|
||||
| `tolerations` | Tolerations for pod assignment, this supports the full [Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#toleration-v1-core) API. | `[]` |
|
||||
| `affinity` | Affinity settings for pod assignment, this supports the full [Affinity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#affinity-v1-core) API. | `{}` |
|
||||
| `topologySpreadConstraints` | TopologySpreadConstraint settings for pod assignment, this supports the full [TopologySpreadConstraints](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#topologyspreadconstraint-v1-core) API. | `[]` |
|
||||
| `logLevel` | Verbosity of the logs, available values are: `panic`, `debug`, `info`, `warn`, `error`, `fatal`. | `info` |
|
||||
| `logFormat` | Formats of the logs, available values are: `text`, `json`. | `text` |
|
||||
| `interval` | The interval for DNS updates. | `1m` |
|
||||
|
@ -30,7 +30,7 @@ rules:
|
||||
verbs: ["get","watch","list"]
|
||||
{{- end }}
|
||||
|
||||
{{- if has "istio-gateway" .Values.sources }}
|
||||
{{- if or (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) }}
|
||||
- apiGroups: ["networking.istio.io"]
|
||||
resources: ["gateways"]
|
||||
verbs: ["get","watch","list"]
|
||||
|
@ -4,6 +4,10 @@ metadata:
|
||||
name: {{ include "external-dns.fullname" . }}
|
||||
labels:
|
||||
{{- include "external-dns.labels" . | nindent 4 }}
|
||||
{{- with .Values.deploymentAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
@ -67,6 +71,9 @@ spec:
|
||||
{{- if .Values.txtPrefix }}
|
||||
- --txt-prefix={{ .Values.txtPrefix }}
|
||||
{{- end }}
|
||||
{{- if and (eq .Values.txtPrefix "") (ne .Values.txtSuffix "") }}
|
||||
- --txt-suffix={{ .Values.txtSuffix }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- range .Values.domainFilters }}
|
||||
- --domain-filter={{ . }}
|
||||
@ -103,6 +110,10 @@ spec:
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.topologySpreadConstraints }}
|
||||
topologySpreadConstraints:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
|
@ -26,8 +26,12 @@ rbac:
|
||||
create: true
|
||||
additionalPermissions: {}
|
||||
|
||||
# Annotations to add to the Deployment
|
||||
deploymentAnnotations: {}
|
||||
|
||||
podLabels: {}
|
||||
|
||||
# Annotations to add to the Pod
|
||||
podAnnotations: {}
|
||||
|
||||
podSecurityContext:
|
||||
@ -88,6 +92,8 @@ tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
topologySpreadConstraints: []
|
||||
|
||||
logLevel: info
|
||||
logFormat: text
|
||||
|
||||
@ -103,6 +109,7 @@ policy: upsert-only
|
||||
registry: txt
|
||||
txtOwnerId: ""
|
||||
txtPrefix: ""
|
||||
txtSuffix: ""
|
||||
|
||||
domainFilters: []
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Quick Start
|
||||
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [Go 1.16+](https://golang.org/dl/)
|
||||
- [Go 1.17+](https://golang.org/dl/)
|
||||
- [Go modules](https://github.com/golang/go/wiki/Modules)
|
||||
- [golangci-lint](https://github.com/golangci/golangci-lint)
|
||||
- [Docker](https://docs.docker.com/install/)
|
||||
|
@ -122,8 +122,4 @@ A single loop iteration of external-dns operation:
|
||||
1. DNS Provider should use batch operations
|
||||
2. DNS Provider should be called with CREATE operation (not UPSERT!) when the record does not yet exist!
|
||||
|
||||
### Resources:
|
||||
|
||||
1. Registry implementation draft:
|
||||
|
||||
[Flow](https://lh3.googleusercontent.com/BNUZZQ8XivYkXyYVPDgPCoZpwYv0pOyoyfBKbOnYJGsqueeB-EUXfzBZLk7xP-E_GDo7YHiTlA4XgPEs6ao_Ex0TY2SN66-yg5iRmn5Tc2EXVqs_yS9CtumhE1T4krZc4Z8_1gHOirDxCegU-Fk0K3fvg-J3UpzdKmGDG-JZwdzRyP4WyORWUQilJO9jErh-HP8AtM8p2ZjiqN9B3-VXdYuHbsiR6EHNFw43aOQAk52muDf2AgjqX2YUSbN9eO0Akt39ien3euT2HsZJlPvm5s8v2a_ZqTSW0DVcGaRhLQbZXcogSEP-ebbuGunuVbz45Ws8X6zJhZpASNQ-jknhGZEhZkSAQdwvihZpTsDdUuJx9RFDXNwA0lEaE_xediW119uJGywSNc6w8hnJZ6Xo49YQStuGbJKRAieQMvEhZXofiqCKyOUXSlsO7j9iE-rzis0JRSHWB8acA3AlcXqBj9D70AHfRHC_HfBLw9lcusy4dInmK2OCzGqXV11PoqibiZPqh-oNED31pToZQk4NB1xbOuUC_Tjf8UR_xAyhJ3yKzS09K898uCf-87Ra4iqRDCz3N35b=w2560-h1260)
|
||||
|
210
docs/tutorials/UKFast_SafeDNS.md
Normal file
210
docs/tutorials/UKFast_SafeDNS.md
Normal file
@ -0,0 +1,210 @@
|
||||
# Setting up ExternalDNS for Services on UKFast's SafeDNS
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using SafeDNS.
|
||||
|
||||
Make sure to use **>=0.11.0** version of ExternalDNS for this tutorial.
|
||||
|
||||
## Managing DNS with SafeDNS
|
||||
|
||||
If you want to learn about how to use the SafeDNS service read the following tutorials:
|
||||
To learn more about the use of SafeDNS in general, see the following page:
|
||||
|
||||
[UKFast's SafeDNS documentation](https://docs.ukfast.co.uk/domains/safedns/index.html).
|
||||
|
||||
## Creating SafeDNS credentials
|
||||
|
||||
Generate a fresh API token for use with ExternalDNS, following the instructions
|
||||
at the UKFast developer [Getting-Started](https://developers.ukfast.io/getting-started)
|
||||
page. You will need to grant read/write access to the SafeDNS API. No access to
|
||||
any other UKFast service is required.
|
||||
|
||||
The environment variable `SAFEDNS_TOKEN` must have a value of this token to run
|
||||
ExternalDNS with SafeDNS integration.
|
||||
|
||||
## Deploy ExternalDNS
|
||||
|
||||
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
|
||||
Then apply one of the following manifests file to deploy ExternalDNS.
|
||||
|
||||
### Manifest (for clusters without RBAC enabled)
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
# You will need to check what the latest version is yourself:
|
||||
# https://github.com/kubernetes-sigs/external-dns/releases
|
||||
image: k8s.gcr.io/external-dns/external-dns:vX.Y.Z
|
||||
args:
|
||||
- --source=service # ingress is also possible
|
||||
# (optional) limit to only example.com domains; change to match the
|
||||
# zone created above.
|
||||
- --domain-filter=example.com
|
||||
- --provider=safedns
|
||||
env:
|
||||
- name: SAFEDNS_TOKEN
|
||||
value: "SAFEDNSTOKENSAFEDNSTOKEN"
|
||||
```
|
||||
|
||||
### Manifest (for clusters with RBAC enabled)
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: external-dns
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: external-dns
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services","endpoints","pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions","networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: external-dns-viewer
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: external-dns
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: external-dns
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: k8s.gcr.io/external-dns/external-dns:v0.11.0
|
||||
args:
|
||||
- --source=service # ingress is also possible
|
||||
# (optional) limit to only example.com domains; change to match the
|
||||
# zone created above.
|
||||
- --domain-filter=example.com
|
||||
- --provider=safedns
|
||||
env:
|
||||
- name: SAFEDNS_TOKEN
|
||||
value: "SAFEDNSTOKENSAFEDNSTOKEN"
|
||||
```
|
||||
|
||||
## Deploying an Nginx Service
|
||||
|
||||
Create a service file called 'nginx.yaml' with the following contents:
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: nginx
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/hostname: my-app.example.com
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
type: LoadBalancer
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
```
|
||||
|
||||
Note the annotation on the service; use a hostname that matches the domain
|
||||
filter specified above.
|
||||
|
||||
ExternalDNS uses this annotation to determine what services should be registered
|
||||
with DNS. Removing the annotation will cause ExternalDNS to remove the
|
||||
corresponding DNS records.
|
||||
|
||||
Create the deployment and service:
|
||||
|
||||
```console
|
||||
$ kubectl create -f nginx.yaml
|
||||
```
|
||||
|
||||
Depending where you run your service it can take a little while for your cloud
|
||||
provider to create an external IP for the service.
|
||||
|
||||
Once the service has an external IP assigned, ExternalDNS will notice the new
|
||||
service IP address and synchronize the SafeDNS records.
|
||||
|
||||
## Verifying SafeDNS records
|
||||
|
||||
Check your [SafeDNS UI](https://my.ukfast.co.uk/safedns/index.php) and select
|
||||
the appropriate domain to view the records for your SafeDNS zone.
|
||||
|
||||
This should show the external IP address of the service as the A record for your
|
||||
domain.
|
||||
|
||||
Alternatively, you can perform a DNS lookup for the hostname specified:
|
||||
```console
|
||||
$ dig +short my-app.example.com
|
||||
an.ip.addr.ess
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
Now that we have verified that ExternalDNS will automatically manage SafeDNS
|
||||
records, we can delete the tutorial's example:
|
||||
|
||||
```
|
||||
$ kubectl delete service -f nginx.yaml
|
||||
$ kubectl delete service -f externaldns.yaml
|
||||
```
|
@ -383,7 +383,7 @@ metadata:
|
||||
name: nginx
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com
|
||||
external-dns.alpha.kubernetes.io/ttl: 60
|
||||
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||
spec:
|
||||
...
|
||||
```
|
||||
|
@ -7,8 +7,72 @@ Install the BlueCat Gateway product and deploy the [community gateway workflows]
|
||||
|
||||
## Configuration Options
|
||||
|
||||
The options for configuring the Bluecat Provider are available through the json file provided to External-DNS via the flag `--bluecat-config-file`. The
|
||||
BlueCat Gateway username and password can be supplied using the configuration file or environment variables `BLUECAT_USERNAME` and `BLUECAT_PASSWORD`.
|
||||
There are two ways to pass configuration options to the Bluecat Provider JSON configuration file and command line flags. The JSON configuration file option
|
||||
is deprecated and will eventually be removed.
|
||||
|
||||
BlueCat provider supports getting the proxy URL from the environment variables. The format is the one specified by golang's [http.ProxyFromEnvironment](https://pkg.go.dev/net/http#ProxyFromEnvironment).
|
||||
|
||||
### Using CLI Flags
|
||||
When using CLI flags to configure the Bluecat Provider the BlueCat Gateway credentials are passed in using environment variables `BLUECAT_USERNAME` and `BLUECAT_PASSWORD`.
|
||||
|
||||
#### Deploy
|
||||
Setup up namespace, deployment, and service account:
|
||||
```
|
||||
kubectl create namespace bluecat-example
|
||||
kubectl create secret generic bluecat-credentials --from-literal=username=bluecatuser --from-literal=password=bluecatpassword -n bluecat-example
|
||||
cat << EOF > ~/bluecat.yml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: external-dns
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
strategy:
|
||||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: k8s.gcr.io/external-dns/external-dns:v0.8.0
|
||||
args:
|
||||
- --log-level=debug
|
||||
- --source=service
|
||||
- --provider=bluecat
|
||||
- --txt-owner-id=bluecat-example
|
||||
- --bluecat-dns-configuration=Example
|
||||
- --bluecat-dns-view=Internal
|
||||
- --bluecat-gateway-host=https://bluecatgw.example.com
|
||||
- --bluecat-root-zone=example.com
|
||||
env:
|
||||
- name: BLUECAT_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: bluecat-credentials
|
||||
key: username
|
||||
- name: BLUECAT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: bluecat-credentials
|
||||
key: password
|
||||
EOF
|
||||
kubectl apply -f ~/bluecat.yml -n bluecat-example
|
||||
```
|
||||
|
||||
|
||||
### Using JSON Configuration file (DEPRECATED)
|
||||
The options for configuring the Bluecat Provider are available through the JSON file provided to External-DNS via the flag `--bluecat-config-file`.
|
||||
|
||||
| Key | Required |
|
||||
| ----------------- | ------------------ |
|
||||
@ -18,13 +82,11 @@ BlueCat Gateway username and password can be supplied using the configuration fi
|
||||
| dnsConfiguration | Yes |
|
||||
| dnsView | Yes |
|
||||
| rootZone | Yes |
|
||||
| dnsServerName | No |
|
||||
| dnsDeployType | No |
|
||||
| skipTLSVerify | No (default false) |
|
||||
|
||||
### HTTP proxy
|
||||
|
||||
BlueCat provider supports getting the proxy URL from the environment variables. The format is the one specified by golang's [http.ProxyFromEnvironment](https://pkg.go.dev/net/http#ProxyFromEnvironment).
|
||||
|
||||
## Deploy
|
||||
#### Deploy
|
||||
Setup configuration file as k8s `Secret`.
|
||||
```
|
||||
cat << EOF > ~/bluecat.json
|
||||
|
148
go.mod
148
go.mod
@ -1,84 +1,164 @@
|
||||
module sigs.k8s.io/external-dns
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.97.0
|
||||
cloud.google.com/go/compute v1.2.0
|
||||
git.blindage.org/21h/hcloud-dns v0.0.0-20200807003420-f768ffe03f8d
|
||||
github.com/Azure/azure-sdk-for-go v46.4.0+incompatible
|
||||
github.com/Azure/azure-sdk-for-go v61.4.0+incompatible
|
||||
github.com/Azure/go-autorest/autorest v0.11.21
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.16
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1
|
||||
github.com/StackExchange/dnscontrol v0.2.8
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
|
||||
github.com/alecthomas/colour v0.1.0 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1
|
||||
github.com/alecthomas/kingpin v2.2.5+incompatible
|
||||
github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.357
|
||||
github.com/aws/aws-sdk-go v1.40.53
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1483
|
||||
github.com/aws/aws-sdk-go v1.42.52
|
||||
github.com/bodgit/tsig v0.0.2
|
||||
github.com/cloudflare/cloudflare-go v0.25.0
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/datawire/ambassador v1.6.0
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba
|
||||
github.com/digitalocean/godo v1.69.1
|
||||
github.com/dnsimple/dnsimple-go v0.60.0
|
||||
github.com/exoscale/egoscale v0.80.1
|
||||
github.com/digitalocean/godo v1.75.0
|
||||
github.com/dnsimple/dnsimple-go v0.71.1
|
||||
github.com/exoscale/egoscale v1.19.0
|
||||
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
|
||||
github.com/go-gandi/go-gandi v0.0.0-20200921091836-0d8a64b9cc09
|
||||
github.com/go-logr/logr v1.1.0 // indirect
|
||||
github.com/golang/sync v0.0.0-20180314180146-1d60e4601c6f
|
||||
github.com/google/go-cmp v0.5.6
|
||||
github.com/google/go-cmp v0.5.7
|
||||
github.com/gophercloud/gophercloud v0.22.0
|
||||
github.com/hooklift/gowsdl v0.5.0
|
||||
github.com/infobloxopen/infoblox-go-client v1.1.1
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/linki/instrumented_http v0.3.0
|
||||
github.com/linode/linodego v0.32.2
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/maxatome/go-testdeep v1.10.1
|
||||
github.com/miekg/dns v1.1.36-0.20210109083720-731b191cabd1
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/nesv/go-dynect v0.6.0
|
||||
github.com/nic-at/rc0go v1.1.1
|
||||
github.com/onsi/gomega v1.14.0 // indirect
|
||||
github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae
|
||||
github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73
|
||||
github.com/oracle/oci-go-sdk v21.4.0+incompatible
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/projectcontour/contour v1.18.2
|
||||
github.com/projectcontour/contour v1.20.0
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/smartystreets/gunit v1.3.4 // indirect
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/terra-farm/udnssdk v1.3.5 // indirect
|
||||
github.com/transip/gotransip/v6 v6.6.2
|
||||
github.com/ukfast/sdk-go v1.4.23
|
||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
||||
github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556
|
||||
github.com/vultr/govultr/v2 v2.9.0
|
||||
go.etcd.io/etcd/api/v3 v3.5.0
|
||||
go.etcd.io/etcd/client/v3 v3.5.0
|
||||
go.uber.org/ratelimit v0.2.0
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
|
||||
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/api v0.58.0
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
google.golang.org/api v0.66.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
istio.io/api v0.0.0-20210128181506-0c4b8e54850f
|
||||
istio.io/client-go v0.0.0-20210128182905-ee2edd059e02
|
||||
k8s.io/api v0.22.2
|
||||
k8s.io/apimachinery v0.22.2
|
||||
k8s.io/client-go v0.22.2
|
||||
k8s.io/klog/v2 v2.20.0 // indirect
|
||||
k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e // indirect
|
||||
k8s.io/api v0.23.1
|
||||
k8s.io/apimachinery v0.23.3
|
||||
k8s.io/client-go v0.23.1
|
||||
)
|
||||
|
||||
require (
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
|
||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Masterminds/semver v1.4.2 // indirect
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
|
||||
github.com/alecthomas/colour v0.1.0 // indirect
|
||||
github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c // indirect
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 // indirect
|
||||
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.12.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
|
||||
github.com/gofrs/uuid v3.2.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/googleapis/gnostic v0.5.5 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/gofork v1.0.0 // indirect
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.1 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.2 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.28.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
|
||||
github.com/smartystreets/gunit v1.3.4 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.3.0 // indirect
|
||||
github.com/terra-farm/udnssdk v1.3.5 // indirect
|
||||
github.com/ukfast/go-durationstring v1.0.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.19.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44 // indirect
|
||||
google.golang.org/grpc v1.42.0 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.27.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.66.2 // indirect
|
||||
gopkg.in/resty.v1 v1.12.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a // indirect
|
||||
k8s.io/klog/v2 v2.30.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect
|
||||
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
|
||||
sigs.k8s.io/controller-runtime v0.11.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
|
36
main.go
36
main.go
@ -62,6 +62,7 @@ import (
|
||||
"sigs.k8s.io/external-dns/provider/rcode0"
|
||||
"sigs.k8s.io/external-dns/provider/rdns"
|
||||
"sigs.k8s.io/external-dns/provider/rfc2136"
|
||||
"sigs.k8s.io/external-dns/provider/safedns"
|
||||
"sigs.k8s.io/external-dns/provider/scaleway"
|
||||
"sigs.k8s.io/external-dns/provider/transip"
|
||||
"sigs.k8s.io/external-dns/provider/ultradns"
|
||||
@ -136,7 +137,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Lookup all the selected sources by names and pass them the desired configuration.
|
||||
sources, err := source.ByNames(&source.SingletonClientGenerator{
|
||||
sources, err := source.ByNames(ctx, &source.SingletonClientGenerator{
|
||||
KubeConfig: cfg.KubeConfig,
|
||||
APIServerURL: cfg.APIServerURL,
|
||||
// If update events are enabled, disable timeout.
|
||||
@ -206,13 +207,13 @@ func main() {
|
||||
log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
|
||||
cfg.Registry = "aws-sd"
|
||||
}
|
||||
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.DryRun)
|
||||
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID)
|
||||
case "azure-dns", "azure":
|
||||
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
||||
case "azure-private-dns":
|
||||
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
||||
case "bluecat":
|
||||
p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, cfg.BluecatDNSConfiguration, cfg.BluecatDNSServerName, cfg.BluecatDNSDeployType, cfg.BluecatDNSView, cfg.BluecatGatewayHost, cfg.BluecatRootZone, domainFilter, zoneIDFilter, cfg.DryRun, cfg.BluecatSkipTLSVerify)
|
||||
case "vinyldns":
|
||||
p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
case "vultr":
|
||||
@ -238,19 +239,20 @@ func main() {
|
||||
case "infoblox":
|
||||
p, err = infoblox.NewInfobloxProvider(
|
||||
infoblox.InfobloxConfig{
|
||||
DomainFilter: domainFilter,
|
||||
ZoneIDFilter: zoneIDFilter,
|
||||
Host: cfg.InfobloxGridHost,
|
||||
Port: cfg.InfobloxWapiPort,
|
||||
Username: cfg.InfobloxWapiUsername,
|
||||
Password: cfg.InfobloxWapiPassword,
|
||||
Version: cfg.InfobloxWapiVersion,
|
||||
SSLVerify: cfg.InfobloxSSLVerify,
|
||||
View: cfg.InfobloxView,
|
||||
MaxResults: cfg.InfobloxMaxResults,
|
||||
DryRun: cfg.DryRun,
|
||||
FQDNRexEx: cfg.InfobloxFQDNRegEx,
|
||||
CreatePTR: cfg.InfobloxCreatePTR,
|
||||
DomainFilter: domainFilter,
|
||||
ZoneIDFilter: zoneIDFilter,
|
||||
Host: cfg.InfobloxGridHost,
|
||||
Port: cfg.InfobloxWapiPort,
|
||||
Username: cfg.InfobloxWapiUsername,
|
||||
Password: cfg.InfobloxWapiPassword,
|
||||
Version: cfg.InfobloxWapiVersion,
|
||||
SSLVerify: cfg.InfobloxSSLVerify,
|
||||
View: cfg.InfobloxView,
|
||||
MaxResults: cfg.InfobloxMaxResults,
|
||||
DryRun: cfg.DryRun,
|
||||
FQDNRexEx: cfg.InfobloxFQDNRegEx,
|
||||
CreatePTR: cfg.InfobloxCreatePTR,
|
||||
CacheDuration: cfg.InfobloxCacheDuration,
|
||||
},
|
||||
)
|
||||
case "dyn":
|
||||
@ -324,6 +326,8 @@ func main() {
|
||||
p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun)
|
||||
case "gandi":
|
||||
p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun)
|
||||
case "safedns":
|
||||
p, err = safedns.NewSafeDNSProvider(domainFilter, cfg.DryRun)
|
||||
default:
|
||||
log.Fatalf("unknown dns provider: %s", cfg.Provider)
|
||||
}
|
||||
|
@ -88,11 +88,19 @@ type Config struct {
|
||||
AWSAPIRetries int
|
||||
AWSPreferCNAME bool
|
||||
AWSZoneCacheDuration time.Duration
|
||||
AWSSDServiceCleanup bool
|
||||
AzureConfigFile string
|
||||
AzureResourceGroup string
|
||||
AzureSubscriptionID string
|
||||
AzureUserAssignedIdentityClientID string
|
||||
BluecatDNSConfiguration string
|
||||
BluecatConfigFile string
|
||||
BluecatDNSView string
|
||||
BluecatGatewayHost string
|
||||
BluecatRootZone string
|
||||
BluecatDNSServerName string
|
||||
BluecatDNSDeployType string
|
||||
BluecatSkipTLSVerify bool
|
||||
CloudflareProxied bool
|
||||
CloudflareZonesPerPage int
|
||||
CoreDNSPrefix string
|
||||
@ -113,6 +121,7 @@ type Config struct {
|
||||
InfobloxMaxResults int
|
||||
InfobloxFQDNRegEx string
|
||||
InfobloxCreatePTR bool
|
||||
InfobloxCacheDuration int
|
||||
DynCustomerName string
|
||||
DynUsername string
|
||||
DynPassword string `secure:"yes"`
|
||||
@ -220,10 +229,12 @@ var defaultConfig = &Config{
|
||||
AWSAPIRetries: 3,
|
||||
AWSPreferCNAME: false,
|
||||
AWSZoneCacheDuration: 0 * time.Second,
|
||||
AWSSDServiceCleanup: false,
|
||||
AzureConfigFile: "/etc/kubernetes/azure.json",
|
||||
AzureResourceGroup: "",
|
||||
AzureSubscriptionID: "",
|
||||
BluecatConfigFile: "/etc/kubernetes/bluecat.json",
|
||||
BluecatDNSDeployType: "no-deploy",
|
||||
CloudflareProxied: false,
|
||||
CloudflareZonesPerPage: 50,
|
||||
CoreDNSPrefix: "/skydns/",
|
||||
@ -244,6 +255,7 @@ var defaultConfig = &Config{
|
||||
InfobloxMaxResults: 0,
|
||||
InfobloxFQDNRegEx: "",
|
||||
InfobloxCreatePTR: false,
|
||||
InfobloxCacheDuration: 0,
|
||||
OCIConfigFile: "/etc/kubernetes/oci.yaml",
|
||||
InMemoryZones: []string{},
|
||||
OVHEndpoint: "ovh-eu",
|
||||
@ -388,7 +400,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("default-targets", "Set globally default IP address that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional)").StringsVar(&cfg.DefaultTargets)
|
||||
|
||||
// Flags related to providers
|
||||
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi")
|
||||
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, hetzner, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "hetzner", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns")
|
||||
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
|
||||
app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains)
|
||||
app.Flag("regex-domain-filter", "Limit possible domains and target zones by a Regex filter; Overrides domain-filter (optional)").Default(defaultConfig.RegexDomainFilter.String()).RegexpVar(&cfg.RegexDomainFilter)
|
||||
@ -410,11 +422,22 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("aws-api-retries", "When using the AWS provider, set the maximum number of retries for API calls before giving up.").Default(strconv.Itoa(defaultConfig.AWSAPIRetries)).IntVar(&cfg.AWSAPIRetries)
|
||||
app.Flag("aws-prefer-cname", "When using the AWS provider, prefer using CNAME instead of ALIAS (default: disabled)").BoolVar(&cfg.AWSPreferCNAME)
|
||||
app.Flag("aws-zones-cache-duration", "When using the AWS provider, set the zones list cache TTL (0s to disable).").Default(defaultConfig.AWSZoneCacheDuration.String()).DurationVar(&cfg.AWSZoneCacheDuration)
|
||||
app.Flag("aws-sd-service-cleanup", "When using the AWS CloudMap provider, delete empty Services without endpoints (default: disabled)").BoolVar(&cfg.AWSSDServiceCleanup)
|
||||
app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile)
|
||||
app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (required when --provider=azure-private-dns)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)
|
||||
app.Flag("azure-subscription-id", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure-private-dns)").Default(defaultConfig.AzureSubscriptionID).StringVar(&cfg.AzureSubscriptionID)
|
||||
app.Flag("azure-user-assigned-identity-client-id", "When using the Azure provider, override the client id of user assigned identity in config file (optional)").Default("").StringVar(&cfg.AzureUserAssignedIdentityClientID)
|
||||
app.Flag("bluecat-config-file", "When using the Bluecat provider, specify the Bluecat configuration file (required when --provider=bluecat").Default(defaultConfig.BluecatConfigFile).StringVar(&cfg.BluecatConfigFile)
|
||||
|
||||
// Flags related to BlueCat provider
|
||||
app.Flag("bluecat-dns-configuration", "When using the Bluecat provider, specify the Bluecat DNS configuration string (optional when --provider=bluecat)").Default("").StringVar(&cfg.BluecatDNSConfiguration)
|
||||
app.Flag("bluecat-config-file", "When using the Bluecat provider, specify the Bluecat configuration file (optional when --provider=bluecat)").Default(defaultConfig.BluecatConfigFile).StringVar(&cfg.BluecatConfigFile)
|
||||
app.Flag("bluecat-dns-view", "When using the Bluecat provider, specify the Bluecat DNS view string (optional when --provider=bluecat)").Default("").StringVar(&cfg.BluecatDNSView)
|
||||
app.Flag("bluecat-gateway-host", "When using the Bluecat provider, specify the Bluecat Gateway Host (optional when --provider=bluecat)").Default("").StringVar(&cfg.BluecatGatewayHost)
|
||||
app.Flag("bluecat-root-zone", "When using the Bluecat provider, specify the Bluecat root zone (optional when --provider=bluecat)").Default("").StringVar(&cfg.BluecatRootZone)
|
||||
app.Flag("bluecat-skip-tls-verify", "When using the Bluecat provider, specify to skip TLS verification (optional when --provider=bluecat) (default: false)").BoolVar(&cfg.BluecatSkipTLSVerify)
|
||||
app.Flag("bluecat-dns-server-name", "When using the Bluecat provider, specify the Bluecat DNS Server to initiate deploys against. This is only used if --bluecat-dns-deploy-type is not 'no-deploy' (optional when --provider=bluecat)").Default("").StringVar(&cfg.BluecatDNSServerName)
|
||||
app.Flag("bluecat-dns-deploy-type", "When using the Bluecat provider, specify the type of DNS deployment to initiate after records are updated. Valid options are 'full-deploy' and 'no-deploy'. Deploy will only execute if --bluecat-dns-server-name is set (optional when --provider=bluecat)").Default(defaultConfig.BluecatDNSDeployType).StringVar(&cfg.BluecatDNSDeployType)
|
||||
|
||||
app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied)
|
||||
app.Flag("cloudflare-zones-per-page", "When using the Cloudflare provider, specify how many zones per page listed, max. possible 50 (default: 50)").Default(strconv.Itoa(defaultConfig.CloudflareZonesPerPage)).IntVar(&cfg.CloudflareZonesPerPage)
|
||||
app.Flag("coredns-prefix", "When using the CoreDNS provider, specify the prefix name").Default(defaultConfig.CoreDNSPrefix).StringVar(&cfg.CoreDNSPrefix)
|
||||
@ -434,6 +457,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("infoblox-max-results", "Add _max_results as query parameter to the URL on all API requests. The default is 0 which means _max_results is not set and the default of the server is used.").Default(strconv.Itoa(defaultConfig.InfobloxMaxResults)).IntVar(&cfg.InfobloxMaxResults)
|
||||
app.Flag("infoblox-fqdn-regex", "Apply this regular expression as a filter for obtaining zone_auth objects. This is disabled by default.").Default(defaultConfig.InfobloxFQDNRegEx).StringVar(&cfg.InfobloxFQDNRegEx)
|
||||
app.Flag("infoblox-create-ptr", "When using the Infoblox provider, create a ptr entry in addition to an entry").Default(strconv.FormatBool(defaultConfig.InfobloxCreatePTR)).BoolVar(&cfg.InfobloxCreatePTR)
|
||||
app.Flag("infoblox-cache-duration", "When using the Infoblox provider, set the record TTL (0s to disable).").Default(strconv.Itoa(defaultConfig.InfobloxCacheDuration)).IntVar(&cfg.InfobloxCacheDuration)
|
||||
app.Flag("dyn-customer-name", "When using the Dyn provider, specify the Customer Name").Default("").StringVar(&cfg.DynCustomerName)
|
||||
app.Flag("dyn-username", "When using the Dyn provider, specify the Username").Default("").StringVar(&cfg.DynUsername)
|
||||
app.Flag("dyn-password", "When using the Dyn provider, specify the password").Default("").StringVar(&cfg.DynPassword)
|
||||
|
@ -63,10 +63,18 @@ var (
|
||||
AWSAPIRetries: 3,
|
||||
AWSPreferCNAME: false,
|
||||
AWSZoneCacheDuration: 0 * time.Second,
|
||||
AWSSDServiceCleanup: false,
|
||||
AzureConfigFile: "/etc/kubernetes/azure.json",
|
||||
AzureResourceGroup: "",
|
||||
AzureSubscriptionID: "",
|
||||
BluecatDNSConfiguration: "",
|
||||
BluecatDNSServerName: "",
|
||||
BluecatConfigFile: "/etc/kubernetes/bluecat.json",
|
||||
BluecatDNSView: "",
|
||||
BluecatGatewayHost: "",
|
||||
BluecatRootZone: "",
|
||||
BluecatDNSDeployType: defaultConfig.BluecatDNSDeployType,
|
||||
BluecatSkipTLSVerify: false,
|
||||
CloudflareProxied: false,
|
||||
CloudflareZonesPerPage: 50,
|
||||
CoreDNSPrefix: "/skydns/",
|
||||
@ -153,10 +161,18 @@ var (
|
||||
AWSAPIRetries: 13,
|
||||
AWSPreferCNAME: true,
|
||||
AWSZoneCacheDuration: 10 * time.Second,
|
||||
AWSSDServiceCleanup: true,
|
||||
AzureConfigFile: "azure.json",
|
||||
AzureResourceGroup: "arg",
|
||||
AzureSubscriptionID: "arg",
|
||||
BluecatDNSConfiguration: "arg",
|
||||
BluecatDNSServerName: "arg",
|
||||
BluecatConfigFile: "bluecat.json",
|
||||
BluecatDNSView: "arg",
|
||||
BluecatGatewayHost: "arg",
|
||||
BluecatRootZone: "arg",
|
||||
BluecatDNSDeployType: "full-deploy",
|
||||
BluecatSkipTLSVerify: true,
|
||||
CloudflareProxied: true,
|
||||
CloudflareZonesPerPage: 20,
|
||||
CoreDNSPrefix: "/coredns/",
|
||||
@ -209,7 +225,7 @@ var (
|
||||
TransIPAccountName: "transip",
|
||||
TransIPPrivateKeyFile: "/path/to/transip.key",
|
||||
DigitalOceanAPIPageSize: 100,
|
||||
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
|
||||
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME, endpoint.RecordTypeNS},
|
||||
RFC2136BatchChangeSize: 100,
|
||||
}
|
||||
)
|
||||
@ -257,7 +273,14 @@ func TestParseFlags(t *testing.T) {
|
||||
"--azure-config-file=azure.json",
|
||||
"--azure-resource-group=arg",
|
||||
"--azure-subscription-id=arg",
|
||||
"--bluecat-dns-configuration=arg",
|
||||
"--bluecat-config-file=bluecat.json",
|
||||
"--bluecat-dns-view=arg",
|
||||
"--bluecat-dns-server-name=arg",
|
||||
"--bluecat-gateway-host=arg",
|
||||
"--bluecat-root-zone=arg",
|
||||
"--bluecat-dns-deploy-type=full-deploy",
|
||||
"--bluecat-skip-tls-verify",
|
||||
"--cloudflare-proxied",
|
||||
"--cloudflare-zones-per-page=20",
|
||||
"--coredns-prefix=/coredns/",
|
||||
@ -304,6 +327,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"--aws-api-retries=13",
|
||||
"--aws-prefer-cname",
|
||||
"--aws-zones-cache-duration=10s",
|
||||
"--aws-sd-service-cleanup",
|
||||
"--no-aws-evaluate-target-health",
|
||||
"--policy=upsert-only",
|
||||
"--registry=noop",
|
||||
@ -330,6 +354,9 @@ func TestParseFlags(t *testing.T) {
|
||||
"--transip-account=transip",
|
||||
"--transip-keyfile=/path/to/transip.key",
|
||||
"--digitalocean-api-page-size=100",
|
||||
"--managed-record-types=A",
|
||||
"--managed-record-types=CNAME",
|
||||
"--managed-record-types=NS",
|
||||
"--rfc2136-batch-change-size=100",
|
||||
},
|
||||
envVars: map[string]string{},
|
||||
@ -360,7 +387,14 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_AZURE_CONFIG_FILE": "azure.json",
|
||||
"EXTERNAL_DNS_AZURE_RESOURCE_GROUP": "arg",
|
||||
"EXTERNAL_DNS_AZURE_SUBSCRIPTION_ID": "arg",
|
||||
"EXTERNAL_DNS_BLUECAT_DNS_CONFIGURATION": "arg",
|
||||
"EXTERNAL_DNS_BLUECAT_DNS_SERVER_NAME": "arg",
|
||||
"EXTERNAL_DNS_BLUECAT_DNS_DEPLOY_TYPE": "full-deploy",
|
||||
"EXTERNAL_DNS_BLUECAT_CONFIG_FILE": "bluecat.json",
|
||||
"EXTERNAL_DNS_BLUECAT_DNS_VIEW": "arg",
|
||||
"EXTERNAL_DNS_BLUECAT_GATEWAY_HOST": "arg",
|
||||
"EXTERNAL_DNS_BLUECAT_ROOT_ZONE": "arg",
|
||||
"EXTERNAL_DNS_BLUECAT_SKIP_TLS_VERIFY": "1",
|
||||
"EXTERNAL_DNS_CLOUDFLARE_PROXIED": "1",
|
||||
"EXTERNAL_DNS_CLOUDFLARE_ZONES_PER_PAGE": "20",
|
||||
"EXTERNAL_DNS_COREDNS_PREFIX": "/coredns/",
|
||||
@ -404,6 +438,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_AWS_API_RETRIES": "13",
|
||||
"EXTERNAL_DNS_AWS_PREFER_CNAME": "true",
|
||||
"EXTERNAL_DNS_AWS_ZONES_CACHE_DURATION": "10s",
|
||||
"EXTERNAL_DNS_AWS_SD_SERVICE_CLEANUP": "true",
|
||||
"EXTERNAL_DNS_POLICY": "upsert-only",
|
||||
"EXTERNAL_DNS_REGISTRY": "noop",
|
||||
"EXTERNAL_DNS_TXT_OWNER_ID": "owner-1",
|
||||
@ -429,6 +464,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_TRANSIP_ACCOUNT": "transip",
|
||||
"EXTERNAL_DNS_TRANSIP_KEYFILE": "/path/to/transip.key",
|
||||
"EXTERNAL_DNS_DIGITALOCEAN_API_PAGE_SIZE": "100",
|
||||
"EXTERNAL_DNS_MANAGED_RECORD_TYPES": "A\nCNAME\nNS",
|
||||
"EXTERNAL_DNS_RFC2136_BATCH_CHANGE_SIZE": "100",
|
||||
},
|
||||
expected: overriddenConfig,
|
||||
|
@ -223,7 +223,7 @@ func (p AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint
|
||||
return endpoints, err
|
||||
}
|
||||
for _, zone := range zones.Zones {
|
||||
recordsets, err := p.client.GetRecordsets(zone.Zone, dns.RecordsetQueryArgs{})
|
||||
recordsets, err := p.client.GetRecordsets(zone.Zone, dns.RecordsetQueryArgs{ShowAll: true})
|
||||
if err != nil {
|
||||
log.Errorf("Recordsets retrieval for zone: '%s' failed! %s", zone.Zone, err.Error())
|
||||
continue
|
||||
@ -242,7 +242,7 @@ func (p AkamaiProvider) Records(context.Context) (endpoints []*endpoint.Endpoint
|
||||
continue
|
||||
}
|
||||
var temp interface{} = int64(recordset.TTL)
|
||||
var ttl endpoint.TTL = endpoint.TTL(temp.(int64))
|
||||
var ttl = endpoint.TTL(temp.(int64))
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(recordset.Name,
|
||||
recordset.Type,
|
||||
ttl,
|
||||
|
@ -94,7 +94,7 @@ func (m *MockAlibabaCloudDNSAPI) UpdateDomainRecord(request *alidns.UpdateDomain
|
||||
}
|
||||
|
||||
func (m *MockAlibabaCloudDNSAPI) DescribeDomains(request *alidns.DescribeDomainsRequest) (response *alidns.DescribeDomainsResponse, err error) {
|
||||
var result alidns.Domains
|
||||
var result alidns.DomainsInDescribeDomains
|
||||
for _, record := range m.records {
|
||||
domain := alidns.Domain{}
|
||||
domain.DomainName = record.DomainName
|
||||
@ -209,7 +209,7 @@ func (m *MockAlibabaCloudPrivateZoneAPI) DescribeZoneInfo(request *pvtz.Describe
|
||||
response = pvtz.CreateDescribeZoneInfoResponse()
|
||||
response.ZoneId = m.zone.ZoneId
|
||||
response.ZoneName = m.zone.ZoneName
|
||||
response.BindVpcs = pvtz.BindVpcs{Vpc: m.zone.Vpcs.Vpc}
|
||||
response.BindVpcs = pvtz.BindVpcsInDescribeZoneInfo{Vpc: m.zone.Vpcs.Vpc}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
@ -119,6 +119,8 @@ var (
|
||||
"elb.af-south-1.amazonaws.com": "Z203XCE67M25HM",
|
||||
// Global Accelerator
|
||||
"awsglobalaccelerator.com": "Z2BJ6XQ5FK7U4H",
|
||||
// Cloudfront
|
||||
"cloudfront.net": "Z2FDTNDATAQYW2",
|
||||
}
|
||||
)
|
||||
|
||||
@ -290,10 +292,7 @@ func (p *AWSProvider) Zones(ctx context.Context) (map[string]*route53.HostedZone
|
||||
// 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.Contains(s, "\\052") {
|
||||
s = strings.Replace(s, "\\052", "*", 1)
|
||||
}
|
||||
return s
|
||||
return strings.Replace(s, "\\052", "*", 1)
|
||||
}
|
||||
|
||||
// Records returns the list of records in a given hosted zone.
|
||||
@ -312,9 +311,6 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*route53.Hos
|
||||
for _, r := range resp.ResourceRecordSets {
|
||||
newEndpoints := make([]*endpoint.Endpoint, 0)
|
||||
|
||||
// TODO(linki, ownership): Remove once ownership system is in place.
|
||||
// See: https://github.com/kubernetes-sigs/external-dns/pull/122/files/74e2c3d3e237411e619aefc5aab694742001cdec#r109863370
|
||||
|
||||
if !provider.SupportedRecordType(aws.StringValue(r.Type)) {
|
||||
continue
|
||||
}
|
||||
@ -889,8 +885,5 @@ func canonicalHostedZone(hostname string) string {
|
||||
|
||||
// cleanZoneID removes the "/hostedzone/" prefix
|
||||
func cleanZoneID(id string) string {
|
||||
if strings.HasPrefix(id, "/hostedzone/") {
|
||||
id = strings.TrimPrefix(id, "/hostedzone/")
|
||||
}
|
||||
return id
|
||||
return strings.TrimPrefix(id, "/hostedzone/")
|
||||
}
|
||||
|
@ -64,12 +64,12 @@ var (
|
||||
type AWSSDClient interface {
|
||||
CreateService(input *sd.CreateServiceInput) (*sd.CreateServiceOutput, error)
|
||||
DeregisterInstance(input *sd.DeregisterInstanceInput) (*sd.DeregisterInstanceOutput, error)
|
||||
GetService(input *sd.GetServiceInput) (*sd.GetServiceOutput, error)
|
||||
ListInstancesPages(input *sd.ListInstancesInput, fn func(*sd.ListInstancesOutput, bool) bool) error
|
||||
ListNamespacesPages(input *sd.ListNamespacesInput, fn func(*sd.ListNamespacesOutput, bool) bool) error
|
||||
ListServicesPages(input *sd.ListServicesInput, fn func(*sd.ListServicesOutput, bool) bool) error
|
||||
RegisterInstance(input *sd.RegisterInstanceInput) (*sd.RegisterInstanceOutput, error)
|
||||
UpdateService(input *sd.UpdateServiceInput) (*sd.UpdateServiceOutput, error)
|
||||
DeleteService(input *sd.DeleteServiceInput) (*sd.DeleteServiceOutput, error)
|
||||
}
|
||||
|
||||
// AWSSDProvider is an implementation of Provider for AWS Cloud Map.
|
||||
@ -81,10 +81,14 @@ type AWSSDProvider struct {
|
||||
namespaceFilter endpoint.DomainFilter
|
||||
// filter namespace by type (private or public)
|
||||
namespaceTypeFilter *sd.NamespaceFilter
|
||||
// enables service without instances cleanup
|
||||
cleanEmptyService bool
|
||||
// filter services for removal
|
||||
ownerID string
|
||||
}
|
||||
|
||||
// NewAWSSDProvider initializes a new AWS Cloud Map based Provider.
|
||||
func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, assumeRole string, dryRun bool) (*AWSSDProvider, error) {
|
||||
func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, assumeRole string, dryRun, cleanEmptyService bool, ownerID string) (*AWSSDProvider, error) {
|
||||
config := aws.NewConfig()
|
||||
|
||||
config = config.WithHTTPClient(
|
||||
@ -113,9 +117,11 @@ func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string,
|
||||
|
||||
provider := &AWSSDProvider{
|
||||
client: sd.New(sess),
|
||||
dryRun: dryRun,
|
||||
namespaceFilter: domainFilter,
|
||||
namespaceTypeFilter: newSdNamespaceFilter(namespaceType),
|
||||
dryRun: dryRun,
|
||||
cleanEmptyService: cleanEmptyService,
|
||||
ownerID: ownerID,
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
@ -162,6 +168,12 @@ func (p *AWSSDProvider) Records(ctx context.Context) (endpoints []*endpoint.Endp
|
||||
ep := p.instancesToEndpoint(ns, srv, instances)
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
if len(instances) == 0 {
|
||||
err = p.DeleteService(srv)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to delete service \"%s\", error: %s", aws.StringValue(srv.Name), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,9 +297,8 @@ func (p *AWSSDProvider) submitCreates(namespaces []*sd.NamespaceSummary, changes
|
||||
}
|
||||
// update local list of services
|
||||
services[*srv.Name] = srv
|
||||
} else if (ch.RecordTTL.IsConfigured() && *srv.DnsConfig.DnsRecords[0].TTL != int64(ch.RecordTTL)) ||
|
||||
aws.StringValue(srv.Description) != ch.Labels[endpoint.AWSSDDescriptionLabel] {
|
||||
// update service when TTL or Description differ
|
||||
} else if ch.RecordTTL.IsConfigured() && *srv.DnsConfig.DnsRecords[0].TTL != int64(ch.RecordTTL) {
|
||||
// update service when TTL differ
|
||||
err = p.UpdateService(srv, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -359,13 +370,10 @@ func (p *AWSSDProvider) ListNamespaces() ([]*sd.NamespaceSummary, error) {
|
||||
|
||||
// ListServicesByNamespaceID returns list of services in given namespace. Returns map[srv_name]*sd.Service
|
||||
func (p *AWSSDProvider) ListServicesByNamespaceID(namespaceID *string) (map[string]*sd.Service, error) {
|
||||
serviceIds := make([]*string, 0)
|
||||
services := make([]*sd.ServiceSummary, 0)
|
||||
|
||||
f := func(resp *sd.ListServicesOutput, lastPage bool) bool {
|
||||
for _, srv := range resp.Services {
|
||||
serviceIds = append(serviceIds, srv.Id)
|
||||
}
|
||||
|
||||
services = append(services, resp.Services...)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -374,35 +382,31 @@ func (p *AWSSDProvider) ListServicesByNamespaceID(namespaceID *string) (map[stri
|
||||
Name: aws.String(sd.ServiceFilterNameNamespaceId),
|
||||
Values: []*string{namespaceID},
|
||||
}},
|
||||
MaxResults: aws.Int64(100),
|
||||
}, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get detail of each listed service
|
||||
services := make(map[string]*sd.Service)
|
||||
for _, serviceID := range serviceIds {
|
||||
service, err := p.GetServiceDetail(serviceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
servicesMap := make(map[string]*sd.Service)
|
||||
for _, serviceSummary := range services {
|
||||
service := &sd.Service{
|
||||
Arn: serviceSummary.Arn,
|
||||
CreateDate: serviceSummary.CreateDate,
|
||||
Description: serviceSummary.Description,
|
||||
DnsConfig: serviceSummary.DnsConfig,
|
||||
HealthCheckConfig: serviceSummary.HealthCheckConfig,
|
||||
HealthCheckCustomConfig: serviceSummary.HealthCheckCustomConfig,
|
||||
Id: serviceSummary.Id,
|
||||
InstanceCount: serviceSummary.InstanceCount,
|
||||
Name: serviceSummary.Name,
|
||||
NamespaceId: namespaceID,
|
||||
Type: serviceSummary.Type,
|
||||
}
|
||||
|
||||
services[aws.StringValue(service.Name)] = service
|
||||
servicesMap[aws.StringValue(service.Name)] = service
|
||||
}
|
||||
|
||||
return services, nil
|
||||
}
|
||||
|
||||
// GetServiceDetail returns detail of given service
|
||||
func (p *AWSSDProvider) GetServiceDetail(serviceID *string) (*sd.Service, error) {
|
||||
output, err := p.client.GetService(&sd.GetServiceInput{
|
||||
Id: serviceID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return output.Service, nil
|
||||
return servicesMap, nil
|
||||
}
|
||||
|
||||
// ListInstancesByServiceID returns list of instances registered in given service.
|
||||
@ -491,6 +495,27 @@ func (p *AWSSDProvider) UpdateService(service *sd.Service, ep *endpoint.Endpoint
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteService deletes empty Service from AWS API if its owner id match
|
||||
func (p *AWSSDProvider) DeleteService(service *sd.Service) error {
|
||||
log.Debugf("Check if service \"%s\" owner id match and it can be deleted", *service.Name)
|
||||
if !p.dryRun && p.cleanEmptyService {
|
||||
// convert ownerID string to service description format
|
||||
label := endpoint.NewLabels()
|
||||
label[endpoint.OwnerLabelKey] = p.ownerID
|
||||
label[endpoint.AWSSDDescriptionLabel] = label.Serialize(false)
|
||||
|
||||
if aws.StringValue(service.Description) == label[endpoint.AWSSDDescriptionLabel] {
|
||||
log.Infof("Deleting service \"%s\"", *service.Name)
|
||||
_, err := p.client.DeleteService(&sd.DeleteServiceInput{
|
||||
Id: aws.String(*service.Id),
|
||||
})
|
||||
return err
|
||||
}
|
||||
log.Debugf("Skipping service removal %s because owner id does not match, found: \"%s\", required: \"%s\"", aws.StringValue(service.Name), aws.StringValue(service.Description), label[endpoint.AWSSDDescriptionLabel])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterInstance creates a new instance in given service.
|
||||
func (p *AWSSDProvider) RegisterInstance(service *sd.Service, ep *endpoint.Endpoint) error {
|
||||
for _, target := range ep.Targets {
|
||||
@ -578,11 +603,16 @@ func serviceToServiceSummary(service *sd.Service) *sd.ServiceSummary {
|
||||
}
|
||||
|
||||
return &sd.ServiceSummary{
|
||||
Name: service.Name,
|
||||
Id: service.Id,
|
||||
Arn: service.Arn,
|
||||
Description: service.Description,
|
||||
InstanceCount: service.InstanceCount,
|
||||
Arn: service.Arn,
|
||||
CreateDate: service.CreateDate,
|
||||
Description: service.Description,
|
||||
DnsConfig: service.DnsConfig,
|
||||
HealthCheckConfig: service.HealthCheckConfig,
|
||||
HealthCheckCustomConfig: service.HealthCheckCustomConfig,
|
||||
Id: service.Id,
|
||||
InstanceCount: service.InstanceCount,
|
||||
Name: service.Name,
|
||||
Type: service.Type,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,12 +181,27 @@ func (s *AWSSDClientStub) UpdateService(input *sd.UpdateServiceInput) (*sd.Updat
|
||||
return &sd.UpdateServiceOutput{}, nil
|
||||
}
|
||||
|
||||
func newTestAWSSDProvider(api AWSSDClient, domainFilter endpoint.DomainFilter, namespaceTypeFilter string) *AWSSDProvider {
|
||||
func (s *AWSSDClientStub) DeleteService(input *sd.DeleteServiceInput) (*sd.DeleteServiceOutput, error) {
|
||||
out, err := s.GetService(&sd.GetServiceInput{Id: input.Id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := out.Service
|
||||
namespace := s.services[*service.NamespaceId]
|
||||
delete(namespace, *input.Id)
|
||||
|
||||
return &sd.DeleteServiceOutput{}, nil
|
||||
}
|
||||
|
||||
func newTestAWSSDProvider(api AWSSDClient, domainFilter endpoint.DomainFilter, namespaceTypeFilter, ownerID string) *AWSSDProvider {
|
||||
return &AWSSDProvider{
|
||||
client: api,
|
||||
dryRun: false,
|
||||
namespaceFilter: domainFilter,
|
||||
namespaceTypeFilter: newSdNamespaceFilter(namespaceTypeFilter),
|
||||
dryRun: false,
|
||||
cleanEmptyService: true,
|
||||
ownerID: ownerID,
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,7 +303,7 @@ func TestAWSSDProvider_Records(t *testing.T) {
|
||||
instances: instances,
|
||||
}
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "")
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
endpoints, _ := provider.Records(context.Background())
|
||||
|
||||
@ -316,7 +331,7 @@ func TestAWSSDProvider_ApplyChanges(t *testing.T) {
|
||||
{DNSName: "service3.private.com", Targets: endpoint.Targets{"cname.target.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 100},
|
||||
}
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "")
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
@ -376,7 +391,7 @@ func TestAWSSDProvider_ListNamespaces(t *testing.T) {
|
||||
{"domain filter", endpoint.NewDomainFilter([]string{"public.com"}), "", []*sd.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
|
||||
{"non-existing domain", endpoint.NewDomainFilter([]string{"xxx.com"}), "", []*sd.NamespaceSummary{}},
|
||||
} {
|
||||
provider := newTestAWSSDProvider(api, tc.domainFilter, tc.namespaceTypeFilter)
|
||||
provider := newTestAWSSDProvider(api, tc.domainFilter, tc.namespaceTypeFilter, "")
|
||||
|
||||
result, err := provider.ListNamespaces()
|
||||
require.NoError(t, err)
|
||||
@ -413,18 +428,21 @@ func TestAWSSDProvider_ListServicesByNamespace(t *testing.T) {
|
||||
services := map[string]map[string]*sd.Service{
|
||||
"private": {
|
||||
"srv1": {
|
||||
Id: aws.String("srv1"),
|
||||
Name: aws.String("service1"),
|
||||
Id: aws.String("srv1"),
|
||||
Name: aws.String("service1"),
|
||||
NamespaceId: aws.String("private"),
|
||||
},
|
||||
"srv2": {
|
||||
Id: aws.String("srv2"),
|
||||
Name: aws.String("service2"),
|
||||
Id: aws.String("srv2"),
|
||||
Name: aws.String("service2"),
|
||||
NamespaceId: aws.String("private"),
|
||||
},
|
||||
},
|
||||
"public": {
|
||||
"srv3": {
|
||||
Id: aws.String("srv3"),
|
||||
Name: aws.String("service3"),
|
||||
Id: aws.String("srv3"),
|
||||
Name: aws.String("service3"),
|
||||
NamespaceId: aws.String("public"),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -439,14 +457,11 @@ func TestAWSSDProvider_ListServicesByNamespace(t *testing.T) {
|
||||
}{
|
||||
{map[string]*sd.Service{"service1": services["private"]["srv1"], "service2": services["private"]["srv2"]}},
|
||||
} {
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "")
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
result, err := provider.ListServicesByNamespaceID(namespaces["private"].Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
if !reflect.DeepEqual(result, tc.expectedServices) {
|
||||
t.Errorf("AWSSDProvider.ListServicesByNamespaceID() error = %v, wantErr %v", result, tc.expectedServices)
|
||||
}
|
||||
assert.Equal(t, tc.expectedServices, result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -495,7 +510,7 @@ func TestAWSSDProvider_ListInstancesByService(t *testing.T) {
|
||||
instances: instances,
|
||||
}
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "")
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
result, err := provider.ListInstancesByServiceID(services["private"]["srv1"].Id)
|
||||
require.NoError(t, err)
|
||||
@ -532,7 +547,7 @@ func TestAWSSDProvider_CreateService(t *testing.T) {
|
||||
|
||||
expectedServices := make(map[string]*sd.Service)
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "")
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
// A type
|
||||
provider.CreateService(aws.String("private"), aws.String("A-srv"), &endpoint.Endpoint{
|
||||
@ -636,7 +651,7 @@ func TestAWSSDProvider_UpdateService(t *testing.T) {
|
||||
services: services,
|
||||
}
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "")
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
// update service with different TTL
|
||||
provider.UpdateService(services["private"]["srv1"], &endpoint.Endpoint{
|
||||
@ -647,6 +662,56 @@ func TestAWSSDProvider_UpdateService(t *testing.T) {
|
||||
assert.Equal(t, int64(100), *api.services["private"]["srv1"].DnsConfig.DnsRecords[0].TTL)
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_DeleteService(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
},
|
||||
}
|
||||
|
||||
services := map[string]map[string]*sd.Service{
|
||||
"private": {
|
||||
"srv1": {
|
||||
Id: aws.String("srv1"),
|
||||
Description: aws.String("heritage=external-dns,external-dns/owner=owner-id"),
|
||||
Name: aws.String("service1"),
|
||||
NamespaceId: aws.String("private"),
|
||||
},
|
||||
"srv2": {
|
||||
Id: aws.String("srv2"),
|
||||
Description: aws.String("heritage=external-dns,external-dns/owner=owner-id"),
|
||||
Name: aws.String("service2"),
|
||||
NamespaceId: aws.String("private"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
api := &AWSSDClientStub{
|
||||
namespaces: namespaces,
|
||||
services: services,
|
||||
}
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "owner-id")
|
||||
|
||||
// delete fist service
|
||||
err := provider.DeleteService(services["private"]["srv1"])
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, api.services["private"], 1)
|
||||
|
||||
expectedServices := map[string]*sd.Service{
|
||||
"srv2": {
|
||||
Id: aws.String("srv2"),
|
||||
Description: aws.String("heritage=external-dns,external-dns/owner=owner-id"),
|
||||
Name: aws.String("service2"),
|
||||
NamespaceId: aws.String("private"),
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedServices, api.services["private"])
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_RegisterInstance(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
"private": {
|
||||
@ -703,7 +768,7 @@ func TestAWSSDProvider_RegisterInstance(t *testing.T) {
|
||||
instances: make(map[string]map[string]*sd.Instance),
|
||||
}
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "")
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
expectedInstances := make(map[string]*sd.Instance)
|
||||
|
||||
@ -820,7 +885,7 @@ func TestAWSSDProvider_DeregisterInstance(t *testing.T) {
|
||||
instances: instances,
|
||||
}
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "")
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
provider.DeregisterInstance(services["private"]["srv1"], endpoint.NewEndpoint("srv1.private.com.", endpoint.RecordTypeA, "1.2.3.4"))
|
||||
|
||||
|
@ -216,7 +216,7 @@ func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneID
|
||||
},
|
||||
}
|
||||
|
||||
mockZoneListResultPage := privatedns.NewPrivateZoneListResultPage(pageIterator.getNextPage)
|
||||
mockZoneListResultPage := privatedns.NewPrivateZoneListResultPage(privatedns.PrivateZoneListResult{}, pageIterator.getNextPage)
|
||||
mockZoneClientIterator := privatedns.NewPrivateZoneListResultIterator(mockZoneListResultPage)
|
||||
zonesClient := mockPrivateZonesClient{
|
||||
mockZonesClientIterator: &mockZoneClientIterator,
|
||||
@ -230,7 +230,8 @@ func newMockedAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneID
|
||||
},
|
||||
},
|
||||
}
|
||||
mockRecordSetListResultPage := privatedns.NewRecordSetListResultPage(resultPageIterator.getNextPage)
|
||||
|
||||
mockRecordSetListResultPage := privatedns.NewRecordSetListResultPage(privatedns.RecordSetListResult{}, resultPageIterator.getNextPage)
|
||||
mockRecordSetListIterator := privatedns.NewRecordSetListResultIterator(mockRecordSetListResultPage)
|
||||
recordSetsClient := mockPrivateRecordSetsClient{
|
||||
mockRecordSetListIterator: &mockRecordSetListIterator,
|
||||
@ -370,7 +371,7 @@ func testAzurePrivateDNSApplyChangesInternal(t *testing.T, dryRun bool, client P
|
||||
zlr,
|
||||
}
|
||||
|
||||
mockZoneListResultPage := privatedns.NewPrivateZoneListResultPage(func(ctxParam context.Context, zlrParam privatedns.PrivateZoneListResult) (privatedns.PrivateZoneListResult, error) {
|
||||
mockZoneListResultPage := privatedns.NewPrivateZoneListResultPage(privatedns.PrivateZoneListResult{}, func(ctxParam context.Context, zlrParam privatedns.PrivateZoneListResult) (privatedns.PrivateZoneListResult, error) {
|
||||
if len(results) > 0 {
|
||||
result := results[0]
|
||||
results = nil
|
||||
|
@ -215,7 +215,7 @@ func newMockedAzureProvider(domainFilter endpoint.DomainFilter, zoneNameFilter e
|
||||
},
|
||||
}
|
||||
|
||||
mockZoneListResultPage := dns.NewZoneListResultPage(pageIterator.getNextPage)
|
||||
mockZoneListResultPage := dns.NewZoneListResultPage(dns.ZoneListResult{}, pageIterator.getNextPage)
|
||||
mockZoneClientIterator := dns.NewZoneListResultIterator(mockZoneListResultPage)
|
||||
zonesClient := mockZonesClient{
|
||||
mockZonesClientIterator: &mockZoneClientIterator,
|
||||
@ -229,7 +229,8 @@ func newMockedAzureProvider(domainFilter endpoint.DomainFilter, zoneNameFilter e
|
||||
},
|
||||
},
|
||||
}
|
||||
mockRecordSetListResultPage := dns.NewRecordSetListResultPage(resultPageIterator.getNextPage)
|
||||
|
||||
mockRecordSetListResultPage := dns.NewRecordSetListResultPage(dns.RecordSetListResult{}, resultPageIterator.getNextPage)
|
||||
mockRecordSetListIterator := dns.NewRecordSetListResultIterator(mockRecordSetListResultPage)
|
||||
recordSetsClient := mockRecordSetsClient{
|
||||
mockRecordSetListIterator: &mockRecordSetListIterator,
|
||||
@ -377,7 +378,7 @@ func testAzureApplyChangesInternal(t *testing.T, dryRun bool, client RecordSetsC
|
||||
zlr,
|
||||
}
|
||||
|
||||
mockZoneListResultPage := dns.NewZoneListResultPage(func(ctxParam context.Context, zlrParam dns.ZoneListResult) (dns.ZoneListResult, error) {
|
||||
mockZoneListResultPage := dns.NewZoneListResultPage(dns.ZoneListResult{}, func(ctxParam context.Context, zlrParam dns.ZoneListResult) (dns.ZoneListResult, error) {
|
||||
if len(results) > 0 {
|
||||
result := results[0]
|
||||
results = nil
|
||||
@ -512,7 +513,7 @@ func testAzureApplyChangesInternalZoneName(t *testing.T, dryRun bool, client Rec
|
||||
zlr,
|
||||
}
|
||||
|
||||
mockZoneListResultPage := dns.NewZoneListResultPage(func(ctxParam context.Context, zlrParam dns.ZoneListResult) (dns.ZoneListResult, error) {
|
||||
mockZoneListResultPage := dns.NewZoneListResultPage(dns.ZoneListResult{}, func(ctxParam context.Context, zlrParam dns.ZoneListResult) (dns.ZoneListResult, error) {
|
||||
if len(results) > 0 {
|
||||
result := results[0]
|
||||
results = nil
|
||||
|
@ -44,6 +44,8 @@ type bluecatConfig struct {
|
||||
GatewayUsername string `json:"gatewayUsername,omitempty"`
|
||||
GatewayPassword string `json:"gatewayPassword,omitempty"`
|
||||
DNSConfiguration string `json:"dnsConfiguration"`
|
||||
DNSServerName string `json:"dnsServerName"`
|
||||
DNSDeployType string `json:"dnsDeployType"`
|
||||
View string `json:"dnsView"`
|
||||
RootZone string `json:"rootZone"`
|
||||
SkipTLSVerify bool `json:"skipTLSVerify"`
|
||||
@ -57,6 +59,8 @@ type BluecatProvider struct {
|
||||
dryRun bool
|
||||
RootZone string
|
||||
DNSConfiguration string
|
||||
DNSServerName string
|
||||
DNSDeployType string
|
||||
View string
|
||||
gatewayClient GatewayClient
|
||||
}
|
||||
@ -76,6 +80,7 @@ type GatewayClient interface {
|
||||
getTXTRecord(name string, record *BluecatTXTRecord) error
|
||||
createTXTRecord(zone string, req *bluecatCreateTXTRecordRequest) (res interface{}, err error)
|
||||
deleteTXTRecord(name string, zone string) error
|
||||
serverFullDeploy() error
|
||||
}
|
||||
|
||||
// GatewayClientConfig defines new client on bluecat gateway
|
||||
@ -86,6 +91,7 @@ type GatewayClientConfig struct {
|
||||
DNSConfiguration string
|
||||
View string
|
||||
RootZone string
|
||||
DNSServerName string
|
||||
SkipTLSVerify bool
|
||||
}
|
||||
|
||||
@ -144,26 +150,48 @@ type bluecatCreateTXTRecordRequest struct {
|
||||
Text string `json:"txt"`
|
||||
}
|
||||
|
||||
type bluecatServerFullDeployRequest struct {
|
||||
ServerName string `json:"server_name"`
|
||||
}
|
||||
|
||||
// NewBluecatProvider creates a new Bluecat provider.
|
||||
//
|
||||
// Returns a pointer to the provider or an error if a provider could not be created.
|
||||
func NewBluecatProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool) (*BluecatProvider, error) {
|
||||
contents, err := ioutil.ReadFile(configFile)
|
||||
func NewBluecatProvider(configFile, dnsConfiguration, dnsServerName, dnsDeployType, dnsView, gatewayHost, rootZone string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun, skipTLSVerify bool) (*BluecatProvider, error) {
|
||||
cfg := bluecatConfig{}
|
||||
contents, err := os.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read Bluecat config file %v", configFile)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
cfg = bluecatConfig{
|
||||
GatewayHost: gatewayHost,
|
||||
DNSConfiguration: dnsConfiguration,
|
||||
DNSServerName: dnsServerName,
|
||||
DNSDeployType: dnsDeployType,
|
||||
View: dnsView,
|
||||
RootZone: rootZone,
|
||||
SkipTLSVerify: skipTLSVerify,
|
||||
GatewayUsername: "",
|
||||
GatewayPassword: "",
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "failed to read Bluecat config file %v", configFile)
|
||||
}
|
||||
} else {
|
||||
err = json.Unmarshal(contents, &cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to parse Bluecat JSON config file %v", configFile)
|
||||
}
|
||||
}
|
||||
|
||||
cfg := bluecatConfig{}
|
||||
err = json.Unmarshal(contents, &cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to read Bluecat config file %v", configFile)
|
||||
if !isValidDNSDeployType(cfg.DNSDeployType) {
|
||||
return nil, errors.Errorf("%v is not a valid deployment type", cfg.DNSDeployType)
|
||||
}
|
||||
|
||||
token, cookie, err := getBluecatGatewayToken(cfg)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get API token from Bluecat Gateway")
|
||||
}
|
||||
gatewayClient := NewGatewayClient(cookie, token, cfg.GatewayHost, cfg.DNSConfiguration, cfg.View, cfg.RootZone, cfg.SkipTLSVerify)
|
||||
gatewayClient := NewGatewayClient(cookie, token, cfg.GatewayHost, cfg.DNSConfiguration, cfg.View, cfg.RootZone, cfg.DNSServerName, cfg.SkipTLSVerify)
|
||||
|
||||
provider := &BluecatProvider{
|
||||
domainFilter: domainFilter,
|
||||
@ -171,6 +199,8 @@ func NewBluecatProvider(configFile string, domainFilter endpoint.DomainFilter, z
|
||||
dryRun: dryRun,
|
||||
gatewayClient: gatewayClient,
|
||||
DNSConfiguration: cfg.DNSConfiguration,
|
||||
DNSServerName: cfg.DNSServerName,
|
||||
DNSDeployType: cfg.DNSDeployType,
|
||||
View: cfg.View,
|
||||
RootZone: cfg.RootZone,
|
||||
}
|
||||
@ -178,7 +208,9 @@ func NewBluecatProvider(configFile string, domainFilter endpoint.DomainFilter, z
|
||||
}
|
||||
|
||||
// NewGatewayClient creates and returns a new Bluecat gateway client
|
||||
func NewGatewayClient(cookie http.Cookie, token, gatewayHost, dnsConfiguration, view, rootZone string, skipTLSVerify bool) GatewayClientConfig {
|
||||
func NewGatewayClient(cookie http.Cookie, token, gatewayHost, dnsConfiguration, view, rootZone, dnsServerName string, skipTLSVerify bool) GatewayClientConfig {
|
||||
// TODO: do not handle defaulting here
|
||||
//
|
||||
// Right now the Bluecat gateway doesn't seem to have a way to get the root zone from the API. If the user
|
||||
// doesn't provide one via the config file we'll assume it's 'com'
|
||||
if rootZone == "" {
|
||||
@ -189,6 +221,7 @@ func NewGatewayClient(cookie http.Cookie, token, gatewayHost, dnsConfiguration,
|
||||
Token: token,
|
||||
Host: gatewayHost,
|
||||
DNSConfiguration: dnsConfiguration,
|
||||
DNSServerName: dnsServerName,
|
||||
View: view,
|
||||
RootZone: rootZone,
|
||||
SkipTLSVerify: skipTLSVerify,
|
||||
@ -278,8 +311,6 @@ func (p *BluecatProvider) Records(ctx context.Context) (endpoints []*endpoint.En
|
||||
}
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
|
||||
// TODO: add bluecat deploy API call here
|
||||
}
|
||||
|
||||
log.Debugf("fetched %d records from Bluecat", len(endpoints))
|
||||
@ -302,6 +333,20 @@ func (p *BluecatProvider) ApplyChanges(ctx context.Context, changes *plan.Change
|
||||
p.deleteRecords(deleted)
|
||||
p.createRecords(created)
|
||||
|
||||
if p.DNSServerName != "" {
|
||||
switch p.DNSDeployType {
|
||||
case "full-deploy":
|
||||
err := p.gatewayClient.serverFullDeploy()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "no-deploy":
|
||||
log.Debug("Not executing deploy because DNSDeployType is set to 'no-deploy'")
|
||||
}
|
||||
} else {
|
||||
log.Debug("Not executing deploy because server name was not provided")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -922,6 +967,41 @@ func (c GatewayClientConfig) deleteTXTRecord(name string, zone string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c GatewayClientConfig) serverFullDeploy() error {
|
||||
log.Infof("Executing full deploy on server %s", c.DNSServerName)
|
||||
httpClient := newHTTPClient(c.SkipTLSVerify)
|
||||
url := c.Host + "/api/v1/configurations/" + c.DNSConfiguration + "/server/full_deploy/"
|
||||
requestBody := bluecatServerFullDeployRequest{
|
||||
ServerName: c.DNSServerName,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(requestBody)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not marshal body for server full deploy")
|
||||
}
|
||||
|
||||
request, err := c.buildHTTPRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error building http request")
|
||||
}
|
||||
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error executing full deploy")
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusCreated {
|
||||
responseBody, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to read full deploy response body")
|
||||
}
|
||||
return errors.Errorf("got HTTP response code %v, detailed message: %v", response.StatusCode, string(responseBody))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//buildHTTPRequest builds a standard http Request and adds authentication headers required by Bluecat Gateway
|
||||
func (c GatewayClientConfig) buildHTTPRequest(method, url string, body io.Reader) (*http.Request, error) {
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
@ -947,6 +1027,17 @@ func splitProperties(props string) map[string]string {
|
||||
return propMap
|
||||
}
|
||||
|
||||
// isValidDNSDeployType validates the deployment type provided by a users configuration is supported by the Bluecat Provider.
|
||||
func isValidDNSDeployType(deployType string) bool {
|
||||
validDNSDeployTypes := []string{"no-deploy", "full-deploy"}
|
||||
for _, t := range validDNSDeployTypes {
|
||||
if t == deployType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//expandZone takes an absolute domain name such as 'example.com' and returns a zone hierarchy used by Bluecat Gateway,
|
||||
//such as '/zones/com/zones/example/zones/'
|
||||
func expandZone(zone string) string {
|
||||
|
@ -109,6 +109,9 @@ func (g mockGatewayClient) deleteTXTRecord(name string, zone string) error {
|
||||
*g.mockBluecatTXTs = nil
|
||||
return nil
|
||||
}
|
||||
func (g mockGatewayClient) serverFullDeploy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g mockGatewayClient) buildHTTPRequest(method, url string, body io.Reader) (*http.Request, error) {
|
||||
request, _ := http.NewRequest("GET", fmt.Sprintf("%s/users", "http://some.com/api/v1"), nil)
|
||||
@ -374,11 +377,12 @@ func TestBluecatNewGatewayClient(t *testing.T) {
|
||||
testToken := "exampleToken"
|
||||
testgateWayHost := "exampleHost"
|
||||
testDNSConfiguration := "exampleDNSConfiguration"
|
||||
testDNSServer := "exampleServer"
|
||||
testView := "testView"
|
||||
testZone := "example.com"
|
||||
testVerify := true
|
||||
|
||||
client := NewGatewayClient(testCookie, testToken, testgateWayHost, testDNSConfiguration, testView, testZone, testVerify)
|
||||
client := NewGatewayClient(testCookie, testToken, testgateWayHost, testDNSConfiguration, testView, testZone, testDNSServer, testVerify)
|
||||
|
||||
if client.Cookie.Value != testCookie.Value || client.Cookie.Name != testCookie.Name || client.Token != testToken || client.Host != testgateWayHost || client.DNSConfiguration != testDNSConfiguration || client.View != testView || client.RootZone != testZone || client.SkipTLSVerify != testVerify {
|
||||
t.Fatal("Client values dont match")
|
||||
@ -475,6 +479,21 @@ func TestBluecatRecordset(t *testing.T) {
|
||||
assert.Equal(t, cnameActual.res, cnameExpected.res)
|
||||
}
|
||||
|
||||
func TestValidDeployTypes(t *testing.T) {
|
||||
validTypes := []string{"no-deploy", "full-deploy"}
|
||||
invalidTypes := []string{"anything-else"}
|
||||
for _, i := range validTypes {
|
||||
if !isValidDNSDeployType(i) {
|
||||
t.Fatalf("%s should be a valid deploy type", i)
|
||||
}
|
||||
}
|
||||
for _, i := range invalidTypes {
|
||||
if isValidDNSDeployType(i) {
|
||||
t.Fatalf("%s should be a invalid deploy type", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateEndpoints(t *testing.T, actual, expected []*endpoint.Endpoint) {
|
||||
assert.True(t, testutils.SameEndpoints(actual, expected), "actual and expected endpoints don't match. %s:%s", actual, expected)
|
||||
}
|
||||
|
@ -141,6 +141,10 @@ func (p *HetznerProvider) submitChanges(ctx context.Context, changes []*HetznerC
|
||||
}
|
||||
}
|
||||
|
||||
logMessage := "Changing record"
|
||||
if p.DryRun {
|
||||
logMessage = "Would change record"
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"id": change.ResourceRecordSet.ID,
|
||||
"record": change.ResourceRecordSet.Name,
|
||||
@ -150,7 +154,10 @@ func (p *HetznerProvider) submitChanges(ctx context.Context, changes []*HetznerC
|
||||
"action": change.Action,
|
||||
"zone": change.ZoneName,
|
||||
"zone_id": change.ZoneID,
|
||||
}).Info("Changing record")
|
||||
}).Info(logMessage)
|
||||
if p.DryRun {
|
||||
continue
|
||||
}
|
||||
|
||||
switch change.Action {
|
||||
case hetznerCreate:
|
||||
|
@ -42,31 +42,33 @@ const (
|
||||
|
||||
// InfobloxConfig clarifies the method signature
|
||||
type InfobloxConfig struct {
|
||||
DomainFilter endpoint.DomainFilter
|
||||
ZoneIDFilter provider.ZoneIDFilter
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Version string
|
||||
SSLVerify bool
|
||||
DryRun bool
|
||||
View string
|
||||
MaxResults int
|
||||
FQDNRexEx string
|
||||
CreatePTR bool
|
||||
DomainFilter endpoint.DomainFilter
|
||||
ZoneIDFilter provider.ZoneIDFilter
|
||||
Host string
|
||||
Port int
|
||||
Username string
|
||||
Password string
|
||||
Version string
|
||||
SSLVerify bool
|
||||
DryRun bool
|
||||
View string
|
||||
MaxResults int
|
||||
FQDNRexEx string
|
||||
CreatePTR bool
|
||||
CacheDuration int
|
||||
}
|
||||
|
||||
// InfobloxProvider implements the DNS provider for Infoblox.
|
||||
type InfobloxProvider struct {
|
||||
provider.BaseProvider
|
||||
client ibclient.IBConnector
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
view string
|
||||
dryRun bool
|
||||
fqdnRegEx string
|
||||
createPTR bool
|
||||
client ibclient.IBConnector
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
view string
|
||||
dryRun bool
|
||||
fqdnRegEx string
|
||||
createPTR bool
|
||||
cacheDuration int
|
||||
}
|
||||
|
||||
type infobloxRecordSet struct {
|
||||
@ -146,13 +148,14 @@ func NewInfobloxProvider(infobloxConfig InfobloxConfig) (*InfobloxProvider, erro
|
||||
}
|
||||
|
||||
provider := &InfobloxProvider{
|
||||
client: client,
|
||||
domainFilter: infobloxConfig.DomainFilter,
|
||||
zoneIDFilter: infobloxConfig.ZoneIDFilter,
|
||||
dryRun: infobloxConfig.DryRun,
|
||||
view: infobloxConfig.View,
|
||||
fqdnRegEx: infobloxConfig.FQDNRexEx,
|
||||
createPTR: infobloxConfig.CreatePTR,
|
||||
client: client,
|
||||
domainFilter: infobloxConfig.DomainFilter,
|
||||
zoneIDFilter: infobloxConfig.ZoneIDFilter,
|
||||
dryRun: infobloxConfig.DryRun,
|
||||
view: infobloxConfig.View,
|
||||
fqdnRegEx: infobloxConfig.FQDNRexEx,
|
||||
createPTR: infobloxConfig.CreatePTR,
|
||||
cacheDuration: infobloxConfig.CacheDuration,
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
@ -216,6 +219,8 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E
|
||||
}
|
||||
for _, res := range resH {
|
||||
for _, ip := range res.Ipv4Addrs {
|
||||
logrus.Debugf("Record='%s' A(H):'%s'", res.Name, ip.Ipv4Addr)
|
||||
|
||||
// host record is an abstraction in infoblox that combines A and PTR records
|
||||
// for any host record we already should have a PTR record in infoblox, so mark it as created
|
||||
newEndpoint := endpoint.NewEndpoint(res.Name, endpoint.RecordTypeA, ip.Ipv4Addr)
|
||||
@ -238,6 +243,7 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E
|
||||
return nil, fmt.Errorf("could not fetch CNAME records from zone '%s': %s", zone.Fqdn, err)
|
||||
}
|
||||
for _, res := range resC {
|
||||
logrus.Debugf("Record='%s' CNAME:'%s'", res.Name, res.Canonical)
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeCNAME, res.Canonical))
|
||||
}
|
||||
|
||||
@ -281,6 +287,8 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E
|
||||
if _, err := strconv.Unquote(res.Text); err != nil {
|
||||
res.Text = strconv.Quote(res.Text)
|
||||
}
|
||||
|
||||
logrus.Debugf("Record='%s' TXT:'%s'", res.Name, res.Text)
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(res.Name, endpoint.RecordTypeTXT, res.Text))
|
||||
}
|
||||
}
|
||||
@ -320,6 +328,11 @@ func (p *InfobloxProvider) Records(ctx context.Context) (endpoints []*endpoint.E
|
||||
}
|
||||
|
||||
func (p *InfobloxProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
// Update user specified TTL (0 == disabled)
|
||||
for i := range endpoints {
|
||||
endpoints[i].RecordTTL = endpoint.TTL(p.cacheDuration)
|
||||
}
|
||||
|
||||
if !p.createPTR {
|
||||
return endpoints
|
||||
}
|
||||
@ -559,26 +572,27 @@ func (p *InfobloxProvider) recordSet(ep *endpoint.Endpoint, getObject bool, targ
|
||||
func (p *InfobloxProvider) createRecords(created infobloxChangeMap) {
|
||||
for zone, endpoints := range created {
|
||||
for _, ep := range endpoints {
|
||||
if p.dryRun {
|
||||
for targetIndex := range ep.Targets {
|
||||
if p.dryRun {
|
||||
logrus.Infof(
|
||||
|
||||
"Would create %s record named '%s' to '%s' for Infoblox DNS zone '%s'.",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Targets[targetIndex],
|
||||
zone,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Infof(
|
||||
"Would create %s record named '%s' to '%s' for Infoblox DNS zone '%s'.",
|
||||
"Creating %s record named '%s' to '%s' for Infoblox DNS zone '%s'.",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Targets,
|
||||
ep.Targets[targetIndex],
|
||||
zone,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.Infof(
|
||||
"Creating %s record named '%s' to '%s' for Infoblox DNS zone '%s'.",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Targets,
|
||||
zone,
|
||||
)
|
||||
|
||||
for targetIndex := range ep.Targets {
|
||||
recordSet, err := p.recordSet(ep, false, targetIndex)
|
||||
if err != nil {
|
||||
logrus.Errorf(
|
||||
@ -611,50 +625,66 @@ func (p *InfobloxProvider) deleteRecords(deleted infobloxChangeMap) {
|
||||
// Delete records first
|
||||
for zone, endpoints := range deleted {
|
||||
for _, ep := range endpoints {
|
||||
if p.dryRun {
|
||||
logrus.Infof("Would delete %s record named '%s' for Infoblox DNS zone '%s'.", ep.RecordType, ep.DNSName, zone)
|
||||
} else {
|
||||
logrus.Infof("Deleting %s record named '%s' for Infoblox DNS zone '%s'.", ep.RecordType, ep.DNSName, zone)
|
||||
for targetIndex := range ep.Targets {
|
||||
recordSet, err := p.recordSet(ep, true, targetIndex)
|
||||
if err != nil {
|
||||
logrus.Errorf(
|
||||
"Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Targets[targetIndex],
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
continue
|
||||
}
|
||||
switch ep.RecordType {
|
||||
case endpoint.RecordTypeA:
|
||||
for _, record := range *recordSet.res.(*[]ibclient.RecordA) {
|
||||
_, err = p.client.DeleteObject(record.Ref)
|
||||
}
|
||||
case endpoint.RecordTypePTR:
|
||||
for _, record := range *recordSet.res.(*[]ibclient.RecordPTR) {
|
||||
_, err = p.client.DeleteObject(record.Ref)
|
||||
}
|
||||
case endpoint.RecordTypeCNAME:
|
||||
for _, record := range *recordSet.res.(*[]ibclient.RecordCNAME) {
|
||||
_, err = p.client.DeleteObject(record.Ref)
|
||||
}
|
||||
case endpoint.RecordTypeTXT:
|
||||
for _, record := range *recordSet.res.(*[]ibclient.RecordTXT) {
|
||||
for targetIndex := range ep.Targets {
|
||||
recordSet, err := p.recordSet(ep, true, targetIndex)
|
||||
if err != nil {
|
||||
logrus.Errorf(
|
||||
"Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Targets[targetIndex],
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
continue
|
||||
}
|
||||
switch ep.RecordType {
|
||||
case endpoint.RecordTypeA:
|
||||
for _, record := range *recordSet.res.(*[]ibclient.RecordA) {
|
||||
if p.dryRun {
|
||||
logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "A", record.Name, record.Ipv4Addr, record.Zone)
|
||||
} else {
|
||||
logrus.Debugf("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "A", record.Name, record.Ipv4Addr, record.Zone)
|
||||
_, err = p.client.DeleteObject(record.Ref)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf(
|
||||
"Failed to delete %s record named '%s' for Infoblox DNS zone '%s': %v",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
case endpoint.RecordTypePTR:
|
||||
for _, record := range *recordSet.res.(*[]ibclient.RecordPTR) {
|
||||
if p.dryRun {
|
||||
logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "PTR", record.PtrdName, record.Ipv4Addr, record.Zone)
|
||||
} else {
|
||||
logrus.Debugf("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "PTR", record.PtrdName, record.Ipv4Addr, record.Zone)
|
||||
_, err = p.client.DeleteObject(record.Ref)
|
||||
}
|
||||
}
|
||||
case endpoint.RecordTypeCNAME:
|
||||
for _, record := range *recordSet.res.(*[]ibclient.RecordCNAME) {
|
||||
if p.dryRun {
|
||||
logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "CNAME", record.Name, record.Canonical, record.Zone)
|
||||
} else {
|
||||
logrus.Debugf("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "CNAME", record.Name, record.Canonical, record.Zone)
|
||||
_, err = p.client.DeleteObject(record.Ref)
|
||||
}
|
||||
}
|
||||
case endpoint.RecordTypeTXT:
|
||||
for _, record := range *recordSet.res.(*[]ibclient.RecordTXT) {
|
||||
if p.dryRun {
|
||||
logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "TXT", record.Name, record.Text, record.Zone)
|
||||
} else {
|
||||
logrus.Debugf("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "TXT", record.Name, record.Text, record.Zone)
|
||||
_, err = p.client.DeleteObject(record.Ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf(
|
||||
"Failed to delete %s record named '%s' to '%s' for Infoblox DNS zone '%s': %v",
|
||||
ep.RecordType,
|
||||
ep.DNSName,
|
||||
ep.Targets[targetIndex],
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
242
provider/safedns/safedns.go
Normal file
242
provider/safedns/safedns.go
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package safedns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
ukfClient "github.com/ukfast/sdk-go/pkg/client"
|
||||
ukfConnection "github.com/ukfast/sdk-go/pkg/connection"
|
||||
"github.com/ukfast/sdk-go/pkg/service/safedns"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// SafeDNS is an interface that is a subset of the SafeDNS service API that are actually used.
|
||||
// Signatures must match exactly.
|
||||
type SafeDNS interface {
|
||||
CreateZoneRecord(zoneName string, req safedns.CreateRecordRequest) (int, error)
|
||||
DeleteZoneRecord(zoneName string, recordID int) error
|
||||
GetZone(zoneName string) (safedns.Zone, error)
|
||||
GetZoneRecord(zoneName string, recordID int) (safedns.Record, error)
|
||||
GetZoneRecords(zoneName string, parameters ukfConnection.APIRequestParameters) ([]safedns.Record, error)
|
||||
GetZones(parameters ukfConnection.APIRequestParameters) ([]safedns.Zone, error)
|
||||
PatchZoneRecord(zoneName string, recordID int, patch safedns.PatchRecordRequest) (int, error)
|
||||
UpdateZoneRecord(zoneName string, record safedns.Record) (int, error)
|
||||
}
|
||||
|
||||
// SafeDNSProvider implements the DNS provider spec for UKFast SafeDNS.
|
||||
type SafeDNSProvider struct {
|
||||
provider.BaseProvider
|
||||
Client SafeDNS
|
||||
// Only consider hosted zones managing domains ending in this suffix
|
||||
domainFilter endpoint.DomainFilter
|
||||
DryRun bool
|
||||
APIRequestParams ukfConnection.APIRequestParameters
|
||||
}
|
||||
|
||||
// ZoneRecord is a datatype to simplify management of a record in a zone.
|
||||
type ZoneRecord struct {
|
||||
ID int
|
||||
Name string
|
||||
Type safedns.RecordType
|
||||
TTL safedns.RecordTTL
|
||||
Zone string
|
||||
Content string
|
||||
}
|
||||
|
||||
func NewSafeDNSProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*SafeDNSProvider, error) {
|
||||
token, ok := os.LookupEnv("SAFEDNS_TOKEN")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no SAFEDNS_TOKEN found in environment")
|
||||
}
|
||||
|
||||
ukfAPIConnection := ukfConnection.NewAPIKeyCredentialsAPIConnection(token)
|
||||
ukfClient := ukfClient.NewClient(ukfAPIConnection)
|
||||
safeDNS := ukfClient.SafeDNSService()
|
||||
|
||||
provider := &SafeDNSProvider{
|
||||
Client: safeDNS,
|
||||
domainFilter: domainFilter,
|
||||
DryRun: dryRun,
|
||||
APIRequestParams: *ukfConnection.NewAPIRequestParameters(),
|
||||
}
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// Zones returns the list of hosted zones in the SafeDNS account
|
||||
func (p *SafeDNSProvider) Zones(ctx context.Context) ([]safedns.Zone, error) {
|
||||
var zones []safedns.Zone
|
||||
|
||||
allZones, err := p.Client.GetZones(p.APIRequestParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check each found zone to see whether they match the domain filter provided. If they do, append it to the array of
|
||||
// zones defined above. If not, continue to the next item in the loop.
|
||||
for _, zone := range allZones {
|
||||
if p.domainFilter.Match(zone.Name) {
|
||||
zones = append(zones, zone)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
func (p *SafeDNSProvider) ZoneRecords(ctx context.Context) ([]ZoneRecord, error) {
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var zoneRecords []ZoneRecord
|
||||
for _, zone := range zones {
|
||||
// For each zone in the zonelist, get all records of an ExternalDNS supported type.
|
||||
records, err := p.Client.GetZoneRecords(zone.Name, p.APIRequestParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range records {
|
||||
zoneRecord := ZoneRecord{
|
||||
ID: r.ID,
|
||||
Name: r.Name,
|
||||
Type: r.Type,
|
||||
TTL: r.TTL,
|
||||
Zone: zone.Name,
|
||||
Content: r.Content,
|
||||
}
|
||||
zoneRecords = append(zoneRecords, zoneRecord)
|
||||
}
|
||||
}
|
||||
return zoneRecords, nil
|
||||
}
|
||||
|
||||
// Records returns a list of Endpoint resources created from all records in supported zones.
|
||||
func (p *SafeDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
zoneRecords, err := p.ZoneRecords(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, r := range zoneRecords {
|
||||
if provider.SupportedRecordType(string(r.Type)) {
|
||||
endpoints = append(endpoints, endpoint.NewEndpointWithTTL(r.Name, string(r.Type), endpoint.TTL(r.TTL), r.Content))
|
||||
}
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *SafeDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
// Identify the zone name for each record
|
||||
zoneNameIDMapper := provider.ZoneIDName{}
|
||||
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, zone := range zones {
|
||||
zoneNameIDMapper.Add(zone.Name, zone.Name)
|
||||
}
|
||||
|
||||
zoneRecords, err := p.ZoneRecords(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range changes.Create {
|
||||
_, ZoneName := zoneNameIDMapper.FindZone(endpoint.DNSName)
|
||||
for _, target := range endpoint.Targets {
|
||||
request := safedns.CreateRecordRequest{
|
||||
Name: endpoint.DNSName,
|
||||
Type: endpoint.RecordType,
|
||||
Content: target,
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"zoneID": ZoneName,
|
||||
"dnsName": endpoint.DNSName,
|
||||
"recordType": endpoint.RecordType,
|
||||
"Value": target,
|
||||
}).Info("Creating record")
|
||||
_, err := p.Client.CreateZoneRecord(ZoneName, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, endpoint := range changes.UpdateNew {
|
||||
// Currently iterates over each zoneRecord in ZoneRecords for each Endpoint
|
||||
// in UpdateNew; the same will go for Delete. As it's double-iteration,
|
||||
// that's O(n^2), which isn't great. No performance issues have been noted
|
||||
// thus far.
|
||||
var zoneRecord ZoneRecord
|
||||
for _, target := range endpoint.Targets {
|
||||
for _, zr := range zoneRecords {
|
||||
if zr.Name == endpoint.DNSName && zr.Content == target {
|
||||
zoneRecord = zr
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
newTTL := safedns.RecordTTL(int(endpoint.RecordTTL))
|
||||
newRecord := safedns.PatchRecordRequest{
|
||||
Name: endpoint.DNSName,
|
||||
Content: target,
|
||||
TTL: &newTTL,
|
||||
Type: endpoint.RecordType,
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"zoneID": zoneRecord.Zone,
|
||||
"dnsName": newRecord.Name,
|
||||
"recordType": newRecord.Type,
|
||||
"Value": newRecord.Content,
|
||||
"Priority": newRecord.Priority,
|
||||
}).Info("Patching record")
|
||||
_, err = p.Client.PatchZoneRecord(zoneRecord.Zone, zoneRecord.ID, newRecord)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, endpoint := range changes.Delete {
|
||||
// As above, currently iterates in O(n^2). May be a good start for optimisations.
|
||||
var zoneRecord ZoneRecord
|
||||
for _, zr := range zoneRecords {
|
||||
if zr.Name == endpoint.DNSName && string(zr.Type) == endpoint.RecordType {
|
||||
zoneRecord = zr
|
||||
break
|
||||
}
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"zoneID": zoneRecord.Zone,
|
||||
"dnsName": zoneRecord.Name,
|
||||
"recordType": zoneRecord.Type,
|
||||
}).Info("Deleting record")
|
||||
err := p.Client.DeleteZoneRecord(zoneRecord.Zone, zoneRecord.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
366
provider/safedns/safedns_test.go
Normal file
366
provider/safedns/safedns_test.go
Normal file
@ -0,0 +1,366 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package safedns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
ukfConnection "github.com/ukfast/sdk-go/pkg/connection"
|
||||
"github.com/ukfast/sdk-go/pkg/service/safedns"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
)
|
||||
|
||||
// Create an implementation of the SafeDNS interface for Mocking
|
||||
type MockSafeDNSService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) CreateZoneRecord(zoneName string, req safedns.CreateRecordRequest) (int, error) {
|
||||
args := m.Called(zoneName, req)
|
||||
return args.Int(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) DeleteZoneRecord(zoneName string, recordID int) error {
|
||||
args := m.Called(zoneName, recordID)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) GetZone(zoneName string) (safedns.Zone, error) {
|
||||
args := m.Called(zoneName)
|
||||
return args.Get(0).(safedns.Zone), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) GetZoneRecord(zoneName string, recordID int) (safedns.Record, error) {
|
||||
args := m.Called(zoneName, recordID)
|
||||
return args.Get(0).(safedns.Record), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) GetZoneRecords(zoneName string, parameters ukfConnection.APIRequestParameters) ([]safedns.Record, error) {
|
||||
args := m.Called(zoneName, parameters)
|
||||
return args.Get(0).([]safedns.Record), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) GetZones(parameters ukfConnection.APIRequestParameters) ([]safedns.Zone, error) {
|
||||
args := m.Called(parameters)
|
||||
return args.Get(0).([]safedns.Zone), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) PatchZoneRecord(zoneName string, recordID int, patch safedns.PatchRecordRequest) (int, error) {
|
||||
args := m.Called(zoneName, recordID, patch)
|
||||
return args.Int(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockSafeDNSService) UpdateZoneRecord(zoneName string, record safedns.Record) (int, error) {
|
||||
args := m.Called(zoneName, record)
|
||||
return args.Int(0), args.Error(1)
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
func createZones() []safedns.Zone {
|
||||
return []safedns.Zone{
|
||||
{Name: "foo.com", Description: "Foo dot com"},
|
||||
{Name: "bar.io", Description: ""},
|
||||
{Name: "baz.org", Description: "Org"},
|
||||
}
|
||||
}
|
||||
|
||||
func createFooRecords() []safedns.Record {
|
||||
return []safedns.Record{
|
||||
{
|
||||
ID: 11,
|
||||
Type: safedns.RecordTypeA,
|
||||
Name: "foo.com",
|
||||
Content: "targetFoo",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 12,
|
||||
Type: safedns.RecordTypeTXT,
|
||||
Name: "foo.com",
|
||||
Content: "text",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 13,
|
||||
Type: safedns.RecordTypeCAA,
|
||||
Name: "foo.com",
|
||||
Content: "",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createBarRecords() []safedns.Record {
|
||||
return []safedns.Record{}
|
||||
}
|
||||
|
||||
func createBazRecords() []safedns.Record {
|
||||
return []safedns.Record{
|
||||
{
|
||||
ID: 31,
|
||||
Type: safedns.RecordTypeA,
|
||||
Name: "baz.org",
|
||||
Content: "targetBaz",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 32,
|
||||
Type: safedns.RecordTypeTXT,
|
||||
Name: "baz.org",
|
||||
Content: "text",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 33,
|
||||
Type: safedns.RecordTypeA,
|
||||
Name: "api.baz.org",
|
||||
Content: "targetBazAPI",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
{
|
||||
ID: 34,
|
||||
Type: safedns.RecordTypeTXT,
|
||||
Name: "api.baz.org",
|
||||
Content: "text",
|
||||
TTL: safedns.RecordTTL(3600),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Actual tests
|
||||
func TestNewSafeDNSProvider(t *testing.T) {
|
||||
_ = os.Setenv("SAFEDNS_TOKEN", "DUMMYVALUE")
|
||||
_, err := NewSafeDNSProvider(endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true)
|
||||
require.NoError(t, err)
|
||||
|
||||
_ = os.Unsetenv("SAFEDNS_TOKEN")
|
||||
_, err = NewSafeDNSProvider(endpoint.NewDomainFilter([]string{"ext-dns-test.zalando.to."}), true)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRecords(t *testing.T) {
|
||||
mockSafeDNSService := MockSafeDNSService{}
|
||||
|
||||
provider := &SafeDNSProvider{
|
||||
Client: &mockSafeDNSService,
|
||||
domainFilter: endpoint.NewDomainFilter([]string{}),
|
||||
DryRun: false,
|
||||
}
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZones",
|
||||
mock.Anything,
|
||||
).Return(createZones(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"foo.com",
|
||||
mock.Anything,
|
||||
).Return(createFooRecords(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"bar.io",
|
||||
mock.Anything,
|
||||
).Return(createBarRecords(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"baz.org",
|
||||
mock.Anything,
|
||||
).Return(createBazRecords(), nil).Once()
|
||||
|
||||
actual, err := provider.Records(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: []string{"targetFoo"},
|
||||
RecordType: "A",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
Targets: []string{"text"},
|
||||
RecordType: "TXT",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "baz.org",
|
||||
Targets: []string{"targetBaz"},
|
||||
RecordType: "A",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "baz.org",
|
||||
Targets: []string{"text"},
|
||||
RecordType: "TXT",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "api.baz.org",
|
||||
Targets: []string{"targetBazAPI"},
|
||||
RecordType: "A",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
{
|
||||
DNSName: "api.baz.org",
|
||||
Targets: []string{"text"},
|
||||
RecordType: "TXT",
|
||||
RecordTTL: 3600,
|
||||
Labels: endpoint.NewLabels(),
|
||||
},
|
||||
}
|
||||
|
||||
mockSafeDNSService.AssertExpectations(t)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestSafeDNSApplyChanges(t *testing.T) {
|
||||
mockSafeDNSService := MockSafeDNSService{}
|
||||
|
||||
provider := &SafeDNSProvider{
|
||||
Client: &mockSafeDNSService,
|
||||
domainFilter: endpoint.NewDomainFilter([]string{}),
|
||||
DryRun: false,
|
||||
}
|
||||
|
||||
// Dummy data
|
||||
mockSafeDNSService.On(
|
||||
"GetZones",
|
||||
mock.Anything,
|
||||
).Return(createZones(), nil).Once()
|
||||
mockSafeDNSService.On(
|
||||
"GetZones",
|
||||
mock.Anything,
|
||||
).Return(createZones(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"foo.com",
|
||||
mock.Anything,
|
||||
).Return(createFooRecords(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"bar.io",
|
||||
mock.Anything,
|
||||
).Return(createBarRecords(), nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"GetZoneRecords",
|
||||
"baz.org",
|
||||
mock.Anything,
|
||||
).Return(createBazRecords(), nil).Once()
|
||||
|
||||
// Apply actions
|
||||
mockSafeDNSService.On(
|
||||
"DeleteZoneRecord",
|
||||
"baz.org",
|
||||
33,
|
||||
).Return(nil).Once()
|
||||
mockSafeDNSService.On(
|
||||
"DeleteZoneRecord",
|
||||
"baz.org",
|
||||
34,
|
||||
).Return(nil).Once()
|
||||
|
||||
TTL300 := safedns.RecordTTL(300)
|
||||
mockSafeDNSService.On(
|
||||
"PatchZoneRecord",
|
||||
"foo.com",
|
||||
11,
|
||||
safedns.PatchRecordRequest{
|
||||
Type: "A",
|
||||
Name: "foo.com",
|
||||
Content: "targetFoo",
|
||||
TTL: &TTL300,
|
||||
},
|
||||
).Return(123, nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"CreateZoneRecord",
|
||||
"bar.io",
|
||||
safedns.CreateRecordRequest{
|
||||
Type: "A",
|
||||
Name: "create.bar.io",
|
||||
Content: "targetBar",
|
||||
},
|
||||
).Return(246, nil).Once()
|
||||
|
||||
mockSafeDNSService.On(
|
||||
"CreateZoneRecord",
|
||||
"bar.io",
|
||||
safedns.CreateRecordRequest{
|
||||
Type: "A",
|
||||
Name: "bar.io",
|
||||
Content: "targetBar",
|
||||
},
|
||||
).Return(369, nil).Once()
|
||||
|
||||
err := provider.ApplyChanges(context.Background(), &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "create.bar.io",
|
||||
RecordType: "A",
|
||||
Targets: []string{"targetBar"},
|
||||
RecordTTL: 3600,
|
||||
},
|
||||
{
|
||||
DNSName: "bar.io",
|
||||
RecordType: "A",
|
||||
Targets: []string{"targetBar"},
|
||||
RecordTTL: 3600,
|
||||
},
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "api.baz.org",
|
||||
RecordType: "A",
|
||||
},
|
||||
{
|
||||
DNSName: "api.baz.org",
|
||||
RecordType: "TXT",
|
||||
},
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foo.com",
|
||||
RecordType: "A",
|
||||
RecordTTL: 300,
|
||||
Targets: []string{"targetFoo"},
|
||||
},
|
||||
},
|
||||
UpdateOld: []*endpoint.Endpoint{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockSafeDNSService.AssertExpectations(t)
|
||||
}
|
@ -30,7 +30,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/informers"
|
||||
@ -64,6 +63,7 @@ type ambassadorHostSource struct {
|
||||
|
||||
// NewAmbassadorHostSource creates a new ambassadorHostSource with the given config.
|
||||
func NewAmbassadorHostSource(
|
||||
ctx context.Context,
|
||||
dynamicKubeClient dynamic.Interface,
|
||||
kubeClient kubernetes.Interface,
|
||||
namespace string) (Source, error) {
|
||||
@ -82,8 +82,7 @@ func NewAmbassadorHostSource(
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
|
||||
if err := waitForDynamicCacheSync(context.Background(), informerFactory); err != nil {
|
||||
return nil, err
|
||||
|
@ -28,7 +28,6 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/informers"
|
||||
@ -53,6 +52,7 @@ type httpProxySource struct {
|
||||
|
||||
// NewContourHTTPProxySource creates a new contourHTTPProxySource with the given config.
|
||||
func NewContourHTTPProxySource(
|
||||
ctx context.Context,
|
||||
dynamicKubeClient dynamic.Interface,
|
||||
namespace string,
|
||||
annotationFilter string,
|
||||
@ -78,8 +78,7 @@ func NewContourHTTPProxySource(
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForDynamicCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -88,6 +88,7 @@ func (suite *HTTPProxySuite) SetupTest() {
|
||||
var err error
|
||||
|
||||
suite.source, err = NewContourHTTPProxySource(
|
||||
context.TODO(),
|
||||
fakeDynamicClient,
|
||||
"default",
|
||||
"",
|
||||
@ -184,6 +185,7 @@ func TestNewContourHTTPProxySource(t *testing.T) {
|
||||
fakeDynamicClient, _ := newDynamicKubernetesClient()
|
||||
|
||||
_, err := NewContourHTTPProxySource(
|
||||
context.TODO(),
|
||||
fakeDynamicClient,
|
||||
"",
|
||||
ti.annotationFilter,
|
||||
@ -1033,6 +1035,7 @@ func testHTTPProxyEndpoints(t *testing.T) {
|
||||
}
|
||||
|
||||
httpProxySource, err := NewContourHTTPProxySource(
|
||||
context.TODO(),
|
||||
fakeDynamicClient,
|
||||
ti.targetNamespace,
|
||||
ti.annotationFilter,
|
||||
@ -1059,6 +1062,7 @@ func newTestHTTPProxySource() (*httpProxySource, error) {
|
||||
fakeDynamicClient, _ := newDynamicKubernetesClient()
|
||||
|
||||
src, err := NewContourHTTPProxySource(
|
||||
context.TODO(),
|
||||
fakeDynamicClient,
|
||||
"default",
|
||||
"",
|
||||
|
@ -28,7 +28,6 @@ import (
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
netinformers "k8s.io/client-go/informers/networking/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -67,7 +66,7 @@ type ingressSource struct {
|
||||
}
|
||||
|
||||
// NewIngressSource creates a new ingressSource with the given config.
|
||||
func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, labelSelector labels.Selector, ingressClassNames []string) (Source, error) {
|
||||
func NewIngressSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, labelSelector labels.Selector, ingressClassNames []string) (Source, error) {
|
||||
tmpl, err := parseTemplate(fqdnTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -101,8 +100,7 @@ func NewIngressSource(kubeClient kubernetes.Interface, namespace, annotationFilt
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -56,6 +56,7 @@ func (suite *IngressSuite) SetupTest() {
|
||||
suite.NoError(err, "should succeed")
|
||||
|
||||
suite.sc, err = NewIngressSource(
|
||||
context.TODO(),
|
||||
fakeClient,
|
||||
"",
|
||||
"",
|
||||
@ -151,6 +152,7 @@ func TestNewIngressSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewIngressSource(
|
||||
context.TODO(),
|
||||
fake.NewSimpleClientset(),
|
||||
"",
|
||||
ti.annotationFilter,
|
||||
@ -1291,6 +1293,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
}
|
||||
|
||||
source, _ := NewIngressSource(
|
||||
context.TODO(),
|
||||
fakeClient,
|
||||
ti.targetNamespace,
|
||||
ti.annotationFilter,
|
||||
|
@ -30,7 +30,6 @@ import (
|
||||
networkingv1alpha3informer "istio.io/client-go/pkg/informers/externalversions/networking/v1alpha3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -56,6 +55,7 @@ type gatewaySource struct {
|
||||
|
||||
// NewIstioGatewaySource creates a new gatewaySource with the given config.
|
||||
func NewIstioGatewaySource(
|
||||
ctx context.Context,
|
||||
kubeClient kubernetes.Interface,
|
||||
istioClient istioclient.Interface,
|
||||
namespace string,
|
||||
@ -93,9 +93,8 @@ func NewIstioGatewaySource(
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
istioInformerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
istioInformerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -69,6 +69,7 @@ func (suite *GatewaySuite) SetupTest() {
|
||||
}
|
||||
|
||||
suite.source, err = NewIstioGatewaySource(
|
||||
context.TODO(),
|
||||
fakeKubernetesClient,
|
||||
fakeIstioClient,
|
||||
"",
|
||||
@ -142,6 +143,7 @@ func TestNewIstioGatewaySource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewIstioGatewaySource(
|
||||
context.TODO(),
|
||||
fake.NewSimpleClientset(),
|
||||
istiofake.NewSimpleClientset(),
|
||||
"",
|
||||
@ -1165,6 +1167,7 @@ func testGatewayEndpoints(t *testing.T) {
|
||||
}
|
||||
|
||||
gatewaySource, err := NewIstioGatewaySource(
|
||||
context.TODO(),
|
||||
fakeKubernetesClient,
|
||||
fakeIstioClient,
|
||||
ti.targetNamespace,
|
||||
@ -1201,6 +1204,7 @@ func newTestGatewaySource(loadBalancerList []fakeIngressGatewayService) (*gatewa
|
||||
}
|
||||
|
||||
src, err := NewIstioGatewaySource(
|
||||
context.TODO(),
|
||||
fakeKubernetesClient,
|
||||
fakeIstioClient,
|
||||
"",
|
||||
|
@ -31,7 +31,6 @@ import (
|
||||
networkingv1alpha3informer "istio.io/client-go/pkg/informers/externalversions/networking/v1alpha3"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -60,6 +59,7 @@ type virtualServiceSource struct {
|
||||
|
||||
// NewIstioVirtualServiceSource creates a new virtualServiceSource with the given config.
|
||||
func NewIstioVirtualServiceSource(
|
||||
ctx context.Context,
|
||||
kubeClient kubernetes.Interface,
|
||||
istioClient istioclient.Interface,
|
||||
namespace string,
|
||||
@ -97,9 +97,8 @@ func NewIstioVirtualServiceSource(
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
istioInformerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
istioInformerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -89,6 +89,7 @@ func (suite *VirtualServiceSuite) SetupTest() {
|
||||
suite.NoError(err, "should succeed")
|
||||
|
||||
suite.source, err = NewIstioVirtualServiceSource(
|
||||
context.TODO(),
|
||||
fakeKubernetesClient,
|
||||
fakeIstioClient,
|
||||
"",
|
||||
@ -165,6 +166,7 @@ func TestNewIstioVirtualServiceSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewIstioVirtualServiceSource(
|
||||
context.TODO(),
|
||||
fake.NewSimpleClientset(),
|
||||
istiofake.NewSimpleClientset(),
|
||||
"",
|
||||
@ -1482,6 +1484,7 @@ func testVirtualServiceEndpoints(t *testing.T) {
|
||||
}
|
||||
|
||||
virtualServiceSource, err := NewIstioVirtualServiceSource(
|
||||
context.TODO(),
|
||||
fakeKubernetesClient,
|
||||
fakeIstioClient,
|
||||
ti.targetNamespace,
|
||||
@ -1557,6 +1560,7 @@ func newTestVirtualServiceSource(loadBalancerList []fakeIngressGatewayService, g
|
||||
}
|
||||
|
||||
src, err := NewIstioVirtualServiceSource(
|
||||
context.TODO(),
|
||||
fakeKubernetesClient,
|
||||
fakeIstioClient,
|
||||
"",
|
||||
|
@ -29,7 +29,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/informers"
|
||||
@ -57,7 +56,7 @@ type kongTCPIngressSource struct {
|
||||
}
|
||||
|
||||
// NewKongTCPIngressSource creates a new kongTCPIngressSource with the given config.
|
||||
func NewKongTCPIngressSource(dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface, namespace string, annotationFilter string) (Source, error) {
|
||||
func NewKongTCPIngressSource(ctx context.Context, dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface, namespace string, annotationFilter string) (Source, error) {
|
||||
var err error
|
||||
|
||||
// Use shared informer to listen for add/update/delete of Host in the specified namespace.
|
||||
@ -73,8 +72,7 @@ func NewKongTCPIngressSource(dynamicKubeClient dynamic.Interface, kubeClient kub
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForDynamicCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -241,7 +241,7 @@ func TestKongTCPIngressEndpoints(t *testing.T) {
|
||||
_, err = fakeDynamicClient.Resource(kongGroupdVersionResource).Namespace(defaultKongNamespace).Create(context.Background(), &tcpi, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
source, err := NewKongTCPIngressSource(fakeDynamicClient, fakeKubernetesClient, defaultKongNamespace, "kubernetes.io/ingress.class=kong")
|
||||
source, err := NewKongTCPIngressSource(context.TODO(), fakeDynamicClient, fakeKubernetesClient, defaultKongNamespace, "kubernetes.io/ingress.class=kong")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, source)
|
||||
|
||||
|
@ -25,7 +25,6 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -42,7 +41,7 @@ type nodeSource struct {
|
||||
}
|
||||
|
||||
// NewNodeSource creates a new nodeSource with the given config.
|
||||
func NewNodeSource(kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string) (Source, error) {
|
||||
func NewNodeSource(ctx context.Context, kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string) (Source, error) {
|
||||
tmpl, err := parseTemplate(fqdnTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -62,8 +61,7 @@ func NewNodeSource(kubeClient kubernetes.Interface, annotationFilter, fqdnTempla
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -71,6 +71,7 @@ func testNodeSourceNewNodeSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewNodeSource(
|
||||
context.TODO(),
|
||||
fake.NewSimpleClientset(),
|
||||
ti.annotationFilter,
|
||||
ti.fqdnTemplate,
|
||||
@ -353,6 +354,7 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, err := NewNodeSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
tc.annotationFilter,
|
||||
tc.fqdnTemplate,
|
||||
|
@ -29,7 +29,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
@ -54,6 +53,7 @@ type ocpRouteSource struct {
|
||||
|
||||
// NewOcpRouteSource creates a new ocpRouteSource with the given config.
|
||||
func NewOcpRouteSource(
|
||||
ctx context.Context,
|
||||
ocpClient versioned.Interface,
|
||||
namespace string,
|
||||
annotationFilter string,
|
||||
@ -81,8 +81,7 @@ func NewOcpRouteSource(
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -43,6 +43,7 @@ func (suite *OCPRouteSuite) SetupTest() {
|
||||
var err error
|
||||
|
||||
suite.sc, err = NewOcpRouteSource(
|
||||
context.TODO(),
|
||||
fakeClient,
|
||||
"",
|
||||
"",
|
||||
@ -141,6 +142,7 @@ func testOcpRouteSourceNewOcpRouteSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewOcpRouteSource(
|
||||
context.TODO(),
|
||||
fake.NewSimpleClientset(),
|
||||
"",
|
||||
ti.annotationFilter,
|
||||
@ -439,6 +441,7 @@ func testOcpRouteSourceEndpoints(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
source, err := NewOcpRouteSource(
|
||||
context.TODO(),
|
||||
fakeClient,
|
||||
"",
|
||||
"",
|
||||
|
@ -24,7 +24,6 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -40,7 +39,7 @@ type podSource struct {
|
||||
}
|
||||
|
||||
// NewPodSource creates a new podSource with the given config.
|
||||
func NewPodSource(kubeClient kubernetes.Interface, namespace string, compatibility string) (Source, error) {
|
||||
func NewPodSource(ctx context.Context, kubeClient kubernetes.Interface, namespace string, compatibility string) (Source, error) {
|
||||
informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace))
|
||||
podInformer := informerFactory.Core().V1().Pods()
|
||||
nodeInformer := informerFactory.Core().V1().Nodes()
|
||||
@ -58,7 +57,7 @@ func NewPodSource(kubeClient kubernetes.Interface, namespace string, compatibili
|
||||
},
|
||||
)
|
||||
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -412,7 +412,7 @@ func TestPodSource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
client, err := NewPodSource(kubernetes, tc.targetNamespace, tc.compatibility)
|
||||
client, err := NewPodSource(context.TODO(), kubernetes, tc.targetNamespace, tc.compatibility)
|
||||
require.NoError(t, err)
|
||||
|
||||
endpoints, err := client.Endpoints(ctx)
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -67,7 +66,7 @@ type serviceSource struct {
|
||||
}
|
||||
|
||||
// NewServiceSource creates a new serviceSource with the given config.
|
||||
func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool, labelSelector labels.Selector) (Source, error) {
|
||||
func NewServiceSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, compatibility string, publishInternal bool, publishHostIP bool, alwaysPublishNotReadyAddresses bool, serviceTypeFilter []string, ignoreHostnameAnnotation bool, labelSelector labels.Selector) (Source, error) {
|
||||
tmpl, err := parseTemplate(fqdnTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -107,8 +106,7 @@ func NewServiceSource(kubeClient kubernetes.Interface, namespace, annotationFilt
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
informerFactory.Start(ctx.Done())
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||
|
@ -65,6 +65,7 @@ func (suite *ServiceSuite) SetupTest() {
|
||||
suite.NoError(err, "should successfully create service")
|
||||
|
||||
suite.sc, err = NewServiceSource(
|
||||
context.TODO(),
|
||||
fakeClient,
|
||||
"",
|
||||
"",
|
||||
@ -144,6 +145,7 @@ func testServiceSourceNewServiceSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := NewServiceSource(
|
||||
context.TODO(),
|
||||
fake.NewSimpleClientset(),
|
||||
"",
|
||||
ti.annotationFilter,
|
||||
@ -1039,6 +1041,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, err := NewServiceSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
tc.targetNamespace,
|
||||
tc.annotationFilter,
|
||||
@ -1227,6 +1230,7 @@ func testMultipleServicesEndpoints(t *testing.T) {
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, err := NewServiceSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
tc.targetNamespace,
|
||||
tc.annotationFilter,
|
||||
@ -1391,6 +1395,7 @@ func TestClusterIpServices(t *testing.T) {
|
||||
}
|
||||
// Create our object under test and get the endpoints.
|
||||
client, _ := NewServiceSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
tc.targetNamespace,
|
||||
tc.annotationFilter,
|
||||
@ -1960,6 +1965,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, _ := NewServiceSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
tc.targetNamespace,
|
||||
tc.annotationFilter,
|
||||
@ -2295,6 +2301,7 @@ func TestHeadlessServices(t *testing.T) {
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, _ := NewServiceSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
tc.targetNamespace,
|
||||
"",
|
||||
@ -2651,6 +2658,7 @@ func TestHeadlessServicesHostIP(t *testing.T) {
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, _ := NewServiceSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
tc.targetNamespace,
|
||||
"",
|
||||
@ -2762,6 +2770,7 @@ func TestExternalServices(t *testing.T) {
|
||||
|
||||
// Create our object under test and get the endpoints.
|
||||
client, _ := NewServiceSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
tc.targetNamespace,
|
||||
"",
|
||||
@ -2815,6 +2824,7 @@ func BenchmarkServiceEndpoints(b *testing.B) {
|
||||
require.NoError(b, err)
|
||||
|
||||
client, err := NewServiceSource(
|
||||
context.TODO(),
|
||||
kubernetes,
|
||||
v1.NamespaceAll,
|
||||
"",
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
@ -159,10 +160,10 @@ func (p *SingletonClientGenerator) OpenShiftClient() (openshift.Interface, error
|
||||
}
|
||||
|
||||
// ByNames returns multiple Sources given multiple names.
|
||||
func ByNames(p ClientGenerator, names []string, cfg *Config) ([]Source, error) {
|
||||
func ByNames(ctx context.Context, p ClientGenerator, names []string, cfg *Config) ([]Source, error) {
|
||||
sources := []Source{}
|
||||
for _, name := range names {
|
||||
source, err := BuildWithConfig(name, p, cfg)
|
||||
source, err := BuildWithConfig(ctx, name, p, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -173,32 +174,32 @@ func ByNames(p ClientGenerator, names []string, cfg *Config) ([]Source, error) {
|
||||
}
|
||||
|
||||
// BuildWithConfig allows to generate a Source implementation from the shared config
|
||||
func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
switch source {
|
||||
case "node":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewNodeSource(client, cfg.AnnotationFilter, cfg.FQDNTemplate)
|
||||
return NewNodeSource(ctx, client, cfg.AnnotationFilter, cfg.FQDNTemplate)
|
||||
case "service":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewServiceSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter)
|
||||
return NewServiceSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.Compatibility, cfg.PublishInternal, cfg.PublishHostIP, cfg.AlwaysPublishNotReadyAddresses, cfg.ServiceTypeFilter, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter)
|
||||
case "ingress":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIngressSource(client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter, cfg.IngressClassNames)
|
||||
return NewIngressSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter, cfg.IngressClassNames)
|
||||
case "pod":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewPodSource(client, cfg.Namespace, cfg.Compatibility)
|
||||
return NewPodSource(ctx, client, cfg.Namespace, cfg.Compatibility)
|
||||
case "istio-gateway":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
@ -208,7 +209,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIstioGatewaySource(kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
return NewIstioGatewaySource(ctx, kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
case "istio-virtualservice":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
@ -218,7 +219,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIstioVirtualServiceSource(kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
return NewIstioVirtualServiceSource(ctx, kubernetesClient, istioClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
case "cloudfoundry":
|
||||
cfClient, err := p.CloudFoundryClient(cfg.CFAPIEndpoint, cfg.CFUsername, cfg.CFPassword)
|
||||
if err != nil {
|
||||
@ -234,13 +235,13 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAmbassadorHostSource(dynamicClient, kubernetesClient, cfg.Namespace)
|
||||
return NewAmbassadorHostSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace)
|
||||
case "contour-httpproxy":
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewContourHTTPProxySource(dynamicClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
return NewContourHTTPProxySource(ctx, dynamicClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
|
||||
case "gloo-proxy":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
@ -256,7 +257,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewOcpRouteSource(ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.OCPRouterName)
|
||||
return NewOcpRouteSource(ctx, ocpClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.LabelFilter, cfg.OCPRouterName)
|
||||
case "fake":
|
||||
return NewFakeSource(cfg.FQDNTemplate)
|
||||
case "connector":
|
||||
@ -291,7 +292,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewKongTCPIngressSource(dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter)
|
||||
return NewKongTCPIngressSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter)
|
||||
}
|
||||
return nil, ErrSourceNotFound
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
@ -115,7 +116,7 @@ func (suite *ByNamesTestSuite) TestAllInitialized() {
|
||||
}: "TCPIngressesList",
|
||||
}), nil)
|
||||
|
||||
sources, err := ByNames(mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-httpproxy", "kong-tcpingress", "fake"}, minimalConfig)
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-httpproxy", "kong-tcpingress", "fake"}, minimalConfig)
|
||||
suite.NoError(err, "should not generate errors")
|
||||
suite.Len(sources, 6, "should generate all six sources")
|
||||
}
|
||||
@ -124,7 +125,7 @@ func (suite *ByNamesTestSuite) TestOnlyFake() {
|
||||
mockClientGenerator := new(MockClientGenerator)
|
||||
mockClientGenerator.On("KubeClient").Return(fakeKube.NewSimpleClientset(), nil)
|
||||
|
||||
sources, err := ByNames(mockClientGenerator, []string{"fake"}, minimalConfig)
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"fake"}, minimalConfig)
|
||||
suite.NoError(err, "should not generate errors")
|
||||
suite.Len(sources, 1, "should generate fake source")
|
||||
suite.Nil(mockClientGenerator.kubeClient, "client should not be created")
|
||||
@ -134,7 +135,7 @@ func (suite *ByNamesTestSuite) TestSourceNotFound() {
|
||||
mockClientGenerator := new(MockClientGenerator)
|
||||
mockClientGenerator.On("KubeClient").Return(fakeKube.NewSimpleClientset(), nil)
|
||||
|
||||
sources, err := ByNames(mockClientGenerator, []string{"foo"}, minimalConfig)
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"foo"}, minimalConfig)
|
||||
suite.Equal(err, ErrSourceNotFound, "should return source not found")
|
||||
suite.Len(sources, 0, "should not returns any source")
|
||||
}
|
||||
@ -143,16 +144,16 @@ func (suite *ByNamesTestSuite) TestKubeClientFails() {
|
||||
mockClientGenerator := new(MockClientGenerator)
|
||||
mockClientGenerator.On("KubeClient").Return(nil, errors.New("foo"))
|
||||
|
||||
_, err := ByNames(mockClientGenerator, []string{"service"}, minimalConfig)
|
||||
_, err := ByNames(context.TODO(), mockClientGenerator, []string{"service"}, minimalConfig)
|
||||
suite.Error(err, "should return an error if kubernetes client cannot be created")
|
||||
|
||||
_, err = ByNames(mockClientGenerator, []string{"ingress"}, minimalConfig)
|
||||
_, err = ByNames(context.TODO(), mockClientGenerator, []string{"ingress"}, minimalConfig)
|
||||
suite.Error(err, "should return an error if kubernetes client cannot be created")
|
||||
|
||||
_, err = ByNames(mockClientGenerator, []string{"istio-gateway"}, minimalConfig)
|
||||
_, err = ByNames(context.TODO(), mockClientGenerator, []string{"istio-gateway"}, minimalConfig)
|
||||
suite.Error(err, "should return an error if kubernetes client cannot be created")
|
||||
|
||||
_, err = ByNames(mockClientGenerator, []string{"kong-tcpingress"}, minimalConfig)
|
||||
_, err = ByNames(context.TODO(), mockClientGenerator, []string{"kong-tcpingress"}, minimalConfig)
|
||||
suite.Error(err, "should return an error if kubernetes client cannot be created")
|
||||
}
|
||||
|
||||
@ -162,10 +163,10 @@ func (suite *ByNamesTestSuite) TestIstioClientFails() {
|
||||
mockClientGenerator.On("IstioClient").Return(nil, errors.New("foo"))
|
||||
mockClientGenerator.On("DynamicKubernetesClient").Return(nil, errors.New("foo"))
|
||||
|
||||
_, err := ByNames(mockClientGenerator, []string{"istio-gateway"}, minimalConfig)
|
||||
_, err := ByNames(context.TODO(), mockClientGenerator, []string{"istio-gateway"}, minimalConfig)
|
||||
suite.Error(err, "should return an error if istio client cannot be created")
|
||||
|
||||
_, err = ByNames(mockClientGenerator, []string{"contour-httpproxy"}, minimalConfig)
|
||||
_, err = ByNames(context.TODO(), mockClientGenerator, []string{"contour-httpproxy"}, minimalConfig)
|
||||
suite.Error(err, "should return an error if contour client cannot be created")
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user