mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2026-05-04 14:21:33 +02:00
Merge branch 'master' into cloudflare/region
This commit is contained in:
commit
3b5ef9733b
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v5
|
||||
|
||||
2
.github/workflows/codeql-analysis.yaml
vendored
2
.github/workflows/codeql-analysis.yaml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- name: Install go version
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
|
||||
4
.github/workflows/docs.yaml
vendored
4
.github/workflows/docs.yaml
vendored
@ -15,11 +15,11 @@ jobs:
|
||||
name: Release Docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
|
||||
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: "pip"
|
||||
|
||||
2
.github/workflows/json-yaml-validate.yml
vendored
2
.github/workflows/json-yaml-validate.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
json-yaml-validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: json-yaml-validate
|
||||
uses: GrantBirki/json-yaml-validate@v3.2.1
|
||||
|
||||
4
.github/workflows/lint-test-chart.yaml
vendored
4
.github/workflows/lint-test-chart.yaml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
version: latest
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0
|
||||
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
python-version: "3.x"
|
||||
|
||||
2
.github/workflows/lint.yaml
vendored
2
.github/workflows/lint.yaml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v5
|
||||
|
||||
2
.github/workflows/release-chart.yaml
vendored
2
.github/workflows/release-chart.yaml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
2
.github/workflows/staging-image-tester.yaml
vendored
2
.github/workflows/staging-image-tester.yaml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v5
|
||||
|
||||
36
README.md
36
README.md
@ -74,20 +74,25 @@ See PR #3063 for all the discussions about it.
|
||||
|
||||
Known providers using webhooks:
|
||||
|
||||
| Provider | Repo |
|
||||
| -------- | ----------- |
|
||||
| Adguard Home Provider | https://github.com/muhlba91/external-dns-provider-adguard |
|
||||
| Anexia | https://github.com/ProbstenHias/external-dns-anexia-webhook |
|
||||
| Bizfly Cloud | https://github.com/bizflycloud/external-dns-bizflycloud-webhook |
|
||||
| Gcore | https://github.com/G-Core/external-dns-gcore-webhook |
|
||||
| GleSYS | https://github.com/glesys/external-dns-glesys |
|
||||
| Hetzner | https://github.com/mconfalonieri/external-dns-hetzner-webhook |
|
||||
| IONOS | https://github.com/ionos-cloud/external-dns-ionos-webhook |
|
||||
| Infoblox | https://github.com/AbsaOSS/external-dns-infoblox-webhook |
|
||||
| Netcup | https://github.com/mrueg/external-dns-netcup-webhook |
|
||||
| RouterOS | https://github.com/benfiola/external-dns-routeros-provider |
|
||||
| STACKIT | https://github.com/stackitcloud/external-dns-stackit-webhook |
|
||||
| Unifi | https://github.com/kashalls/external-dns-unifi-webhook |
|
||||
| Provider | Repo |
|
||||
|-----------------------|----------------------------------------------------------------------|
|
||||
| Adguard Home Provider | https://github.com/muhlba91/external-dns-provider-adguard |
|
||||
| Anexia | https://github.com/ProbstenHias/external-dns-anexia-webhook |
|
||||
| Bizfly Cloud | https://github.com/bizflycloud/external-dns-bizflycloud-webhook |
|
||||
| Efficient IP | https://github.com/EfficientIP-Labs/external-dns-efficientip-webhook |
|
||||
| Gcore | https://github.com/G-Core/external-dns-gcore-webhook |
|
||||
| GleSYS | https://github.com/glesys/external-dns-glesys |
|
||||
| Hetzner | https://github.com/mconfalonieri/external-dns-hetzner-webhook |
|
||||
| Huawei Cloud | https://github.com/setoru/external-dns-huaweicloud-webhook |
|
||||
| IONOS | https://github.com/ionos-cloud/external-dns-ionos-webhook |
|
||||
| Infoblox | https://github.com/AbsaOSS/external-dns-infoblox-webhook |
|
||||
| Mikrotik | https://github.com/mirceanton/external-dns-provider-mikrotik |
|
||||
| Netcup | https://github.com/mrueg/external-dns-netcup-webhook |
|
||||
| Netic | https://github.com/neticdk/external-dns-tidydns-webhook |
|
||||
| RouterOS | https://github.com/benfiola/external-dns-routeros-provider |
|
||||
| STACKIT | https://github.com/stackitcloud/external-dns-stackit-webhook |
|
||||
| Unifi | https://github.com/kashalls/external-dns-unifi-webhook |
|
||||
| Vultr | https://github.com/vultr/external-dns-vultr-webhook |
|
||||
|
||||
## Status of in-tree providers
|
||||
|
||||
@ -123,7 +128,6 @@ The following table clarifies the current status of the providers according to t
|
||||
| RFC2136 | Alpha | |
|
||||
| NS1 | Alpha | |
|
||||
| TransIP | Alpha | |
|
||||
| RancherDNS | Alpha | |
|
||||
| OVH | Alpha | |
|
||||
| Scaleway DNS | Alpha | @Sh4d1 |
|
||||
| UltraDNS | Alpha | |
|
||||
@ -181,10 +185,10 @@ The following tutorials are provided:
|
||||
* [NS1](docs/tutorials/ns1.md)
|
||||
* [NS Record Creation with CRD Source](docs/sources/ns-record.md)
|
||||
* [MX Record Creation with CRD Source](docs/sources/mx-record.md)
|
||||
* [TXT Record Creation with CRD Source](docs/sources/txt-record.md)
|
||||
* [OpenStack Designate](docs/tutorials/designate.md)
|
||||
* [Oracle Cloud Infrastructure (OCI) DNS](docs/tutorials/oracle.md)
|
||||
* [PowerDNS](docs/tutorials/pdns.md)
|
||||
* [RancherDNS (RDNS)](docs/tutorials/rdns.md)
|
||||
* [RFC2136](docs/tutorials/rfc2136.md)
|
||||
* [TransIP](docs/tutorials/transip.md)
|
||||
* [OVH](docs/tutorials/ovh.md)
|
||||
|
||||
@ -18,11 +18,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
### Added
|
||||
|
||||
- Ability to configure `imagePullSecrets` via helm `global` value ([#4667](https://github.com/kubernetes-sigs/external-dns/pull/4667)) _@jkroepke_
|
||||
|
||||
## [v1.15.0] - 2023-09-10
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated _ExternalDNS_ OCI image version to [v0.15.0](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.15.0). ([#xxxx](https://github.com/kubernetes-sigs/external-dns/pull/xxxx)) _@stevehipwell_
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `provider.webhook.resources` behavior to correctly leverage resource limits ([#4560](https://github.com/kubernetes-sigs/external-dns/pull/4560))
|
||||
- Fixed `provider.webhook.imagePullPolicy` behavior to correctly leverage pull policy ([#4643](https://github.com/kubernetes-sigs/external-dns/pull/4643)) _@kimsondrup_
|
||||
- Add correct webhook metric port to `Service` and `ServiceMonitor` ([#4643](https://github.com/kubernetes-sigs/external-dns/pull/4643)) _@kimsondrup_
|
||||
- Fixed `provider.webhook.resources` behavior to correctly leverage resource limits. ([#4560](https://github.com/kubernetes-sigs/external-dns/pull/4560)) _@crutonjohn_
|
||||
- Fixed `provider.webhook.imagePullPolicy` behavior to correctly leverage pull policy. ([#4643](https://github.com/kubernetes-sigs/external-dns/pull/4643)) _@kimsondrup_
|
||||
- Fixed to add correct webhook metric port to `Service` and `ServiceMonitor`. ([#4643](https://github.com/kubernetes-sigs/external-dns/pull/4643)) _@kimsondrup_
|
||||
- Fixed to no longer require the unauthenticated webhook provider port to be exposed for health probes. ([#4691](https://github.com/kubernetes-sigs/external-dns/pull/4691)) _@kimsondrup_ & _@hatrx_
|
||||
|
||||
## [v1.14.5] - 2023-06-10
|
||||
|
||||
@ -193,6 +204,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
RELEASE LINKS
|
||||
-->
|
||||
[UNRELEASED]: https://github.com/kubernetes-sigs/external-dns/tree/master/charts/external-dns
|
||||
[v1.15.0]: https://github.com/kubernetes-sigs/external-dns/releases/tag/external-dns-helm-chart-1.15.0
|
||||
[v1.14.5]: https://github.com/kubernetes-sigs/external-dns/releases/tag/external-dns-helm-chart-1.14.5
|
||||
[v1.14.4]: https://github.com/kubernetes-sigs/external-dns/releases/tag/external-dns-helm-chart-1.14.4
|
||||
[v1.14.3]: https://github.com/kubernetes-sigs/external-dns/releases/tag/external-dns-helm-chart-1.14.3
|
||||
|
||||
@ -2,8 +2,8 @@ apiVersion: v2
|
||||
name: external-dns
|
||||
description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
|
||||
type: application
|
||||
version: 1.14.5
|
||||
appVersion: 0.14.2
|
||||
version: 1.15.0
|
||||
appVersion: 0.15.0
|
||||
keywords:
|
||||
- kubernetes
|
||||
- externaldns
|
||||
@ -20,15 +20,13 @@ maintainers:
|
||||
email: steve.hipwell@gmail.com
|
||||
annotations:
|
||||
artifacthub.io/changes: |
|
||||
- kind: added
|
||||
description: "Added support for `extraContainers` argument."
|
||||
- kind: added
|
||||
description: "Added support for setting `excludeDomains` argument."
|
||||
- kind: changed
|
||||
description: "Updated _ExternalDNS_ OCI image version to [v0.14.2](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.14.2)."
|
||||
- kind: changed
|
||||
description: "Updated `DNSEndpoint` CRD."
|
||||
- kind: changed
|
||||
description: "Changed the implementation for `revisionHistoryLimit` to be more generic."
|
||||
description: "Updated _ExternalDNS_ OCI image version to [v0.15.0](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.15.0)."
|
||||
- kind: fixed
|
||||
description: "Fixed the `ServiceMonitor` job name to correctly use the instance label."
|
||||
description: "Fixed `provider.webhook.resources` behavior to correctly leverage resource limits."
|
||||
- kind: fixed
|
||||
description: "Fixed `provider.webhook.imagePullPolicy` behavior to correctly leverage pull policy."
|
||||
- kind: fixed
|
||||
description: "Fixed to add correct webhook metric port to `Service` and `ServiceMonitor`."
|
||||
- kind: fixed
|
||||
description: "Fixed to no longer require the unauthenticated webhook provider port to be exposed for health probes."
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# external-dns
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
|
||||
|
||||
@ -27,7 +27,7 @@ helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
|
||||
After you've installed the repo you can install the chart.
|
||||
|
||||
```shell
|
||||
helm upgrade --install external-dns external-dns/external-dns --version 1.14.5
|
||||
helm upgrade --install external-dns external-dns/external-dns --version 1.15.0
|
||||
```
|
||||
|
||||
## Providers
|
||||
@ -105,6 +105,7 @@ If `namespaced` is set to `true`, please ensure that `sources` my only contains
|
||||
| extraVolumeMounts | list | `[]` | Extra [volume mounts](https://kubernetes.io/docs/concepts/storage/volumes/) for the `external-dns` container. |
|
||||
| extraVolumes | list | `[]` | Extra [volumes](https://kubernetes.io/docs/concepts/storage/volumes/) for the `Pod`. |
|
||||
| fullnameOverride | string | `nil` | Override the full name of the chart. |
|
||||
| global.imagePullSecrets | list | `[]` | Global image pull secrets. |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy for the `external-dns` container. |
|
||||
| image.repository | string | `"registry.k8s.io/external-dns/external-dns"` | Image repository for the `external-dns` container. |
|
||||
| image.tag | string | `nil` | Image tag for the `external-dns` container, this will default to `.Chart.AppVersion` if not set. |
|
||||
@ -133,7 +134,7 @@ If `namespaced` is set to `true`, please ensure that `sources` my only contains
|
||||
| provider.webhook.readinessProbe | object | See _values.yaml_ | [Readiness probe](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/) configuration for the `webhook` container. |
|
||||
| provider.webhook.resources | object | `{}` | [Resources](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for the `webhook` container. |
|
||||
| provider.webhook.securityContext | object | See _values.yaml_ | [Pod security context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container) for the `webhook` container. |
|
||||
| provider.webhook.service.metricsPort | int | `8080` | Webhook metrics port for the service. |
|
||||
| provider.webhook.service.port | int | `8080` | Webhook exposed HTTP port for the service. |
|
||||
| provider.webhook.serviceMonitor | object | See _values.yaml_ | Optional [Service Monitor](https://prometheus-operator.dev/docs/operator/design/#servicemonitor) configuration for the `webhook` container. |
|
||||
| rbac.additionalPermissions | list | `[]` | Additional rules to add to the `ClusterRole`. |
|
||||
| rbac.create | bool | `true` | If `true`, create a `ClusterRole` & `ClusterRoleBinding` with access to the Kubernetes API. |
|
||||
|
||||
@ -40,7 +40,7 @@ spec:
|
||||
{{- if not (quote .Values.automountServiceAccountToken | empty) }}
|
||||
automountServiceAccountToken: {{ .Values.automountServiceAccountToken }}
|
||||
{{- end }}
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
{{- with (default .Values.global.imagePullSecrets .Values.imagePullSecrets) }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
@ -158,9 +158,6 @@ spec:
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http-webhook
|
||||
protocol: TCP
|
||||
containerPort: 8888
|
||||
- name: http-wh-metrics
|
||||
protocol: TCP
|
||||
containerPort: 8080
|
||||
livenessProbe:
|
||||
|
||||
@ -28,9 +28,9 @@ spec:
|
||||
protocol: TCP
|
||||
{{- if eq $providerName "webhook" }}
|
||||
{{- with .Values.provider.webhook.service }}
|
||||
- name: http-wh-metrics
|
||||
port: {{ .metricsPort }}
|
||||
targetPort: http-wh-metrics
|
||||
- name: http-webhook
|
||||
port: {{ .port }}
|
||||
targetPort: http-webhook
|
||||
protocol: TCP
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
@ -51,7 +51,7 @@ spec:
|
||||
{{- end }}
|
||||
{{- if eq $providerName "webhook" }}
|
||||
{{- with .Values.provider.webhook.serviceMonitor }}
|
||||
- port: http-wh-metrics
|
||||
- port: http-webhook
|
||||
path: /metrics
|
||||
{{- with .interval }}
|
||||
interval: {{ . }}
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"global": {
|
||||
"type": "object"
|
||||
},
|
||||
"provider": {
|
||||
"anyOf": [
|
||||
{
|
||||
|
||||
@ -2,6 +2,10 @@
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
global:
|
||||
# -- Global image pull secrets.
|
||||
imagePullSecrets: []
|
||||
|
||||
image:
|
||||
# -- Image repository for the `external-dns` container.
|
||||
repository: registry.k8s.io/external-dns/external-dns
|
||||
@ -270,8 +274,8 @@ provider:
|
||||
failureThreshold: 6
|
||||
successThreshold: 1
|
||||
service:
|
||||
# -- Webhook metrics port for the service.
|
||||
metricsPort: 8080
|
||||
# -- Webhook exposed HTTP port for the service.
|
||||
port: 8080
|
||||
# -- Optional [Service Monitor](https://prometheus-operator.dev/docs/operator/design/#servicemonitor) configuration for the `webhook` container.
|
||||
# @default -- See _values.yaml_
|
||||
serviceMonitor:
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
# MX record with CRD source
|
||||
|
||||
You can create and manage MX records with the help of [CRD source](../contributing/crd-source.md)
|
||||
and `DNSEndpoint` CRD. Currently, this feature is only supported by `aws`, `azure`, and `google` providers.
|
||||
and `DNSEndpoint` CRD. Currently, this feature is only supported by `aws`, `azure`, `google` and `digitalocean` providers.
|
||||
|
||||
In order to start managing MX records you need to set the `--managed-record-types MX` flag.
|
||||
|
||||
```console
|
||||
external-dns --source crd --provider {aws|azure|google} --managed-record-types A --managed-record-types CNAME --managed-record-types MX
|
||||
external-dns --source crd --provider {aws|azure|google|digitalocean} --managed-record-types A --managed-record-types CNAME --managed-record-types MX
|
||||
```
|
||||
|
||||
Targets within the CRD need to be specified according to the RFC 1034 (section 3.6.1). Below is an example of
|
||||
|
||||
@ -7,6 +7,9 @@ The node source adds an `A` record per each node `externalIP` (if not found, any
|
||||
It also adds an `AAAA` record per each node IPv6 `internalIP`.
|
||||
The TTL of the records can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation.
|
||||
|
||||
Nodes marked as **Unschedulable** as per [core/v1/NodeSpec](https://pkg.go.dev/k8s.io/api@v0.31.1/core/v1#NodeSpec) are excluded.
|
||||
This avoid exposing Unhealthy, NotReady or SchedulingDisabled (cordon) nodes.
|
||||
|
||||
## Manifest (for cluster without RBAC enabled)
|
||||
|
||||
```
|
||||
|
||||
30
docs/sources/txt-record.md
Normal file
30
docs/sources/txt-record.md
Normal file
@ -0,0 +1,30 @@
|
||||
# Creating TXT record with CRD source
|
||||
|
||||
You can create and manage TXT records with the help of [CRD source](../contributing/crd-source.md)
|
||||
and `DNSEndpoint` CRD. Currently, this feature is only supported by `digitalocean` providers.
|
||||
|
||||
In order to start managing TXT records you need to set the `--managed-record-types TXT` flag.
|
||||
|
||||
```console
|
||||
external-dns --source crd --provider {digitalocean} --managed-record-types A --managed-record-types CNAME --managed-record-types TXT
|
||||
```
|
||||
|
||||
Targets within the CRD need to be specified according to the RFC 1035 (section 3.3.14). Below is an example of
|
||||
`example.com` DNS TXT two records creation.
|
||||
|
||||
**NOTE** Current implementation do not support RFC 6763 (section 6).
|
||||
|
||||
```yaml
|
||||
apiVersion: externaldns.k8s.io/v1alpha1
|
||||
kind: DNSEndpoint
|
||||
metadata:
|
||||
name: examplemxrecord
|
||||
spec:
|
||||
endpoints:
|
||||
- dnsName: example.com
|
||||
recordTTL: 180
|
||||
recordType: TXT
|
||||
targets:
|
||||
- SOMETXT
|
||||
- ANOTHERTXT
|
||||
```
|
||||
@ -12,7 +12,7 @@ Learn more about the API in the [AWS Cloud Map API Reference](https://docs.aws.a
|
||||
|
||||
## IAM Permissions
|
||||
|
||||
To use the AWS Cloud Map API, a user must have permissions to create the DNS namespace. Additionally you need to make sure that your nodes (on which External DNS runs) have an IAM instance profile with the `AWSCloudMapFullAccess` managed policy attached, that provides following permissions:
|
||||
To use the AWS Cloud Map API, a user must have permissions to create the DNS namespace. You need to make sure that your nodes (on which External DNS runs) have an IAM instance profile with the `AWSCloudMapFullAccess` managed policy attached, that provides following permissions:
|
||||
|
||||
```
|
||||
{
|
||||
@ -42,6 +42,82 @@ To use the AWS Cloud Map API, a user must have permissions to create the DNS nam
|
||||
}
|
||||
```
|
||||
|
||||
### IAM Permissions with ABAC
|
||||
You can use Attribute-based access control(ABAC) for advanced deployments.
|
||||
|
||||
You can define AWS tags that are applied to services created by the controller. By doing so, you can have precise control over your IAM policy to limit the scope of the permissions to services managed by the controller, rather than having to grant full permissions on your entire AWS account.
|
||||
To pass tags to service creation, use either CLI flags or environment variables:
|
||||
|
||||
*cli:* `--aws-sd-create-tag=key1=value1 --aws-sd-create-tag=key2=value2`
|
||||
|
||||
*environment:* `EXTERNAL_DNS_AWS_SD_CREATE_TAG=key1=value1\nkey2=value2`
|
||||
|
||||
Using tags, your `servicediscovery` policy can become:
|
||||
|
||||
```
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"servicediscovery:ListNamespaces",
|
||||
"servicediscovery:ListServices"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"servicediscovery:CreateService",
|
||||
"servicediscovery:TagResource"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:RequestTag/YOUR_TAG_KEY": "YOUR_TAG_VALUE"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"servicediscovery:DiscoverInstances"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"servicediscovery:NamespaceName": "YOUR_NAMESPACE_NAME"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"servicediscovery:RegisterInstance",
|
||||
"servicediscovery:DeregisterInstance",
|
||||
"servicediscovery:DeleteService",
|
||||
"servicediscovery:UpdateService"
|
||||
],
|
||||
"Resource": [
|
||||
"*"
|
||||
],
|
||||
"Condition": {
|
||||
"StringEquals": {
|
||||
"aws:ResourceTag/YOUR_TAG_KEY": "YOUR_TAG_VALUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Set up a namespace
|
||||
|
||||
Create a DNS namespace using the AWS Cloud Map API:
|
||||
@ -228,6 +304,32 @@ spec:
|
||||
|
||||
This will set the TTL for the DNS record to 60 seconds.
|
||||
|
||||
## IPv6 Support
|
||||
|
||||
If your Kubernetes cluster is configured with IPv6 support, such as an [EKS cluster with IPv6 support](https://docs.aws.amazon.com/eks/latest/userguide/deploy-ipv6-cluster.html), ExternalDNS can
|
||||
also create AAAA DNS records.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/hostname: nginx.external-dns-test.my-org.com
|
||||
external-dns.alpha.kubernetes.io/ttl: "60"
|
||||
spec:
|
||||
ipFamilies:
|
||||
- "IPv6"
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 80
|
||||
name: http
|
||||
targetPort: 80
|
||||
selector:
|
||||
app: nginx
|
||||
```
|
||||
|
||||
:information_source: The AWS-SD provider does not currently support dualstack load balancers and will only create A records for these at this time. See the AWS provider and the [AWS Load Balancer Controller Tutorial](./aws-load-balancer-controller.md) for dualstack load balancer support.
|
||||
|
||||
## Clean up
|
||||
|
||||
|
||||
@ -98,6 +98,10 @@ $ az role assignment create --role "Reader" --assignee <appId GUID> --scope <res
|
||||
$ az role assignment create --role "Private DNS Zone Contributor" --assignee <appId GUID> --scope <dns zone resource id>
|
||||
```
|
||||
|
||||
## Throttling
|
||||
|
||||
When the ExternalDNS managed zones list doesn't change frequently, one can set `--azure-zones-cache-duration` (zones list cache time-to-live). The zones list cache is disabled by default, with a value of 0s.
|
||||
|
||||
## Deploy ExternalDNS
|
||||
Configure `kubectl` to be able to communicate and authenticate with your cluster.
|
||||
This is per default done through the file `~/.kube/config`.
|
||||
|
||||
@ -480,6 +480,10 @@ NOTE: it's also possible to specify (or override) ClientID through `userAssigned
|
||||
|
||||
NOTE: make sure the pod is restarted whenever you make a configuration change.
|
||||
|
||||
## Throttling
|
||||
|
||||
When the ExternalDNS managed zones list doesn't change frequently, one can set `--azure-zones-cache-duration` (zones list cache time-to-live). The zones list cache is disabled by default, with a value of 0s.
|
||||
|
||||
## Ingress used with ExternalDNS
|
||||
|
||||
This deployment assumes that you will be using nginx-ingress. When using nginx-ingress do not deploy it as a Daemon Set. This causes nginx-ingress to write the Cluster IP of the backend pods in the ingress status.loadbalancer.ip property which then has external-dns write the Cluster IP(s) in DNS vs. the nginx-ingress service external IP.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# GoDaddy
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for use within a
|
||||
This tutorial describes how to set up ExternalDNS for use within a
|
||||
Kubernetes cluster using GoDaddy DNS.
|
||||
|
||||
Make sure to use **>=0.6** version of ExternalDNS for this tutorial.
|
||||
@ -26,7 +26,7 @@ Connect your `kubectl` client to the cluster with which you want to test Externa
|
||||
|
||||
## Using Helm
|
||||
|
||||
Create a values.yaml file to configure ExternalDNS to use NS1 as the DNS provider. This file should include the necessary environment variables:
|
||||
Create a values.yaml file to configure ExternalDNS to use GoDaddy as the DNS provider. This file should include the necessary environment variables:
|
||||
|
||||
```shell
|
||||
provider:
|
||||
@ -36,7 +36,7 @@ extraArgs:
|
||||
- --godaddy-api-secret=YOUR_API_SECRET
|
||||
```
|
||||
|
||||
Ensure to replace YOUR_API_KEY and YOUR_API_SECRET with your actual godaddy API key and godaddy API secret.
|
||||
Be sure to replace YOUR_API_KEY and YOUR_API_SECRET with your actual GoDaddy API key and GoDaddy API secret.
|
||||
|
||||
Finally, install the ExternalDNS chart with Helm using the configuration specified in your values.yaml file:
|
||||
|
||||
|
||||
@ -47,6 +47,7 @@ spec:
|
||||
- --source=service # or ingress or both
|
||||
- --provider=pdns
|
||||
- --pdns-server={{ pdns-api-url }}
|
||||
- --pdns-server-id={{ pdns-server-id }}
|
||||
- --pdns-api-key={{ pdns-http-api-key }}
|
||||
- --txt-owner-id={{ owner-id-for-this-external-dns }}
|
||||
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the zones matching provided domain; omit to process all available zones in PowerDNS
|
||||
@ -172,3 +173,102 @@ Once the API shows the record correctly, you can double check your record using:
|
||||
```bash
|
||||
$ dig @${PDNS_FQDN} echo.example.com.
|
||||
```
|
||||
|
||||
## Using CRD source to manage DNS records in PowerDNS
|
||||
|
||||
[CRD source](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/contributing/crd-source.md) provides a generic mechanism and declarative way to manage DNS records in PowerDNS using external-dns.
|
||||
|
||||
```bash
|
||||
external-dns --source=crd --provider=pdns \
|
||||
--pdns-server={{ pdns-api-url }} \
|
||||
--pdns-api-key={{ pdns-api-key }} \
|
||||
--domain-filter=example.com \
|
||||
--managed-record-types=A \
|
||||
--managed-record-types=CNAME \
|
||||
--managed-record-types=TXT \
|
||||
--managed-record-types=MX \
|
||||
--managed-record-types=SRV
|
||||
```
|
||||
|
||||
Not all the record types are enabled by default so we can enable the required record types using `--managed-record-types`.
|
||||
|
||||
* Example for record type `A`
|
||||
|
||||
```yaml
|
||||
apiVersion: externaldns.k8s.io/v1alpha1
|
||||
kind: DNSEndpoint
|
||||
metadata:
|
||||
name: examplearecord
|
||||
spec:
|
||||
endpoints:
|
||||
- dnsName: example.com
|
||||
recordTTL: 60
|
||||
recordType: A
|
||||
targets:
|
||||
- 10.0.0.1
|
||||
```
|
||||
|
||||
* Example for record type `CNAME`
|
||||
|
||||
```yaml
|
||||
apiVersion: externaldns.k8s.io/v1alpha1
|
||||
kind: DNSEndpoint
|
||||
metadata:
|
||||
name: examplecnamerecord
|
||||
spec:
|
||||
endpoints:
|
||||
- dnsName: test-a.example.com
|
||||
recordTTL: 300
|
||||
recordType: CNAME
|
||||
targets:
|
||||
- example.com
|
||||
```
|
||||
|
||||
* Example for record type `TXT`
|
||||
|
||||
```yaml
|
||||
apiVersion: externaldns.k8s.io/v1alpha1
|
||||
kind: DNSEndpoint
|
||||
metadata:
|
||||
name: exampletxtrecord
|
||||
spec:
|
||||
endpoints:
|
||||
- dnsName: example.com
|
||||
recordTTL: 3600
|
||||
recordType: TXT
|
||||
targets:
|
||||
- '"v=spf1 include:spf.protection.example.com include:example.org -all"'
|
||||
- '"apple-domain-verification=XXXXXXXXXXXXX"'
|
||||
```
|
||||
|
||||
* Example for record type `MX`
|
||||
|
||||
```yaml
|
||||
apiVersion: externaldns.k8s.io/v1alpha1
|
||||
kind: DNSEndpoint
|
||||
metadata:
|
||||
name: examplemxrecord
|
||||
spec:
|
||||
endpoints:
|
||||
- dnsName: example.com
|
||||
recordTTL: 3600
|
||||
recordType: MX
|
||||
targets:
|
||||
- "10 mailhost1.example.com"
|
||||
```
|
||||
|
||||
* Example for record type `SRV`
|
||||
|
||||
```yaml
|
||||
apiVersion: externaldns.k8s.io/v1alpha1
|
||||
kind: DNSEndpoint
|
||||
metadata:
|
||||
name: examplesrvrecord
|
||||
spec:
|
||||
endpoints:
|
||||
- dnsName: _service._tls.example.com
|
||||
recordTTL: 180
|
||||
recordType: SRV
|
||||
targets:
|
||||
- "100 1 443 service.example.com"
|
||||
```
|
||||
@ -1,173 +0,0 @@
|
||||
# RancherDNS
|
||||
|
||||
This tutorial describes how to setup ExternalDNS for usage within a kubernetes cluster that makes use of [RDNS](https://github.com/rancher/rdns-server) and [nginx ingress controller](https://github.com/kubernetes/ingress-nginx).
|
||||
|
||||
You need to:
|
||||
|
||||
* install RDNS with [etcd](https://github.com/etcd-io/etcd) enabled
|
||||
* install external-dns with rdns as a provider
|
||||
|
||||
## Installing RDNS with etcdv3 backend
|
||||
|
||||
### Clone RDNS
|
||||
```
|
||||
git clone https://github.com/rancher/rdns-server.git
|
||||
```
|
||||
|
||||
### Installing ETCD
|
||||
```
|
||||
cd rdns-server
|
||||
docker-compose -f deploy/etcdv3/etcd-compose.yaml up -d
|
||||
```
|
||||
|
||||
> ETCD was successfully deployed on `http://172.31.35.77:2379`
|
||||
|
||||
### Installing RDNS
|
||||
```
|
||||
export ETCD_ENDPOINTS="http://172.31.35.77:2379"
|
||||
export DOMAIN="lb.rancher.cloud"
|
||||
./scripts/start etcdv3
|
||||
```
|
||||
|
||||
> RDNS was successfully deployed on `172.31.35.77`
|
||||
|
||||
## Installing ExternalDNS
|
||||
### Install external ExternalDNS
|
||||
ETCD_URLS is configured to etcd client service address.
|
||||
RDNS_ROOT_DOMAIN is configured to the same with RDNS DOMAIN environment. e.g. lb.rancher.cloud.
|
||||
|
||||
#### Manifest (for clusters without RBAC enabled)
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
namespace: kube-system
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.k8s.io/external-dns/external-dns:v0.15.0
|
||||
args:
|
||||
- --source=ingress
|
||||
- --provider=rdns
|
||||
- --log-level=debug # debug only
|
||||
env:
|
||||
- name: ETCD_URLS
|
||||
value: http://172.31.35.77:2379
|
||||
- name: RDNS_ROOT_DOMAIN
|
||||
value: lb.rancher.cloud
|
||||
```
|
||||
|
||||
#### Manifest (for clusters with RBAC enabled)
|
||||
```yaml
|
||||
|
||||
---
|
||||
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: kube-system
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: external-dns
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
namespace: kube-system
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
image: registry.k8s.io/external-dns/external-dns:v0.15.0
|
||||
args:
|
||||
- --source=ingress
|
||||
- --provider=rdns
|
||||
- --log-level=debug # debug only
|
||||
env:
|
||||
- name: ETCD_URLS
|
||||
value: http://172.31.35.77:2379
|
||||
- name: RDNS_ROOT_DOMAIN
|
||||
value: lb.rancher.cloud
|
||||
```
|
||||
|
||||
## Testing ingress example
|
||||
```
|
||||
$ cat ingress.yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: nginx.lb.rancher.cloud
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: nginx
|
||||
servicePort: 80
|
||||
|
||||
$ kubectl apply -f ingress.yaml
|
||||
ingress.extensions "nginx" created
|
||||
```
|
||||
|
||||
Wait a moment until DNS has the ingress IP. The RDNS IP in this example is "172.31.35.77".
|
||||
```
|
||||
$ kubectl get ingress
|
||||
NAME HOSTS ADDRESS PORTS AGE
|
||||
nginx nginx.lb.rancher.cloud 172.31.42.211 80 2m
|
||||
|
||||
$ kubectl run -it --rm --restart=Never --image=infoblox/dnstools:latest dnstools
|
||||
If you don't see a command prompt, try pressing enter.
|
||||
dnstools# dig @172.31.35.77 nginx.lb.rancher.cloud +short
|
||||
172.31.42.211
|
||||
dnstools#
|
||||
```
|
||||
@ -16,24 +16,32 @@ Providers implementing the HTTP API have to keep in sync with changes to the JSO
|
||||
|
||||
The following table represents the methods to implement mapped to their HTTP method and route.
|
||||
|
||||
| Provider method | HTTP Method | Route |
|
||||
| --- | --- | --- |
|
||||
| Records | GET | /records |
|
||||
| AdjustEndpoints | POST | /adjustendpoints |
|
||||
| ApplyChanges | POST | /records |
|
||||
| K8s probe | GET | /healthz |
|
||||
|
||||
### Provider endpoints
|
||||
|
||||
| Provider method | HTTP Method | Route | Description |
|
||||
| --------------- | ----------- | ---------------- | ---------------------------------------- |
|
||||
| Negotiate | GET | / | Negotiate `DomainFilter` |
|
||||
| Records | GET | /records | Get records |
|
||||
| AdjustEndpoints | POST | /adjustendpoints | Provider specific adjustments of records |
|
||||
| ApplyChanges | POST | /records | Apply record |
|
||||
|
||||
ExternalDNS will also make requests to the `/` endpoint for negotiation and for deserialization of the `DomainFilter`.
|
||||
|
||||
The server needs to respond to those requests by reading the `Accept` header and responding with a corresponding `Content-Type` header specifying the supported media type format and version.
|
||||
|
||||
The default recommended port is 8888, and should listen only on localhost (ie: only accessible for k8s probes and external-dns).
|
||||
The default recommended port for the provider endpoints is `8888`, and should listen only on `localhost` (ie: only accessible for external-dns).
|
||||
|
||||
**NOTE**: only `5xx` responses will be retried and only `20x` will be considered as successful. All status codes different from those will be considered a failure on ExternalDNS's side.
|
||||
|
||||
## Metrics support
|
||||
### Exposed endpoints
|
||||
|
||||
The metrics should listen ":8080" on `/metrics` following [Open Metrics](https://github.com/OpenObservability/OpenMetrics) format.
|
||||
| Provider method | HTTP Method | Route | Description |
|
||||
| --------------- | ----------- | -------- | -------------------------------------------------------------------------------------------- |
|
||||
| K8s probe | GET | /healthz | Used by `livenessProbe` and `readinessProbe` |
|
||||
| Open Metrics | GET | /metrics | Optional endpoint to expose [Open Metrics](https://github.com/OpenObservability/OpenMetrics) |
|
||||
|
||||
The default recommended port for the exposed endpoints is `8080`, and it should be bound to all interfaces (`0.0.0.0`)
|
||||
|
||||
## Custom Annotations
|
||||
|
||||
|
||||
128
go.mod
128
go.mod
@ -3,28 +3,35 @@ module sigs.k8s.io/external-dns
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute/metadata v0.5.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
|
||||
cloud.google.com/go/compute/metadata v0.5.2
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0
|
||||
github.com/F5Networks/k8s-bigip-ctlr/v2 v2.17.1
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0
|
||||
github.com/F5Networks/k8s-bigip-ctlr/v2 v2.18.1
|
||||
github.com/IBM-Cloud/ibm-cloud-cli-sdk v1.5.0
|
||||
github.com/IBM/go-sdk-core/v5 v5.17.4
|
||||
github.com/IBM/go-sdk-core/v5 v5.18.1
|
||||
github.com/IBM/networking-go-sdk v0.49.0
|
||||
github.com/Yamashou/gqlgenc v0.24.0
|
||||
github.com/Yamashou/gqlgenc v0.25.3
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2
|
||||
github.com/alecthomas/kingpin/v2 v2.4.0
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.0
|
||||
github.com/aws/aws-sdk-go v1.55.5
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.39
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.0
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.41
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.45.2
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.33.2
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2
|
||||
github.com/bodgit/tsig v1.2.2
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/civo/civogo v0.3.73
|
||||
github.com/cloudflare/cloudflare-go v0.102.0
|
||||
github.com/civo/civogo v0.3.84
|
||||
github.com/cloudflare/cloudflare-go v0.108.0
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/datawire/ambassador v1.12.4
|
||||
github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace
|
||||
github.com/digitalocean/godo v1.120.0
|
||||
github.com/digitalocean/godo v1.128.0
|
||||
github.com/dnsimple/dnsimple-go v1.7.0
|
||||
github.com/exoscale/egoscale v0.102.3
|
||||
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
|
||||
@ -32,59 +39,69 @@ require (
|
||||
github.com/go-logr/logr v1.4.2
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gophercloud/gophercloud v1.14.0
|
||||
github.com/gophercloud/gophercloud v1.14.1
|
||||
github.com/linki/instrumented_http v0.3.0
|
||||
github.com/linode/linodego v1.39.0
|
||||
github.com/linode/linodego v1.42.0
|
||||
github.com/maxatome/go-testdeep v1.14.0
|
||||
github.com/miekg/dns v1.1.62
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/openshift/api v0.0.0-20230607130528-611114dca681
|
||||
github.com/openshift/client-go v0.0.0-20230607134213-3cd0021bbee3
|
||||
github.com/oracle/oci-go-sdk/v65 v65.71.1
|
||||
github.com/oracle/oci-go-sdk/v65 v65.77.1
|
||||
github.com/ovh/go-ovh v1.6.0
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pluralsh/gqlclient v1.12.2
|
||||
github.com/projectcontour/contour v1.30.0
|
||||
github.com/prometheus/client_golang v1.20.0
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.29
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.984
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.984
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.984
|
||||
github.com/transip/gotransip/v6 v6.25.0
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1030
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1030
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.1030
|
||||
github.com/transip/gotransip/v6 v6.26.0
|
||||
github.com/ultradns/ultradns-sdk-go v1.3.7
|
||||
go.etcd.io/etcd/api/v3 v3.5.15
|
||||
go.etcd.io/etcd/client/v3 v3.5.15
|
||||
go.etcd.io/etcd/client/v3 v3.5.16
|
||||
go.uber.org/ratelimit v0.3.1
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/oauth2 v0.22.0
|
||||
golang.org/x/net v0.30.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/time v0.6.0
|
||||
google.golang.org/api v0.192.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.12.0
|
||||
golang.org/x/time v0.7.0
|
||||
google.golang.org/api v0.203.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.12.2
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
istio.io/api v1.23.0
|
||||
istio.io/client-go v1.23.0
|
||||
k8s.io/api v0.31.0
|
||||
k8s.io/apimachinery v0.31.0
|
||||
k8s.io/client-go v0.31.0
|
||||
istio.io/api v1.23.3
|
||||
istio.io/client-go v1.23.3
|
||||
k8s.io/api v0.31.2
|
||||
k8s.io/apimachinery v0.31.2
|
||||
k8s.io/client-go v0.31.2
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
sigs.k8s.io/gateway-api v1.1.0
|
||||
sigs.k8s.io/gateway-api v1.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.8.1 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
|
||||
cloud.google.com/go/auth v0.9.9 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f // indirect
|
||||
github.com/99designs/gqlgen v0.17.44 // indirect
|
||||
github.com/99designs/gqlgen v0.17.54 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||
github.com/Masterminds/semver v1.4.2 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect
|
||||
github.com/aws/smithy-go v1.22.0 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@ -119,7 +136,7 @@ require (
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
|
||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
@ -163,33 +180,34 @@ require (
|
||||
github.com/schollz/progressbar/v3 v3.8.6 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sony/gobreaker v0.5.0 // indirect
|
||||
github.com/sosodev/duration v1.2.0 // indirect
|
||||
github.com/sosodev/duration v1.3.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/terra-farm/udnssdk v1.3.5 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.14 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.16 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.5.16 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
|
||||
go.opentelemetry.io/otel v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.29.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.29.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/term v0.23.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
|
||||
google.golang.org/grpc v1.65.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/term v0.25.0 // indirect
|
||||
golang.org/x/text v0.19.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
||||
google.golang.org/grpc v1.67.1 // indirect
|
||||
google.golang.org/protobuf v1.35.1 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
|
||||
278
go.sum
278
go.sum
@ -2,34 +2,36 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxo
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
|
||||
cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
|
||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/auth v0.9.9 h1:BmtbpNQozo8ZwW2t7QJjnrQtdganSdmqeIBxHxNkEZQ=
|
||||
cloud.google.com/go/auth v0.9.9/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
|
||||
cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
|
||||
cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk=
|
||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
git.lukeshu.com/go/libsystemd v0.5.3/go.mod h1:FfDoP0i92r4p5Vn4NCLxvjkd7rCOe6otPa4L6hZg9WM=
|
||||
github.com/99designs/gqlgen v0.17.44 h1:OS2wLk/67Y+vXM75XHbwRnNYJcbuJd4OBL76RX3NQQA=
|
||||
github.com/99designs/gqlgen v0.17.44/go.mod h1:UTCu3xpK2mLI5qcMNw+HKDiEL77it/1XtAjisC4sLwM=
|
||||
github.com/99designs/gqlgen v0.17.54 h1:AsF49k/7RJlwA00RQYsYN0T8cQuaosnV/7G1dHC3Uh8=
|
||||
github.com/99designs/gqlgen v0.17.54/go.mod h1:77/+pVe6zlTsz++oUg2m8VLgzdUPHxjoAG3BxI5y8Rc=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible h1:KnPIugL51v3N3WwvaSmZbxukD1WuWXOiE9fRdu32f2I=
|
||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0/go.mod h1:LRr2FzBTQlONPPa5HREE5+RjSCTXl7BwOvYOaWTqCaI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0 h1:9Eih8XcEeQnFD0ntMlUDleKMzfeCeUfa+VbnDCI4AZs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.2.0/go.mod h1:wGPyTi+aURdqPAGMZDQqnNs9IrShADF8w2WZb6bKeq0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1/go.mod h1:c/wcGeGx5FUPbM/JltUYHZcKmigwyVLJlDq+4HdtXaw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 h1:yzrctSl9GMIQ5lHu7jc8olOsGjWDCsBpJhWqfGa/YIM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0/go.mod h1:GE4m0rnnfwLGX0Y9A9A25Zx5N/90jneT5ABevqzhuFQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
|
||||
@ -39,18 +41,20 @@ github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxB
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/F5Networks/k8s-bigip-ctlr/v2 v2.17.1 h1:DsX6HIG2kxYg2bRM7jIVgyBb6PcJXBt9iCsTfLvFwWE=
|
||||
github.com/F5Networks/k8s-bigip-ctlr/v2 v2.17.1/go.mod h1:mMF9pk71U8aIzMBS+CWq8OL3gLcFCRCiy+wNpE4gDIE=
|
||||
github.com/F5Networks/k8s-bigip-ctlr/v2 v2.18.1 h1:2JJzYLLONOJR73uudihkDh2ks8jL8/h6AhO7cbYpdIo=
|
||||
github.com/F5Networks/k8s-bigip-ctlr/v2 v2.18.1/go.mod h1:NZ7znr7KT1/0+MFPA9MHBsa4e0YhVz65ZCioi/m5KiE=
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
|
||||
github.com/IBM-Cloud/ibm-cloud-cli-sdk v1.5.0 h1:+a994rHmNFwlSA609Z6SYhn9xt+lhGFF+dsgjMF75hY=
|
||||
github.com/IBM-Cloud/ibm-cloud-cli-sdk v1.5.0/go.mod h1:XxWyb5MQDU4GnRBSDZpGgIFwfbcn+GAUbPKS8CR8Bxc=
|
||||
github.com/IBM/go-sdk-core/v5 v5.17.4 h1:VGb9+mRrnS2HpHZFM5hy4J6ppIWnwNrw0G+tLSgcJLc=
|
||||
github.com/IBM/go-sdk-core/v5 v5.17.4/go.mod h1:KsAAI7eStAWwQa4F96MLy+whYSh39JzNjklZRbN/8ns=
|
||||
github.com/IBM/go-sdk-core/v5 v5.18.1 h1:wdftQO8xejECTWTKF3FGXyW0McKxxDAopH7MKwA187c=
|
||||
github.com/IBM/go-sdk-core/v5 v5.18.1/go.mod h1:3ywpylZ41WhWPusqtpJZWopYlt2brebcphV7mA2JncU=
|
||||
github.com/IBM/networking-go-sdk v0.49.0 h1:lPS34u3C0JVrbxH+Ulua76Nwl6Frv8BEfq6LRkyvOv0=
|
||||
github.com/IBM/networking-go-sdk v0.49.0/go.mod h1:G9CKbmPE8gSLjN+ABh4hIZ1bMx076enl5Eekvj6zQnA=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
@ -77,8 +81,8 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/Yamashou/gqlgenc v0.24.0 h1:Aeufjb2zF0XxkeSTAVQ+DfiHL+ney/M2ovShZozBmHw=
|
||||
github.com/Yamashou/gqlgenc v0.24.0/go.mod h1:3QQD8ZoeEyVXuzqcMDsl8OfCCCTk+ulaxkvFFQDupIA=
|
||||
github.com/Yamashou/gqlgenc v0.25.3 h1:mVV8/Ho8EDZUQKQZbQqqXGNq8jc8aQfPpHhZOnTkMNE=
|
||||
github.com/Yamashou/gqlgenc v0.25.3/go.mod h1:G0g1N81xpIklVdnyboW1zwOHcj/n4hNfhTwfN29Rjig=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
|
||||
@ -94,8 +98,8 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAu
|
||||
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4=
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.0 h1:GIwkDPfeF/IBh5lZ5Mig50r1LXomNXR7t/oKGSMJWns=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.0/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.39 h1:zlenrBGDiSEu7YnpWiAPscKNolgIo9Z6jvM5pcWAEL4=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.63.39/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U=
|
||||
@ -116,9 +120,45 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:W
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
|
||||
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2 h1:AkNLZEyYMLnx/Q/mSKkcMqwNFXMAvFto9bNsHqcTduI=
|
||||
github.com/aws/aws-sdk-go-v2 v1.32.2/go.mod h1:2SK5n0a2karNTv5tbP1SjsX0uhttou00v/HpXKM1ZUo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.0 h1:FosVYWcqEtWNxHn8gB/Vs6jOlNwSoyOCA/g/sxyySOQ=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.28.0/go.mod h1:pYhbtvg1siOOg8h5an77rXle9tVG8T+BWLWAo7cOukc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.41 h1:7gXo+Axmp+R4Z+AK8YFQO0ZV3L0gizGINCOWxSLY9W8=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.41/go.mod h1:u4Eb8d3394YLubphT4jLEwN1rLNq2wFOlT6OuxFwPzU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12 h1:zYf8E8zaqolHA5nQ+VmX2r3wc4K6xw5i6xKvvMjZBL0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.15.12/go.mod h1:vYGIVLASk19Gb0FGwAcwES+qQF/aekD7m2G/X6mBOdQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21 h1:UAsR3xA31QGf79WzpG/ixT9FZvQlh5HY1NRqSHBNOCk=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.21/go.mod h1:JNr43NFf5L9YaG3eKTm7HQzls9J+A9YYcGI5Quh1r2Y=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21 h1:6jZVETqmYCadGFvrYEQfC5fAQmlo80CeL5psbno6r0s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.21/go.mod h1:1SR0GbLlnN3QUmYaflZNiH1ql+1qrSiB2vwcJ+4UM60=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2 h1:kJqyYcGqhWFmXqjRrtFFD4Oc9FXiskhsll2xnlpe8Do=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.36.2/go.mod h1:+t2Zc5VNOzhaWzpGE+cEYZADsgAAQT5v55AO+fhU+2s=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2 h1:E7Tuo0ipWpBl0f3uThz8cZsuyD5H8jLCnbtbKR4YL2s=
|
||||
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.24.2/go.mod h1:txOfweuNPBLhHodsV+C2lvPPRTommVTWbts9SZV6Myc=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2 h1:1G7TTQNPNv5fhCyIQGYk8FOggLgkzKq6c4Y1nOGzAOE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.2/go.mod h1:+ybYGLXoF7bcD7wIcMcklxyABZQmuBf1cHUhvY6FGIo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.45.2 h1:P4ElvGTPph12a87YpxPDIqCvVICeYJFV32UMMS/TIPc=
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.45.2/go.mod h1:zLKE53MjadFH0VYrDerAx25brxLYiSg4Vk3C+qPY4BQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.33.2 h1:TVfX2jnpYDxgORh5ozbSBpFa/D0B82Iq28a2+bY62uQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/servicediscovery v1.33.2/go.mod h1:Qy6c/ZAKohV1Ikot1ZOMm9be4bazUs27RLQjnERG4/U=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 h1:bSYXVyUzoTHoKalBmwaZxs97HU9DWWI3ehHSAMa7xOk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.2/go.mod h1:skMqY7JElusiOUjMJMOv1jJsP7YUg7DrhgqZZWuzu1U=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 h1:AhmO1fHINP9vFYUE0LHzCWg/LfUWUF+zFPEcY9QXb7o=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2/go.mod h1:o8aQygT2+MVP0NaV6kbdE1YnnIM8RRVQzoeUH45GOdI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ4CtM1Ll0XavNuVo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo=
|
||||
github.com/aws/smithy-go v1.22.0 h1:uunKnWlcoL3zO7q+gG2Pk53joueEOsnNB28QdMsmiMM=
|
||||
github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@ -147,12 +187,12 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
|
||||
github.com/civo/civogo v0.3.73 h1:thkNnkziU+xh+MEOChIUwRZI1forN20+SSAPe/VFDME=
|
||||
github.com/civo/civogo v0.3.73/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM=
|
||||
github.com/civo/civogo v0.3.84 h1:jf5IT7VJFPaReO6g8B0zqKhsYCIizaGo4PjDLY7Sl6Y=
|
||||
github.com/civo/civogo v0.3.84/go.mod h1:7UCYX+qeeJbrG55E1huv+0ySxcHTqq/26FcHLVelQJM=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.102.0 h1:+0MGbkirM/yzVLOYpWMgW7CDdKzesSbdwA2Y+rABrWI=
|
||||
github.com/cloudflare/cloudflare-go v0.102.0/go.mod h1:BOB41tXf31ti/qtBO9paYhyapotQbGRDbQoLOAF7pSg=
|
||||
github.com/cloudflare/cloudflare-go v0.108.0 h1:C4Skfjd8I8X3uEOGmQUT4/iGyZcWdkIU7HwvMoLkEE0=
|
||||
github.com/cloudflare/cloudflare-go v0.108.0/go.mod h1:m492eNahT/9MsN7Ppnoge8AaI7QhVFtEgVm3I9HJFeU=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s=
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@ -214,9 +254,11 @@ github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace h1:1SnCTPFh2AA
|
||||
github.com/denverdino/aliyungo v0.0.0-20230411124812-ab98a9173ace/go.mod h1:TK05uvk4XXfK2kdvRwfcZ1NaxjDxmm7H3aQLko0mJxA=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/digitalocean/godo v1.120.0 h1:t2DpzIitSnCDNQM7svSW4+cZd8E4Lv6+r8y33Kym0Xw=
|
||||
github.com/digitalocean/godo v1.120.0/go.mod h1:WQVH83OHUy6gC4gXpEVQKtxTd4L5oCp+5OialidkPLY=
|
||||
github.com/digitalocean/godo v1.128.0 h1:cGn/ibMSRZ9+8etbzMv2MnnCEPTTGlEnx3HHTPwdk1U=
|
||||
github.com/digitalocean/godo v1.128.0/go.mod h1:PU8JB6I1XYkQIdHFop8lLAY9ojp6M0XcU0TWaQSxbrc=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY=
|
||||
github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8=
|
||||
@ -261,8 +303,8 @@ github.com/exoscale/egoscale v0.102.3/go.mod h1:RPf2Gah6up+6kAEayHTQwqapzXlm93f0
|
||||
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
|
||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@ -487,8 +529,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
|
||||
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
|
||||
@ -497,8 +539,8 @@ github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
|
||||
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||
github.com/gookit/color v1.2.3/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gophercloud/gophercloud v1.14.0 h1:Bt9zQDhPrbd4qX7EILGmy+i7GP35cc+AAL2+wIJpUE8=
|
||||
github.com/gophercloud/gophercloud v1.14.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
|
||||
github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw=
|
||||
github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||
@ -618,6 +660,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
|
||||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
@ -668,8 +712,8 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/linki/instrumented_http v0.3.0 h1:dsN92+mXpfZtjJraartcQ99jnuw7fqsnPDjr85ma2dA=
|
||||
github.com/linki/instrumented_http v0.3.0/go.mod h1:pjYbItoegfuVi2GUOMhEqzvm/SJKuEL3H0tc8QRLRFk=
|
||||
github.com/linode/linodego v1.39.0 h1:gRsj2PXX+HTO3eYQaXEuQGsLeeLFDSBDontC5JL3Nn8=
|
||||
github.com/linode/linodego v1.39.0/go.mod h1:da8KzAQKSm5obwa06yXk5CZSDFMP9Wb08GA/O+aR9W0=
|
||||
github.com/linode/linodego v1.42.0 h1:ZSbi4MtvwrfB9Y6bknesorvvueBGGilcmh2D5dq76RM=
|
||||
github.com/linode/linodego v1.42.0/go.mod h1:2yzmY6pegPBDgx2HDllmt0eIk2IlzqcgK6NR0wFCFRY=
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/lyft/protoc-gen-star v0.4.10/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
@ -813,8 +857,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.71.1 h1:t1GpyLYaD/x2OrUoSyxNwBQaDaQP4F084FX8LQMXA/s=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.71.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.77.1 h1:gqjTXIUWvTihkn470AclxSAMcR1JecqjD2IUtp+sDIU=
|
||||
github.com/oracle/oci-go-sdk/v65 v65.77.1/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
|
||||
github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
|
||||
github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
|
||||
github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso=
|
||||
@ -858,8 +902,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
|
||||
github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI=
|
||||
github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
@ -890,6 +934,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
||||
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
@ -908,8 +954,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.29 h1:BkTk4gynLjguayxrYxZoMZjBnAOh7ntQvUkOFmkMqPU=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.29/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30 h1:yoKAVkEVwAqbGbR8n87rHQ1dulL25rKloGadb3vm770=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.30/go.mod h1:sH0u6fq6x4R5M7WxkoQFY/o7UaiItec0o1LinLCJNq8=
|
||||
github.com/schollz/progressbar/v3 v3.8.6 h1:QruMUdzZ1TbEP++S1m73OqRJk20ON11m6Wqv4EoGg8c=
|
||||
github.com/schollz/progressbar/v3 v3.8.6/go.mod h1:W5IEwbJecncFGBvuEh4A7HT1nZZ6WNIL2i3qbnI0WKY=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
@ -938,8 +984,8 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
|
||||
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/sosodev/duration v1.2.0 h1:pqK/FLSjsAADWY74SyWDCjOcd5l7H8GSnnOGEB9A1Us=
|
||||
github.com/sosodev/duration v1.2.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
@ -981,19 +1027,19 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.984 h1:QLSx+ibsV68NXKgzofPuo1gxFwYSWk2++rvxZxNjbVo=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.984/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.984 h1:ABZeSsOOkkBn+gToVp8KkMt4E69hQkBMEFegCD4w15Q=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.984/go.mod h1:r++X8dKvTZWltr4J83TIwqGlyvG5fKaVh7RGC2+BryI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.984 h1:dD0pLtMCJyRNMTystzaZ9WAK+UBb2ymGcdbkhtAub+8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.984/go.mod h1:hWQvbAt8kqN3JLfVpgxsn2YNxDBLaiUaXZFtF4ymRjU=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1030 h1:kwiUoCkooUgy7iPyhEEbio7WT21kGJUeZ5JeJfb/dYk=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1030/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1030 h1:U1n/Jr0rpKctFrTOsONkZlkbQeNiLqDbi5+MS7fGMO4=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1030/go.mod h1:erWu7r8lbWSjjVuWOlLFziOEY0K674qAOWHVSvdSvrY=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.1030 h1:reuw5wChMq2nXPYrRDrU7XRXU3kDvXh87kk8GMa80no=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.1030/go.mod h1:gPK/7DZzJ0CKMT3Fg8de70rkvg/ClcUUOMvfAkPVZ88=
|
||||
github.com/terra-farm/udnssdk v1.3.5 h1:MNR3adfuuEK/l04+jzo8WW/0fnorY+nW515qb3vEr6I=
|
||||
github.com/terra-farm/udnssdk v1.3.5/go.mod h1:8RnM56yZTR7mYyUIvrDgXzdRaEyFIzqdEi7+um26Sv8=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/transip/gotransip/v6 v6.25.0 h1:/H+SjMq/9HNZ0/maE1OLhJpxLaCGHsxq0PWaMPJHxK4=
|
||||
github.com/transip/gotransip/v6 v6.25.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s=
|
||||
github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550=
|
||||
github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
|
||||
@ -1013,8 +1059,8 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
|
||||
github.com/vektah/gqlparser/v2 v2.5.14 h1:dzLq75BJe03jjQm6n56PdH1oweB8ana42wj7E4jRy70=
|
||||
github.com/vektah/gqlparser/v2 v2.5.14/go.mod h1:WQQjFc+I1YIzoPvZBhUQX7waZgg3pMLi0r8KymvAE2w=
|
||||
github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8=
|
||||
github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@ -1039,12 +1085,12 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
|
||||
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
|
||||
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
|
||||
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
|
||||
go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0=
|
||||
go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E=
|
||||
go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE=
|
||||
go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
@ -1057,14 +1103,14 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
|
||||
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
|
||||
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
|
||||
go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
|
||||
go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
|
||||
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
|
||||
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
@ -1111,8 +1157,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -1138,8 +1184,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -1182,14 +1228,14 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
|
||||
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -1259,8 +1305,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@ -1268,8 +1314,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -1281,8 +1327,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -1290,8 +1336,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -1326,8 +1372,8 @@ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@ -1340,8 +1386,8 @@ gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZ
|
||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.192.0 h1:PljqpNAfZaaSpS+TnANfnNAXKdzHM/B9bKhwRlo7JP0=
|
||||
google.golang.org/api v0.192.0/go.mod h1:9VcphjvAxPKLmSxVSzPlSRXy/5ARMEw5bf58WoVXafQ=
|
||||
google.golang.org/api v0.203.0 h1:SrEeuwU3S11Wlscsn+LA1kb/Y5xT8uggJSkIhD08NAU=
|
||||
google.golang.org/api v0.203.0/go.mod h1:BuOVyCSYEPwJb3npWvDnNmFI92f3GeRnHNkETneT3SI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
@ -1356,10 +1402,10 @@ google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dT
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@ -1374,8 +1420,8 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
|
||||
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
|
||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@ -1388,8 +1434,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -1416,8 +1462,8 @@ gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.12.0 h1:cqdqQoTx17JmTusfxh5m3e2b36jfUzFAZedv89pFX18=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.12.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.12.2 h1:SPM5BTTMJ1zVBhMMiiPFdF7l6Y3fq5o7bKM7jDqsUfM=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.12.2/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
@ -1445,23 +1491,23 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
istio.io/api v1.23.0 h1:yqv3lNW6XSYS5XkbEkxsmFROXIQznp4lFWqj7xKEqCA=
|
||||
istio.io/api v1.23.0/go.mod h1:QPSTGXuIQdnZFEm3myf9NZ5uBMwCdJWUvfj9ZZ+2oBM=
|
||||
istio.io/client-go v1.23.0 h1://xojbifr84q29WE3eMx74p36hD4lvcejX1KxE3iJvY=
|
||||
istio.io/client-go v1.23.0/go.mod h1:3qX/KBS5aR47QV4JhphcZl5ysnZ53x78TBjNQLM2TC4=
|
||||
istio.io/api v1.23.3 h1:+CP0AHz8/+WJ7ZKJLbilHEiqBCi5KLe1Yil9bJI39ow=
|
||||
istio.io/api v1.23.3/go.mod h1:QPSTGXuIQdnZFEm3myf9NZ5uBMwCdJWUvfj9ZZ+2oBM=
|
||||
istio.io/client-go v1.23.3 h1:rs+mO4A+NaXVcZgDO0RRZE7KRAlDooq2PSkxl7tevig=
|
||||
istio.io/client-go v1.23.3/go.mod h1:Lfa3anzx7/kCOpcAciR+JiRMj/SYuzDcbXQDjkThnLg=
|
||||
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
|
||||
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
|
||||
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
|
||||
k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo=
|
||||
k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE=
|
||||
k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
|
||||
k8s.io/api v0.31.2/go.mod h1:bWmGvrGPssSK1ljmLzd3pwCQ9MgoTsRCuK35u6SygUk=
|
||||
k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo=
|
||||
k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY=
|
||||
k8s.io/apiextensions-apiserver v0.18.4/go.mod h1:NYeyeYq4SIpFlPxSAB6jHPIdvu3hL0pc36wuRChybio=
|
||||
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||
k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
|
||||
k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc=
|
||||
k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw=
|
||||
k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
|
||||
k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw=
|
||||
k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw=
|
||||
k8s.io/apiserver v0.18.4/go.mod h1:q+zoFct5ABNnYkGIaGQ3bcbUNdmPyOCoEBcg51LChY8=
|
||||
@ -1470,8 +1516,8 @@ k8s.io/cli-runtime v0.18.4/go.mod h1:9/hS/Cuf7NVzWR5F/5tyS6xsnclxoPLVtwhnkJG1Y4g
|
||||
k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8=
|
||||
k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
|
||||
k8s.io/client-go v0.18.4/go.mod h1:f5sXwL4yAZRkAtzOxRWUhA/N8XzGCb+nPZI8PfobZ9g=
|
||||
k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=
|
||||
k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU=
|
||||
k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc=
|
||||
k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs=
|
||||
k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
|
||||
k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc=
|
||||
k8s.io/code-generator v0.18.4/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
|
||||
@ -1509,8 +1555,8 @@ sigs.k8s.io/controller-runtime v0.6.1/go.mod h1:XRYBPdbf5XJu9kpS84VJiZ7h/u1hF3gE
|
||||
sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw=
|
||||
sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg=
|
||||
sigs.k8s.io/controller-tools v0.3.1-0.20200517180335-820a4a27ea84/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI=
|
||||
sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM=
|
||||
sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs=
|
||||
sigs.k8s.io/gateway-api v1.2.0 h1:LrToiFwtqKTKZcZtoQPTuo3FxhrrhTgzQG0Te+YGSo8=
|
||||
sigs.k8s.io/gateway-api v1.2.0/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
|
||||
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
|
||||
|
||||
40
main.go
40
main.go
@ -25,10 +25,9 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
awsSDK "github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go/service/route53"
|
||||
sd "github.com/aws/aws-sdk-go/service/servicediscovery"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go-v2/service/route53"
|
||||
sd "github.com/aws/aws-sdk-go-v2/service/servicediscovery"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -66,7 +65,6 @@ import (
|
||||
"sigs.k8s.io/external-dns/provider/pdns"
|
||||
"sigs.k8s.io/external-dns/provider/pihole"
|
||||
"sigs.k8s.io/external-dns/provider/plural"
|
||||
"sigs.k8s.io/external-dns/provider/rdns"
|
||||
"sigs.k8s.io/external-dns/provider/rfc2136"
|
||||
"sigs.k8s.io/external-dns/provider/scaleway"
|
||||
"sigs.k8s.io/external-dns/provider/tencentcloud"
|
||||
@ -206,10 +204,10 @@ func main() {
|
||||
case "alibabacloud":
|
||||
p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
|
||||
case "aws":
|
||||
sessions := aws.CreateSessions(cfg)
|
||||
clients := make(map[string]aws.Route53API, len(sessions))
|
||||
for profile, session := range sessions {
|
||||
clients[profile] = route53.New(session)
|
||||
configs := aws.CreateV2Configs(cfg)
|
||||
clients := make(map[string]aws.Route53API, len(configs))
|
||||
for profile, config := range configs {
|
||||
clients[profile] = route53.NewFromConfig(config)
|
||||
}
|
||||
|
||||
p, err = aws.NewAWSProvider(
|
||||
@ -236,11 +234,11 @@ 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.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, sd.New(aws.CreateDefaultSession(cfg)))
|
||||
p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, cfg.AWSSDCreateTag, sd.NewFromConfig(aws.CreateDefaultV2Config(cfg)))
|
||||
case "azure-dns", "azure":
|
||||
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.DryRun)
|
||||
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.DryRun)
|
||||
case "azure-private-dns":
|
||||
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.DryRun)
|
||||
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.DryRun)
|
||||
case "ultradns":
|
||||
p, err = ultradns.NewUltraDNSProvider(domainFilter, cfg.DryRun)
|
||||
case "civo":
|
||||
@ -259,13 +257,6 @@ func main() {
|
||||
p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
case "coredns", "skydns":
|
||||
p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
|
||||
case "rdns":
|
||||
p, err = rdns.NewRDNSProvider(
|
||||
rdns.RDNSConfig{
|
||||
DomainFilter: domainFilter,
|
||||
DryRun: cfg.DryRun,
|
||||
},
|
||||
)
|
||||
case "exoscale":
|
||||
p, err = exoscale.NewExoscaleProvider(
|
||||
cfg.ExoscaleAPIEnvironment,
|
||||
@ -287,6 +278,7 @@ func main() {
|
||||
DomainFilter: domainFilter,
|
||||
DryRun: cfg.DryRun,
|
||||
Server: cfg.PDNSServer,
|
||||
ServerID: cfg.PDNSServerID,
|
||||
APIKey: cfg.PDNSAPIKey,
|
||||
TLSConfig: pdns.TLSConfig{
|
||||
SkipTLSVerify: cfg.PDNSSkipTLSVerify,
|
||||
@ -383,11 +375,15 @@ func main() {
|
||||
var r registry.Registry
|
||||
switch cfg.Registry {
|
||||
case "dynamodb":
|
||||
config := awsSDK.NewConfig()
|
||||
var dynamodbOpts []func(*dynamodb.Options)
|
||||
if cfg.AWSDynamoDBRegion != "" {
|
||||
config = config.WithRegion(cfg.AWSDynamoDBRegion)
|
||||
dynamodbOpts = []func(*dynamodb.Options){
|
||||
func(opts *dynamodb.Options) {
|
||||
opts.Region = cfg.AWSDynamoDBRegion
|
||||
},
|
||||
}
|
||||
}
|
||||
r, err = registry.NewDynamoDBRegistry(p, cfg.TXTOwnerID, dynamodb.New(aws.CreateDefaultSession(cfg), config), cfg.AWSDynamoDBTable, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, []byte(cfg.TXTEncryptAESKey), cfg.TXTCacheInterval)
|
||||
r, err = registry.NewDynamoDBRegistry(p, cfg.TXTOwnerID, dynamodb.NewFromConfig(aws.CreateDefaultV2Config(cfg), dynamodbOpts...), cfg.AWSDynamoDBTable, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, []byte(cfg.TXTEncryptAESKey), cfg.TXTCacheInterval)
|
||||
case "noop":
|
||||
r, err = registry.NewNoopRegistry(p)
|
||||
case "txt":
|
||||
|
||||
@ -86,7 +86,7 @@ type Config struct {
|
||||
AWSZoneTagFilter []string
|
||||
AWSAssumeRole string
|
||||
AWSProfiles []string
|
||||
AWSAssumeRoleExternalID string
|
||||
AWSAssumeRoleExternalID string `secure:"yes"`
|
||||
AWSBatchChangeSize int
|
||||
AWSBatchChangeSizeBytes int
|
||||
AWSBatchChangeSizeValues int
|
||||
@ -96,6 +96,7 @@ type Config struct {
|
||||
AWSPreferCNAME bool
|
||||
AWSZoneCacheDuration time.Duration
|
||||
AWSSDServiceCleanup bool
|
||||
AWSSDCreateTag map[string]string
|
||||
AWSZoneMatchParent bool
|
||||
AWSDynamoDBRegion string
|
||||
AWSDynamoDBTable string
|
||||
@ -104,6 +105,7 @@ type Config struct {
|
||||
AzureSubscriptionID string
|
||||
AzureUserAssignedIdentityClientID string
|
||||
AzureActiveDirectoryAuthorityHost string
|
||||
AzureZonesCacheDuration time.Duration
|
||||
CloudflareProxied bool
|
||||
CloudflareDNSRecordsPerPage int
|
||||
CloudflareRegionKey string
|
||||
@ -123,6 +125,7 @@ type Config struct {
|
||||
OVHEndpoint string
|
||||
OVHApiRateLimit int
|
||||
PDNSServer string
|
||||
PDNSServerID string
|
||||
PDNSAPIKey string `secure:"yes"`
|
||||
PDNSSkipTLSVerify bool
|
||||
TLSCA string
|
||||
@ -256,11 +259,13 @@ var defaultConfig = &Config{
|
||||
AWSPreferCNAME: false,
|
||||
AWSZoneCacheDuration: 0 * time.Second,
|
||||
AWSSDServiceCleanup: false,
|
||||
AWSSDCreateTag: map[string]string{},
|
||||
AWSDynamoDBRegion: "",
|
||||
AWSDynamoDBTable: "external-dns",
|
||||
AzureConfigFile: "/etc/kubernetes/azure.json",
|
||||
AzureResourceGroup: "",
|
||||
AzureSubscriptionID: "",
|
||||
AzureZonesCacheDuration: 0 * time.Second,
|
||||
CloudflareProxied: false,
|
||||
CloudflareDNSRecordsPerPage: 100,
|
||||
CloudflareRegionKey: "earth",
|
||||
@ -278,6 +283,7 @@ var defaultConfig = &Config{
|
||||
OVHEndpoint: "ovh-eu",
|
||||
OVHApiRateLimit: 20,
|
||||
PDNSServer: "http://localhost:8081",
|
||||
PDNSServerID: "localhost",
|
||||
PDNSAPIKey: "",
|
||||
PDNSSkipTLSVerify: false,
|
||||
TLSCA: "",
|
||||
@ -357,7 +363,9 @@ var defaultConfig = &Config{
|
||||
|
||||
// NewConfig returns new Config object
|
||||
func NewConfig() *Config {
|
||||
return &Config{}
|
||||
return &Config{
|
||||
AWSSDCreateTag: map[string]string{},
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) String() string {
|
||||
@ -445,7 +453,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("nat64-networks", "Adding an A record for each AAAA record in NAT64-enabled networks; specify multiple times for multiple possible nets (optional)").StringsVar(&cfg.NAT64Networks)
|
||||
|
||||
// Flags related to providers
|
||||
providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rdns", "rfc2136", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "webhook"}
|
||||
providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rfc2136", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "webhook"}
|
||||
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: "+strings.Join(providers, ", ")+")").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, providers...)
|
||||
app.Flag("provider-cache-time", "The time to cache the DNS provider record list requests.").Default(defaultConfig.ProviderCacheTime.String()).DurationVar(&cfg.ProviderCacheTime)
|
||||
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
|
||||
@ -475,10 +483,12 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
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-zone-match-parent", "Expand limit possible target by sub-domains (default: disabled)").BoolVar(&cfg.AWSZoneMatchParent)
|
||||
app.Flag("aws-sd-service-cleanup", "When using the AWS CloudMap provider, delete empty Services without endpoints (default: disabled)").BoolVar(&cfg.AWSSDServiceCleanup)
|
||||
app.Flag("aws-sd-create-tag", "When using the AWS CloudMap provider, add tag to created services. The flag can be used multiple times").StringMapVar(&cfg.AWSSDCreateTag)
|
||||
app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure)").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile)
|
||||
app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)
|
||||
app.Flag("azure-subscription-id", "When using the Azure provider, override the Azure subscription to use (optional)").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("azure-zones-cache-duration", "When using the Azure provider, set the zones list cache TTL (0s to disable).").Default(defaultConfig.AzureZonesCacheDuration.String()).DurationVar(&cfg.AzureZonesCacheDuration)
|
||||
app.Flag("tencent-cloud-config-file", "When using the Tencent Cloud provider, specify the Tencent Cloud configuration file (required when --provider=tencentcloud)").Default(defaultConfig.TencentCloudConfigFile).StringVar(&cfg.TencentCloudConfigFile)
|
||||
app.Flag("tencent-cloud-zone-type", "When using the Tencent Cloud provider, filter for zones with visibility (optional, options: public, private)").Default(defaultConfig.TencentCloudZoneType).EnumVar(&cfg.TencentCloudZoneType, "", "public", "private")
|
||||
|
||||
@ -501,6 +511,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("ovh-endpoint", "When using the OVH provider, specify the endpoint (default: ovh-eu)").Default(defaultConfig.OVHEndpoint).StringVar(&cfg.OVHEndpoint)
|
||||
app.Flag("ovh-api-rate-limit", "When using the OVH provider, specify the API request rate limit, X operations by seconds (default: 20)").Default(strconv.Itoa(defaultConfig.OVHApiRateLimit)).IntVar(&cfg.OVHApiRateLimit)
|
||||
app.Flag("pdns-server", "When using the PowerDNS/PDNS provider, specify the URL to the pdns server (required when --provider=pdns)").Default(defaultConfig.PDNSServer).StringVar(&cfg.PDNSServer)
|
||||
app.Flag("pdns-server-id", "When using the PowerDNS/PDNS provider, specify the id of the server to retrieve. Should be `localhost` except when the server is behind a proxy (optional when --provider=pdns) (default: localhost)").Default(defaultConfig.PDNSServerID).StringVar(&cfg.PDNSServerID)
|
||||
app.Flag("pdns-api-key", "When using the PowerDNS/PDNS provider, specify the API key to use to authorize requests (required when --provider=pdns)").Default(defaultConfig.PDNSAPIKey).StringVar(&cfg.PDNSAPIKey)
|
||||
app.Flag("pdns-skip-tls-verify", "When using the PowerDNS/PDNS provider, disable verification of any TLS certificates (optional when --provider=pdns) (default: false)").Default(strconv.FormatBool(defaultConfig.PDNSSkipTLSVerify)).BoolVar(&cfg.PDNSSkipTLSVerify)
|
||||
app.Flag("ns1-endpoint", "When using the NS1 provider, specify the URL of the API endpoint to target (default: https://api.nsone.net/v1/)").Default(defaultConfig.NS1Endpoint).StringVar(&cfg.NS1Endpoint)
|
||||
|
||||
@ -68,6 +68,7 @@ var (
|
||||
AWSProfiles: []string{""},
|
||||
AWSZoneCacheDuration: 0 * time.Second,
|
||||
AWSSDServiceCleanup: false,
|
||||
AWSSDCreateTag: map[string]string{},
|
||||
AWSDynamoDBTable: "external-dns",
|
||||
AzureConfigFile: "/etc/kubernetes/azure.json",
|
||||
AzureResourceGroup: "",
|
||||
@ -89,6 +90,7 @@ var (
|
||||
OVHEndpoint: "ovh-eu",
|
||||
OVHApiRateLimit: 20,
|
||||
PDNSServer: "http://localhost:8081",
|
||||
PDNSServerID: "localhost",
|
||||
PDNSAPIKey: "",
|
||||
Policy: "sync",
|
||||
Registry: "txt",
|
||||
@ -167,6 +169,7 @@ var (
|
||||
AWSProfiles: []string{"profile1", "profile2"},
|
||||
AWSZoneCacheDuration: 10 * time.Second,
|
||||
AWSSDServiceCleanup: true,
|
||||
AWSSDCreateTag: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
AWSDynamoDBTable: "custom-table",
|
||||
AzureConfigFile: "azure.json",
|
||||
AzureResourceGroup: "arg",
|
||||
@ -188,6 +191,7 @@ var (
|
||||
OVHEndpoint: "ovh-ca",
|
||||
OVHApiRateLimit: 42,
|
||||
PDNSServer: "http://ns.example.com:8081",
|
||||
PDNSServerID: "localhost",
|
||||
PDNSAPIKey: "some-secret-key",
|
||||
PDNSSkipTLSVerify: true,
|
||||
TLSCA: "/path/to/ca.crt",
|
||||
@ -288,6 +292,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"--ovh-endpoint=ovh-ca",
|
||||
"--ovh-api-rate-limit=42",
|
||||
"--pdns-server=http://ns.example.com:8081",
|
||||
"--pdns-server-id=localhost",
|
||||
"--pdns-api-key=some-secret-key",
|
||||
"--pdns-skip-tls-verify",
|
||||
"--oci-config-file=oci.yaml",
|
||||
@ -325,6 +330,8 @@ func TestParseFlags(t *testing.T) {
|
||||
"--aws-profile=profile2",
|
||||
"--aws-zones-cache-duration=10s",
|
||||
"--aws-sd-service-cleanup",
|
||||
"--aws-sd-create-tag=key1=value1",
|
||||
"--aws-sd-create-tag=key2=value2",
|
||||
"--no-aws-evaluate-target-health",
|
||||
"--policy=upsert-only",
|
||||
"--registry=noop",
|
||||
@ -413,6 +420,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_TARGET_NET_FILTER": "10.0.0.0/9\n10.1.0.0/9",
|
||||
"EXTERNAL_DNS_EXCLUDE_TARGET_NET": "1.0.0.0/9\n1.1.0.0/9",
|
||||
"EXTERNAL_DNS_PDNS_SERVER": "http://ns.example.com:8081",
|
||||
"EXTERNAL_DNS_PDNS_ID": "localhost",
|
||||
"EXTERNAL_DNS_PDNS_API_KEY": "some-secret-key",
|
||||
"EXTERNAL_DNS_PDNS_SKIP_TLS_VERIFY": "1",
|
||||
"EXTERNAL_DNS_RDNS_ROOT_DOMAIN": "lb.rancher.cloud",
|
||||
@ -436,6 +444,7 @@ func TestParseFlags(t *testing.T) {
|
||||
"EXTERNAL_DNS_AWS_PROFILE": "profile1\nprofile2",
|
||||
"EXTERNAL_DNS_AWS_ZONES_CACHE_DURATION": "10s",
|
||||
"EXTERNAL_DNS_AWS_SD_SERVICE_CLEANUP": "true",
|
||||
"EXTERNAL_DNS_AWS_SD_CREATE_TAG": "key1=value1\nkey2=value2",
|
||||
"EXTERNAL_DNS_DYNAMODB_TABLE": "custom-table",
|
||||
"EXTERNAL_DNS_POLICY": "upsert-only",
|
||||
"EXTERNAL_DNS_REGISTRY": "noop",
|
||||
@ -504,8 +513,8 @@ func restoreEnv(t *testing.T, originalEnv map[string]string) {
|
||||
|
||||
func TestPasswordsNotLogged(t *testing.T) {
|
||||
cfg := Config{
|
||||
PDNSAPIKey: "pdns-api-key",
|
||||
RFC2136TSIGSecret: "tsig-secret",
|
||||
PDNSAPIKey: "pdns-api-key",
|
||||
RFC2136TSIGSecret: "tsig-secret",
|
||||
}
|
||||
|
||||
s := cfg.String()
|
||||
|
||||
@ -240,6 +240,10 @@ func (p *Plan) Calculate() *Plan {
|
||||
|
||||
if ownersMatch {
|
||||
changes.Create = append(changes.Create, creates...)
|
||||
} else if log.GetLevel() == log.DebugLevel {
|
||||
for _, current := range row.current {
|
||||
log.Debugf(`Skipping endpoint %v because owner id does not match for one or more items to create, found: "%s", required: "%s"`, current, current.Labels[endpoint.OwnerLabelKey], p.OwnerID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,16 +212,16 @@ func (m *MockAlibabaCloudPrivateZoneAPI) DescribeZoneInfo(request *pvtz.Describe
|
||||
response = pvtz.CreateDescribeZoneInfoResponse()
|
||||
response.ZoneId = m.zone.ZoneId
|
||||
response.ZoneName = m.zone.ZoneName
|
||||
response.BindVpcs = pvtz.BindVpcsInDescribeZoneInfo{Vpc: m.zone.Vpcs.Vpc}
|
||||
response.BindVpcs = pvtz.BindVpcsInDescribeZoneInfo{Vpc: make([]pvtz.VpcInDescribeZoneInfo, len(m.zone.Vpcs.Vpc))}
|
||||
for idx, vpc := range m.zone.Vpcs.Vpc {
|
||||
response.BindVpcs.Vpc[idx] = pvtz.VpcInDescribeZoneInfo{VpcName: vpc.VpcName, VpcId: vpc.VpcId, VpcType: vpc.VpcType, RegionName: vpc.RegionName, RegionId: vpc.RegionId}
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func newTestAlibabaCloudProvider(private bool) *AlibabaCloudProvider {
|
||||
cfg := alibabaCloudConfig{
|
||||
RegionID: "cn-beijing",
|
||||
AccessKeyID: "xxxxxx",
|
||||
AccessKeySecret: "xxxxxx",
|
||||
VPCID: "vpc-xxxxxx",
|
||||
VPCID: "vpc-xxxxxx",
|
||||
}
|
||||
//
|
||||
//dnsClient, _ := alidns.NewClientWithAccessKey(
|
||||
|
||||
@ -20,15 +20,15 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/route53"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/service/route53"
|
||||
route53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
@ -41,11 +41,11 @@ const (
|
||||
recordTTL = 300
|
||||
// From the experiments, it seems that the default MaxItems applied is 100,
|
||||
// and that, on the server side, there is a hard limit of 300 elements per page.
|
||||
// After a discussion with AWS representants, clients should accept
|
||||
// when less items are returned, and still paginate accordingly.
|
||||
// After a discussion with AWS representatives, clients should accept
|
||||
// when fewer items are returned, and still paginate accordingly.
|
||||
// As we are using the standard AWS client, this should already be compliant.
|
||||
// Hence, ifever AWS decides to raise this limit, we will automatically reduce the pressure on rate limits
|
||||
route53PageSize = "300"
|
||||
// Hence, if AWS ever decides to raise this limit, we will automatically reduce the pressure on rate limits
|
||||
route53PageSize int32 = 300
|
||||
// providerSpecificAlias specifies whether a CNAME endpoint maps to an AWS ALIAS record.
|
||||
providerSpecificAlias = "alias"
|
||||
providerSpecificTargetHostedZone = "aws/target-hosted-zone"
|
||||
@ -199,16 +199,16 @@ var canonicalHostedZones = map[string]string{
|
||||
// Route53API is the subset of the AWS Route53 API that we actually use. Add methods as required. Signatures must match exactly.
|
||||
// mostly taken from: https://github.com/kubernetes/kubernetes/blob/853167624edb6bc0cfdcdfb88e746e178f5db36c/federation/pkg/dnsprovider/providers/aws/route53/stubs/route53api.go
|
||||
type Route53API interface {
|
||||
ListResourceRecordSetsPagesWithContext(ctx context.Context, input *route53.ListResourceRecordSetsInput, fn func(resp *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool), opts ...request.Option) error
|
||||
ChangeResourceRecordSetsWithContext(ctx context.Context, input *route53.ChangeResourceRecordSetsInput, opts ...request.Option) (*route53.ChangeResourceRecordSetsOutput, error)
|
||||
CreateHostedZoneWithContext(ctx context.Context, input *route53.CreateHostedZoneInput, opts ...request.Option) (*route53.CreateHostedZoneOutput, error)
|
||||
ListHostedZonesPagesWithContext(ctx context.Context, input *route53.ListHostedZonesInput, fn func(resp *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool), opts ...request.Option) error
|
||||
ListTagsForResourceWithContext(ctx context.Context, input *route53.ListTagsForResourceInput, opts ...request.Option) (*route53.ListTagsForResourceOutput, error)
|
||||
ListResourceRecordSets(ctx context.Context, input *route53.ListResourceRecordSetsInput, optFns ...func(options *route53.Options)) (*route53.ListResourceRecordSetsOutput, error)
|
||||
ChangeResourceRecordSets(ctx context.Context, input *route53.ChangeResourceRecordSetsInput, optFns ...func(options *route53.Options)) (*route53.ChangeResourceRecordSetsOutput, error)
|
||||
CreateHostedZone(ctx context.Context, input *route53.CreateHostedZoneInput, optFns ...func(*route53.Options)) (*route53.CreateHostedZoneOutput, error)
|
||||
ListHostedZones(ctx context.Context, input *route53.ListHostedZonesInput, optFns ...func(options *route53.Options)) (*route53.ListHostedZonesOutput, error)
|
||||
ListTagsForResource(ctx context.Context, input *route53.ListTagsForResourceInput, optFns ...func(options *route53.Options)) (*route53.ListTagsForResourceOutput, error)
|
||||
}
|
||||
|
||||
// wrapper to handle ownership relation throughout the provider implementation
|
||||
type Route53Change struct {
|
||||
route53.Change
|
||||
route53types.Change
|
||||
OwnedRecord string
|
||||
sizeBytes int
|
||||
sizeValues int
|
||||
@ -218,13 +218,13 @@ type Route53Changes []*Route53Change
|
||||
|
||||
type profiledZone struct {
|
||||
profile string
|
||||
zone *route53.HostedZone
|
||||
zone *route53types.HostedZone
|
||||
}
|
||||
|
||||
func (cs Route53Changes) Route53Changes() []*route53.Change {
|
||||
ret := []*route53.Change{}
|
||||
func (cs Route53Changes) Route53Changes() []route53types.Change {
|
||||
ret := []route53types.Change{}
|
||||
for _, c := range cs {
|
||||
ret = append(ret, &c.Change)
|
||||
ret = append(ret, c.Change)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@ -253,7 +253,7 @@ type AWSProvider struct {
|
||||
zoneTypeFilter provider.ZoneTypeFilter
|
||||
// filter hosted zones by tags
|
||||
zoneTagFilter provider.ZoneTagFilter
|
||||
// extend filter for sub-domains in the zone (e.g. first.us-east-1.example.com)
|
||||
// extend filter for subdomains in the zone (e.g. first.us-east-1.example.com)
|
||||
zoneMatchParent bool
|
||||
preferCNAME bool
|
||||
zonesCache *zonesListCache
|
||||
@ -302,13 +302,13 @@ func NewAWSProvider(awsConfig AWSConfig, clients map[string]Route53API) (*AWSPro
|
||||
}
|
||||
|
||||
// Zones returns the list of hosted zones.
|
||||
func (p *AWSProvider) Zones(ctx context.Context) (map[string]*route53.HostedZone, error) {
|
||||
func (p *AWSProvider) Zones(ctx context.Context) (map[string]*route53types.HostedZone, error) {
|
||||
zones, err := p.zones(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]*route53.HostedZone, len(zones))
|
||||
result := make(map[string]*route53types.HostedZone, len(zones))
|
||||
for id, zone := range zones {
|
||||
result[id] = zone.zone
|
||||
}
|
||||
@ -324,61 +324,57 @@ func (p *AWSProvider) zones(ctx context.Context) (map[string]*profiledZone, erro
|
||||
log.Debug("Refreshing zones list cache")
|
||||
|
||||
zones := make(map[string]*profiledZone)
|
||||
var profile string
|
||||
var tagErr error
|
||||
f := func(resp *route53.ListHostedZonesOutput, lastPage bool) (shouldContinue bool) {
|
||||
for _, zone := range resp.HostedZones {
|
||||
if !p.zoneIDFilter.Match(aws.StringValue(zone.Id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !p.zoneTypeFilter.Match(zone) {
|
||||
continue
|
||||
}
|
||||
for profile, client := range p.clients {
|
||||
var tagErr error
|
||||
paginator := route53.NewListHostedZonesPaginator(client, &route53.ListHostedZonesInput{})
|
||||
|
||||
if !p.domainFilter.Match(aws.StringValue(zone.Name)) {
|
||||
if !p.zoneMatchParent {
|
||||
continue
|
||||
}
|
||||
if !p.domainFilter.MatchParent(aws.StringValue(zone.Name)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Only fetch tags if a tag filter was specified
|
||||
if !p.zoneTagFilter.IsEmpty() {
|
||||
tags, err := p.tagsForZone(ctx, *zone.Id, profile)
|
||||
if err != nil {
|
||||
tagErr = err
|
||||
return false
|
||||
}
|
||||
if !p.zoneTagFilter.Match(tags) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
zones[aws.StringValue(zone.Id)] = &profiledZone{
|
||||
profile: profile,
|
||||
zone: zone,
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
for p, client := range p.clients {
|
||||
profile = p
|
||||
err := client.ListHostedZonesPagesWithContext(ctx, &route53.ListHostedZonesInput{}, f)
|
||||
if err != nil {
|
||||
var awsErr awserr.Error
|
||||
if errors.As(err, &awsErr) {
|
||||
if awsErr.Code() == route53.ErrCodeThrottlingException {
|
||||
log.Warnf("Skipping AWS profile %q due to provider side throttling: %v", profile, awsErr.Message())
|
||||
for paginator.HasMorePages() {
|
||||
resp, err := paginator.NextPage(ctx)
|
||||
if err != nil {
|
||||
var te *route53types.ThrottlingException
|
||||
if errors.As(err, &te) {
|
||||
log.Infof("Skipping AWS profile %q due to provider side throttling: %v", profile, te.ErrorMessage())
|
||||
continue
|
||||
}
|
||||
// nothing to do here. Falling through to general error handling
|
||||
return nil, provider.NewSoftError(fmt.Errorf("failed to list hosted zones: %w", err))
|
||||
}
|
||||
for _, zone := range resp.HostedZones {
|
||||
if !p.zoneIDFilter.Match(*zone.Id) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !p.zoneTypeFilter.Match(zone) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !p.domainFilter.Match(*zone.Name) {
|
||||
if !p.zoneMatchParent {
|
||||
continue
|
||||
}
|
||||
if !p.domainFilter.MatchParent(*zone.Name) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Only fetch tags if a tag filter was specified
|
||||
if !p.zoneTagFilter.IsEmpty() {
|
||||
tags, err := p.tagsForZone(ctx, *zone.Id, profile)
|
||||
if err != nil {
|
||||
tagErr = err
|
||||
break
|
||||
}
|
||||
if !p.zoneTagFilter.Match(tags) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
zones[*zone.Id] = &profiledZone{
|
||||
profile: profile,
|
||||
zone: &zone,
|
||||
}
|
||||
}
|
||||
return nil, provider.NewSoftError(fmt.Errorf("failed to list hosted zones: %w", err))
|
||||
}
|
||||
if tagErr != nil {
|
||||
return nil, provider.NewSoftError(fmt.Errorf("failed to list zones tags: %w", tagErr))
|
||||
@ -386,7 +382,7 @@ func (p *AWSProvider) zones(ctx context.Context) (map[string]*profiledZone, erro
|
||||
}
|
||||
|
||||
for _, zone := range zones {
|
||||
log.Debugf("Considering zone: %s (domain: %s)", aws.StringValue(zone.zone.Id), aws.StringValue(zone.zone.Name))
|
||||
log.Debugf("Considering zone: %s (domain: %s)", *zone.zone.Id, *zone.zone.Name)
|
||||
}
|
||||
|
||||
if p.zonesCache.duration > time.Duration(0) {
|
||||
@ -403,6 +399,28 @@ func wildcardUnescape(s string) string {
|
||||
return strings.Replace(s, "\\052", "*", 1)
|
||||
}
|
||||
|
||||
// See https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DomainNameFormat.html
|
||||
// convertOctalToAscii decodes inputs that contain octal escape sequences into their original ASCII characters.
|
||||
// The function returns converted string where any octal escape sequences have been replaced with their corresponding ASCII characters.
|
||||
func convertOctalToAscii(input string) string {
|
||||
if !containsOctalSequence(input) {
|
||||
return input
|
||||
}
|
||||
result, err := strconv.Unquote("\"" + input + "\"")
|
||||
if err != nil {
|
||||
return input
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// validateDomainName checks if the domain name contains valid octal escape sequences.
|
||||
func containsOctalSequence(domain string) bool {
|
||||
// Pattern to match valid octal escape sequences
|
||||
octalEscapePattern := `\\[0-3][0-7]{2}`
|
||||
octalEscapeRegex := regexp.MustCompile(octalEscapePattern)
|
||||
return octalEscapeRegex.MatchString(domain)
|
||||
}
|
||||
|
||||
// Records returns the list of records in a given hosted zone.
|
||||
func (p *AWSProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, _ error) {
|
||||
zones, err := p.zones(ctx)
|
||||
@ -415,92 +433,95 @@ func (p *AWSProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoi
|
||||
|
||||
func (p *AWSProvider) records(ctx context.Context, zones map[string]*profiledZone) ([]*endpoint.Endpoint, error) {
|
||||
endpoints := make([]*endpoint.Endpoint, 0)
|
||||
f := func(resp *route53.ListResourceRecordSetsOutput, lastPage bool) (shouldContinue bool) {
|
||||
for _, r := range resp.ResourceRecordSets {
|
||||
newEndpoints := make([]*endpoint.Endpoint, 0)
|
||||
|
||||
if !p.SupportedRecordType(aws.StringValue(r.Type)) {
|
||||
continue
|
||||
}
|
||||
|
||||
var ttl endpoint.TTL
|
||||
if r.TTL != nil {
|
||||
ttl = endpoint.TTL(*r.TTL)
|
||||
}
|
||||
|
||||
if len(r.ResourceRecords) > 0 {
|
||||
targets := make([]string, len(r.ResourceRecords))
|
||||
for idx, rr := range r.ResourceRecords {
|
||||
targets[idx] = aws.StringValue(rr.Value)
|
||||
}
|
||||
|
||||
ep := endpoint.NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), aws.StringValue(r.Type), ttl, targets...)
|
||||
if aws.StringValue(r.Type) == endpoint.RecordTypeCNAME {
|
||||
ep = ep.WithProviderSpecific(providerSpecificAlias, "false")
|
||||
}
|
||||
newEndpoints = append(newEndpoints, ep)
|
||||
}
|
||||
|
||||
if r.AliasTarget != nil {
|
||||
// Alias records don't have TTLs so provide the default to match the TXT generation
|
||||
if ttl == 0 {
|
||||
ttl = recordTTL
|
||||
}
|
||||
ep := endpoint.
|
||||
NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), endpoint.RecordTypeA, ttl, aws.StringValue(r.AliasTarget.DNSName)).
|
||||
WithProviderSpecific(providerSpecificEvaluateTargetHealth, fmt.Sprintf("%t", aws.BoolValue(r.AliasTarget.EvaluateTargetHealth))).
|
||||
WithProviderSpecific(providerSpecificAlias, "true")
|
||||
newEndpoints = append(newEndpoints, ep)
|
||||
}
|
||||
|
||||
for _, ep := range newEndpoints {
|
||||
if r.SetIdentifier != nil {
|
||||
ep.SetIdentifier = aws.StringValue(r.SetIdentifier)
|
||||
switch {
|
||||
case r.Weight != nil:
|
||||
ep.WithProviderSpecific(providerSpecificWeight, fmt.Sprintf("%d", aws.Int64Value(r.Weight)))
|
||||
case r.Region != nil:
|
||||
ep.WithProviderSpecific(providerSpecificRegion, aws.StringValue(r.Region))
|
||||
case r.Failover != nil:
|
||||
ep.WithProviderSpecific(providerSpecificFailover, aws.StringValue(r.Failover))
|
||||
case r.MultiValueAnswer != nil && aws.BoolValue(r.MultiValueAnswer):
|
||||
ep.WithProviderSpecific(providerSpecificMultiValueAnswer, "")
|
||||
case r.GeoLocation != nil:
|
||||
if r.GeoLocation.ContinentCode != nil {
|
||||
ep.WithProviderSpecific(providerSpecificGeolocationContinentCode, aws.StringValue(r.GeoLocation.ContinentCode))
|
||||
} else {
|
||||
if r.GeoLocation.CountryCode != nil {
|
||||
ep.WithProviderSpecific(providerSpecificGeolocationCountryCode, aws.StringValue(r.GeoLocation.CountryCode))
|
||||
}
|
||||
if r.GeoLocation.SubdivisionCode != nil {
|
||||
ep.WithProviderSpecific(providerSpecificGeolocationSubdivisionCode, aws.StringValue(r.GeoLocation.SubdivisionCode))
|
||||
}
|
||||
}
|
||||
default:
|
||||
// one of the above needs to be set, otherwise SetIdentifier doesn't make sense
|
||||
}
|
||||
}
|
||||
|
||||
if r.HealthCheckId != nil {
|
||||
ep.WithProviderSpecific(providerSpecificHealthCheckID, aws.StringValue(r.HealthCheckId))
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
for _, z := range zones {
|
||||
params := &route53.ListResourceRecordSetsInput{
|
||||
HostedZoneId: z.zone.Id,
|
||||
MaxItems: aws.String(route53PageSize),
|
||||
}
|
||||
|
||||
client := p.clients[z.profile]
|
||||
if err := client.ListResourceRecordSetsPagesWithContext(ctx, params, f); err != nil {
|
||||
return nil, fmt.Errorf("failed to list resource records sets for zone %s using aws profile %q: %w", *z.zone.Id, z.profile, err)
|
||||
|
||||
paginator := route53.NewListResourceRecordSetsPaginator(client, &route53.ListResourceRecordSetsInput{
|
||||
HostedZoneId: z.zone.Id,
|
||||
MaxItems: aws.Int32(route53PageSize),
|
||||
})
|
||||
|
||||
for paginator.HasMorePages() {
|
||||
resp, err := paginator.NextPage(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list resource records sets for zone %s using aws profile %q: %w", *z.zone.Id, z.profile, err)
|
||||
}
|
||||
|
||||
for _, r := range resp.ResourceRecordSets {
|
||||
newEndpoints := make([]*endpoint.Endpoint, 0)
|
||||
|
||||
if !p.SupportedRecordType(r.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
name := convertOctalToAscii(wildcardUnescape(*r.Name))
|
||||
|
||||
var ttl endpoint.TTL
|
||||
if r.TTL != nil {
|
||||
ttl = endpoint.TTL(*r.TTL)
|
||||
}
|
||||
|
||||
if len(r.ResourceRecords) > 0 {
|
||||
targets := make([]string, len(r.ResourceRecords))
|
||||
for idx, rr := range r.ResourceRecords {
|
||||
targets[idx] = *rr.Value
|
||||
}
|
||||
|
||||
ep := endpoint.NewEndpointWithTTL(name, string(r.Type), ttl, targets...)
|
||||
if r.Type == endpoint.RecordTypeCNAME {
|
||||
ep = ep.WithProviderSpecific(providerSpecificAlias, "false")
|
||||
}
|
||||
newEndpoints = append(newEndpoints, ep)
|
||||
}
|
||||
|
||||
if r.AliasTarget != nil {
|
||||
// Alias records don't have TTLs so provide the default to match the TXT generation
|
||||
if ttl == 0 {
|
||||
ttl = recordTTL
|
||||
}
|
||||
ep := endpoint.
|
||||
NewEndpointWithTTL(name, endpoint.RecordTypeA, ttl, *r.AliasTarget.DNSName).
|
||||
WithProviderSpecific(providerSpecificEvaluateTargetHealth, fmt.Sprintf("%t", r.AliasTarget.EvaluateTargetHealth)).
|
||||
WithProviderSpecific(providerSpecificAlias, "true")
|
||||
newEndpoints = append(newEndpoints, ep)
|
||||
}
|
||||
|
||||
for _, ep := range newEndpoints {
|
||||
if r.SetIdentifier != nil {
|
||||
ep.SetIdentifier = *r.SetIdentifier
|
||||
switch {
|
||||
case r.Weight != nil:
|
||||
ep.WithProviderSpecific(providerSpecificWeight, fmt.Sprintf("%d", *r.Weight))
|
||||
case r.Region != "":
|
||||
ep.WithProviderSpecific(providerSpecificRegion, string(r.Region))
|
||||
case r.Failover != "":
|
||||
ep.WithProviderSpecific(providerSpecificFailover, string(r.Failover))
|
||||
case r.MultiValueAnswer != nil && *r.MultiValueAnswer:
|
||||
ep.WithProviderSpecific(providerSpecificMultiValueAnswer, "")
|
||||
case r.GeoLocation != nil:
|
||||
if r.GeoLocation.ContinentCode != nil {
|
||||
ep.WithProviderSpecific(providerSpecificGeolocationContinentCode, *r.GeoLocation.ContinentCode)
|
||||
} else {
|
||||
if r.GeoLocation.CountryCode != nil {
|
||||
ep.WithProviderSpecific(providerSpecificGeolocationCountryCode, *r.GeoLocation.CountryCode)
|
||||
}
|
||||
if r.GeoLocation.SubdivisionCode != nil {
|
||||
ep.WithProviderSpecific(providerSpecificGeolocationSubdivisionCode, *r.GeoLocation.SubdivisionCode)
|
||||
}
|
||||
}
|
||||
default:
|
||||
// one of the above needs to be set, otherwise SetIdentifier doesn't make sense
|
||||
}
|
||||
}
|
||||
|
||||
if r.HealthCheckId != nil {
|
||||
ep.WithProviderSpecific(providerSpecificHealthCheckID, *r.HealthCheckId)
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -560,9 +581,9 @@ func (p *AWSProvider) createUpdateChanges(newEndpoints, oldEndpoints []*endpoint
|
||||
}
|
||||
|
||||
combined := make(Route53Changes, 0, len(deletes)+len(creates)+len(updates))
|
||||
combined = append(combined, p.newChanges(route53.ChangeActionCreate, creates)...)
|
||||
combined = append(combined, p.newChanges(route53.ChangeActionUpsert, updates)...)
|
||||
combined = append(combined, p.newChanges(route53.ChangeActionDelete, deletes)...)
|
||||
combined = append(combined, p.newChanges(route53types.ChangeActionCreate, creates)...)
|
||||
combined = append(combined, p.newChanges(route53types.ChangeActionUpsert, updates)...)
|
||||
combined = append(combined, p.newChanges(route53types.ChangeActionDelete, deletes)...)
|
||||
return combined
|
||||
}
|
||||
|
||||
@ -575,7 +596,7 @@ func (p *AWSProvider) GetDomainFilter() endpoint.DomainFilterInterface {
|
||||
}
|
||||
zoneNames := []string(nil)
|
||||
for _, z := range zones {
|
||||
zoneNames = append(zoneNames, aws.StringValue(z.Name), "."+aws.StringValue(z.Name))
|
||||
zoneNames = append(zoneNames, *z.Name, "."+*z.Name)
|
||||
}
|
||||
log.Infof("Applying provider record filter for domains: %v", zoneNames)
|
||||
return endpoint.NewDomainFilter(zoneNames)
|
||||
@ -591,8 +612,8 @@ func (p *AWSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) e
|
||||
updateChanges := p.createUpdateChanges(changes.UpdateNew, changes.UpdateOld)
|
||||
|
||||
combinedChanges := make(Route53Changes, 0, len(changes.Delete)+len(changes.Create)+len(updateChanges))
|
||||
combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionCreate, changes.Create)...)
|
||||
combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionDelete, changes.Delete)...)
|
||||
combinedChanges = append(combinedChanges, p.newChanges(route53types.ChangeActionCreate, changes.Create)...)
|
||||
combinedChanges = append(combinedChanges, p.newChanges(route53types.ChangeActionDelete, changes.Delete)...)
|
||||
combinedChanges = append(combinedChanges, updateChanges...)
|
||||
|
||||
return p.submitChanges(ctx, combinedChanges, zones)
|
||||
@ -615,7 +636,7 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes Route53Changes,
|
||||
var failedZones []string
|
||||
for z, cs := range changesByZone {
|
||||
log := log.WithFields(log.Fields{
|
||||
"zoneName": aws.StringValue(zones[z].zone.Name),
|
||||
"zoneName": *zones[z].zone.Name,
|
||||
"zoneID": z,
|
||||
"profile": zones[z].profile,
|
||||
})
|
||||
@ -634,13 +655,13 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes Route53Changes,
|
||||
}
|
||||
|
||||
for _, c := range b {
|
||||
log.Infof("Desired change: %s %s %s", *c.Action, *c.ResourceRecordSet.Name, *c.ResourceRecordSet.Type)
|
||||
log.Infof("Desired change: %s %s %s", c.Action, *c.ResourceRecordSet.Name, c.ResourceRecordSet.Type)
|
||||
}
|
||||
|
||||
if !p.dryRun {
|
||||
params := &route53.ChangeResourceRecordSetsInput{
|
||||
HostedZoneId: aws.String(z),
|
||||
ChangeBatch: &route53.ChangeBatch{
|
||||
ChangeBatch: &route53types.ChangeBatch{
|
||||
Changes: b.Route53Changes(),
|
||||
},
|
||||
}
|
||||
@ -648,8 +669,8 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes Route53Changes,
|
||||
successfulChanges := 0
|
||||
|
||||
client := p.clients[zones[z].profile]
|
||||
if _, err := client.ChangeResourceRecordSetsWithContext(ctx, params); err != nil {
|
||||
log.Errorf("Failure in zone %s when submitting change batch: %v", aws.StringValue(zones[z].zone.Name), err)
|
||||
if _, err := client.ChangeResourceRecordSets(ctx, params); err != nil {
|
||||
log.Errorf("Failure in zone %s when submitting change batch: %v", *zones[z].zone.Name, err)
|
||||
|
||||
changesByOwnership := groupChangesByNameAndOwnershipRelation(b)
|
||||
|
||||
@ -658,12 +679,12 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes Route53Changes,
|
||||
|
||||
for _, changes := range changesByOwnership {
|
||||
for _, c := range changes {
|
||||
log.Debugf("Desired change: %s %s %s", *c.Action, *c.ResourceRecordSet.Name, *c.ResourceRecordSet.Type)
|
||||
log.Debugf("Desired change: %s %s %s", c.Action, *c.ResourceRecordSet.Name, c.ResourceRecordSet.Type)
|
||||
}
|
||||
params.ChangeBatch = &route53.ChangeBatch{
|
||||
params.ChangeBatch = &route53types.ChangeBatch{
|
||||
Changes: changes.Route53Changes(),
|
||||
}
|
||||
if _, err := client.ChangeResourceRecordSetsWithContext(ctx, params); err != nil {
|
||||
if _, err := client.ChangeResourceRecordSets(ctx, params); err != nil {
|
||||
failedUpdate = true
|
||||
log.Errorf("Failed submitting change (error: %v), it will be retried in a separate change batch in the next iteration", err)
|
||||
p.failedChangesQueue[z] = append(p.failedChangesQueue[z], changes...)
|
||||
@ -702,7 +723,7 @@ func (p *AWSProvider) submitChanges(ctx context.Context, changes Route53Changes,
|
||||
}
|
||||
|
||||
// newChanges returns a collection of Changes based on the given records and action.
|
||||
func (p *AWSProvider) newChanges(action string, endpoints []*endpoint.Endpoint) Route53Changes {
|
||||
func (p *AWSProvider) newChanges(action route53types.ChangeAction, endpoints []*endpoint.Endpoint) Route53Changes {
|
||||
changes := make(Route53Changes, 0, len(endpoints))
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
@ -711,8 +732,10 @@ func (p *AWSProvider) newChanges(action string, endpoints []*endpoint.Endpoint)
|
||||
if dualstack {
|
||||
// make a copy of change, modify RRS type to AAAA, then add new change
|
||||
rrs := *change.ResourceRecordSet
|
||||
change2 := &Route53Change{Change: route53.Change{Action: change.Action, ResourceRecordSet: &rrs}}
|
||||
change2.ResourceRecordSet.Type = aws.String(route53.RRTypeAaaa)
|
||||
change2 := &Route53Change{
|
||||
Change: route53types.Change{Action: change.Action, ResourceRecordSet: &rrs},
|
||||
}
|
||||
change2.ResourceRecordSet.Type = route53types.RRTypeAaaa
|
||||
changes = append(changes, change2)
|
||||
}
|
||||
}
|
||||
@ -774,11 +797,11 @@ func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoi
|
||||
// returned Change is based on the given record by the given action, e.g.
|
||||
// action=ChangeActionCreate returns a change for creation of the record and
|
||||
// action=ChangeActionDelete returns a change for deletion of the record.
|
||||
func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53Change, bool) {
|
||||
func (p *AWSProvider) newChange(action route53types.ChangeAction, ep *endpoint.Endpoint) (*Route53Change, bool) {
|
||||
change := &Route53Change{
|
||||
Change: route53.Change{
|
||||
Action: aws.String(action),
|
||||
ResourceRecordSet: &route53.ResourceRecordSet{
|
||||
Change: route53types.Change{
|
||||
Action: action,
|
||||
ResourceRecordSet: &route53types.ResourceRecordSet{
|
||||
Name: aws.String(ep.DNSName),
|
||||
},
|
||||
},
|
||||
@ -793,24 +816,24 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
|
||||
if val, ok := ep.Labels[endpoint.DualstackLabelKey]; ok {
|
||||
dualstack = val == "true"
|
||||
}
|
||||
change.ResourceRecordSet.Type = aws.String(route53.RRTypeA)
|
||||
change.ResourceRecordSet.AliasTarget = &route53.AliasTarget{
|
||||
change.ResourceRecordSet.Type = route53types.RRTypeA
|
||||
change.ResourceRecordSet.AliasTarget = &route53types.AliasTarget{
|
||||
DNSName: aws.String(ep.Targets[0]),
|
||||
HostedZoneId: aws.String(cleanZoneID(targetHostedZone)),
|
||||
EvaluateTargetHealth: aws.Bool(evalTargetHealth),
|
||||
EvaluateTargetHealth: evalTargetHealth,
|
||||
}
|
||||
change.sizeBytes += len([]byte(ep.Targets[0]))
|
||||
change.sizeValues += 1
|
||||
} else {
|
||||
change.ResourceRecordSet.Type = aws.String(ep.RecordType)
|
||||
change.ResourceRecordSet.Type = route53types.RRType(ep.RecordType)
|
||||
if !ep.RecordTTL.IsConfigured() {
|
||||
change.ResourceRecordSet.TTL = aws.Int64(recordTTL)
|
||||
} else {
|
||||
change.ResourceRecordSet.TTL = aws.Int64(int64(ep.RecordTTL))
|
||||
}
|
||||
change.ResourceRecordSet.ResourceRecords = make([]*route53.ResourceRecord, len(ep.Targets))
|
||||
change.ResourceRecordSet.ResourceRecords = make([]route53types.ResourceRecord, len(ep.Targets))
|
||||
for idx, val := range ep.Targets {
|
||||
change.ResourceRecordSet.ResourceRecords[idx] = &route53.ResourceRecord{
|
||||
change.ResourceRecordSet.ResourceRecords[idx] = route53types.ResourceRecord{
|
||||
Value: aws.String(val),
|
||||
}
|
||||
change.sizeBytes += len([]byte(val))
|
||||
@ -818,7 +841,7 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
|
||||
}
|
||||
}
|
||||
|
||||
if action == route53.ChangeActionUpsert {
|
||||
if action == route53types.ChangeActionUpsert {
|
||||
// If the value of the Action element is UPSERT, each ResourceRecord element and each character in a Value
|
||||
// element is counted twice
|
||||
change.sizeBytes *= 2
|
||||
@ -837,16 +860,16 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
|
||||
change.ResourceRecordSet.Weight = aws.Int64(weight)
|
||||
}
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificRegion); ok {
|
||||
change.ResourceRecordSet.Region = aws.String(prop)
|
||||
change.ResourceRecordSet.Region = route53types.ResourceRecordSetRegion(prop)
|
||||
}
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificFailover); ok {
|
||||
change.ResourceRecordSet.Failover = aws.String(prop)
|
||||
change.ResourceRecordSet.Failover = route53types.ResourceRecordSetFailover(prop)
|
||||
}
|
||||
if _, ok := ep.GetProviderSpecificProperty(providerSpecificMultiValueAnswer); ok {
|
||||
change.ResourceRecordSet.MultiValueAnswer = aws.Bool(true)
|
||||
}
|
||||
|
||||
geolocation := &route53.GeoLocation{}
|
||||
geolocation := &route53types.GeoLocation{}
|
||||
useGeolocation := false
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGeolocationContinentCode); ok {
|
||||
geolocation.ContinentCode = aws.String(prop)
|
||||
@ -908,7 +931,7 @@ func groupChangesByNameAndOwnershipRelation(cs Route53Changes) map[string]Route5
|
||||
for _, v := range cs {
|
||||
key := v.OwnedRecord
|
||||
if key == "" {
|
||||
key = aws.StringValue(v.ResourceRecordSet.Name)
|
||||
key = *v.ResourceRecordSet.Name
|
||||
}
|
||||
changesByOwnership[key] = append(changesByOwnership[key], v)
|
||||
}
|
||||
@ -918,8 +941,8 @@ func groupChangesByNameAndOwnershipRelation(cs Route53Changes) map[string]Route5
|
||||
func (p *AWSProvider) tagsForZone(ctx context.Context, zoneID string, profile string) (map[string]string, error) {
|
||||
client := p.clients[profile]
|
||||
|
||||
response, err := client.ListTagsForResourceWithContext(ctx, &route53.ListTagsForResourceInput{
|
||||
ResourceType: aws.String("hostedzone"),
|
||||
response, err := client.ListTagsForResource(ctx, &route53.ListTagsForResourceInput{
|
||||
ResourceType: route53types.TagResourceTypeHostedzone,
|
||||
ResourceId: aws.String(zoneID),
|
||||
})
|
||||
if err != nil {
|
||||
@ -1006,10 +1029,10 @@ func batchChangeSet(cs Route53Changes, batchSize int, batchSizeBytes int, batchS
|
||||
|
||||
func sortChangesByActionNameType(cs Route53Changes) Route53Changes {
|
||||
sort.SliceStable(cs, func(i, j int) bool {
|
||||
if *cs[i].Action > *cs[j].Action {
|
||||
if cs[i].Action > cs[j].Action {
|
||||
return true
|
||||
}
|
||||
if *cs[i].Action < *cs[j].Action {
|
||||
if cs[i].Action < cs[j].Action {
|
||||
return false
|
||||
}
|
||||
if *cs[i].ResourceRecordSet.Name < *cs[j].ResourceRecordSet.Name {
|
||||
@ -1018,7 +1041,7 @@ func sortChangesByActionNameType(cs Route53Changes) Route53Changes {
|
||||
if *cs[i].ResourceRecordSet.Name > *cs[j].ResourceRecordSet.Name {
|
||||
return false
|
||||
}
|
||||
return *cs[i].ResourceRecordSet.Type < *cs[j].ResourceRecordSet.Type
|
||||
return cs[i].ResourceRecordSet.Type < cs[j].ResourceRecordSet.Type
|
||||
})
|
||||
|
||||
return cs
|
||||
@ -1029,34 +1052,34 @@ func changesByZone(zones map[string]*profiledZone, changeSet Route53Changes) map
|
||||
changes := make(map[string]Route53Changes)
|
||||
|
||||
for _, z := range zones {
|
||||
changes[aws.StringValue(z.zone.Id)] = Route53Changes{}
|
||||
changes[*z.zone.Id] = Route53Changes{}
|
||||
}
|
||||
|
||||
for _, c := range changeSet {
|
||||
hostname := provider.EnsureTrailingDot(aws.StringValue(c.ResourceRecordSet.Name))
|
||||
hostname := provider.EnsureTrailingDot(*c.ResourceRecordSet.Name)
|
||||
|
||||
zones := suitableZones(hostname, zones)
|
||||
if len(zones) == 0 {
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", c.String())
|
||||
log.Debugf("Skipping record %s because no hosted zone matching record DNS Name was detected", *c.ResourceRecordSet.Name)
|
||||
continue
|
||||
}
|
||||
for _, z := range zones {
|
||||
if c.ResourceRecordSet.AliasTarget != nil && aws.StringValue(c.ResourceRecordSet.AliasTarget.HostedZoneId) == sameZoneAlias {
|
||||
if c.ResourceRecordSet.AliasTarget != nil && *c.ResourceRecordSet.AliasTarget.HostedZoneId == sameZoneAlias {
|
||||
// alias record is to be created; target needs to be in the same zone as endpoint
|
||||
// if it's not, this will fail
|
||||
rrset := *c.ResourceRecordSet
|
||||
aliasTarget := *rrset.AliasTarget
|
||||
aliasTarget.HostedZoneId = aws.String(cleanZoneID(aws.StringValue(z.zone.Id)))
|
||||
aliasTarget.HostedZoneId = aws.String(cleanZoneID(*z.zone.Id))
|
||||
rrset.AliasTarget = &aliasTarget
|
||||
c = &Route53Change{
|
||||
Change: route53.Change{
|
||||
Change: route53types.Change{
|
||||
Action: c.Action,
|
||||
ResourceRecordSet: &rrset,
|
||||
},
|
||||
}
|
||||
}
|
||||
changes[aws.StringValue(z.zone.Id)] = append(changes[aws.StringValue(z.zone.Id)], c)
|
||||
log.Debugf("Adding %s to zone %s [Id: %s]", hostname, aws.StringValue(z.zone.Name), aws.StringValue(z.zone.Id))
|
||||
changes[*z.zone.Id] = append(changes[*z.zone.Id], c)
|
||||
log.Debugf("Adding %s to zone %s [Id: %s]", hostname, *z.zone.Name, *z.zone.Id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1078,10 +1101,10 @@ func suitableZones(hostname string, zones map[string]*profiledZone) []*profiledZ
|
||||
var publicZone *profiledZone
|
||||
|
||||
for _, z := range zones {
|
||||
if aws.StringValue(z.zone.Name) == hostname || strings.HasSuffix(hostname, "."+aws.StringValue(z.zone.Name)) {
|
||||
if z.zone.Config == nil || !aws.BoolValue(z.zone.Config.PrivateZone) {
|
||||
if *z.zone.Name == hostname || strings.HasSuffix(hostname, "."+*z.zone.Name) {
|
||||
if z.zone.Config == nil || !z.zone.Config.PrivateZone {
|
||||
// Only select the best matching public zone
|
||||
if publicZone == nil || len(aws.StringValue(z.zone.Name)) > len(aws.StringValue(publicZone.zone.Name)) {
|
||||
if publicZone == nil || len(*z.zone.Name) > len(*publicZone.zone.Name) {
|
||||
publicZone = z
|
||||
}
|
||||
} else {
|
||||
@ -1156,11 +1179,11 @@ func cleanZoneID(id string) string {
|
||||
return strings.TrimPrefix(id, "/hostedzone/")
|
||||
}
|
||||
|
||||
func (p *AWSProvider) SupportedRecordType(recordType string) bool {
|
||||
func (p *AWSProvider) SupportedRecordType(recordType route53types.RRType) bool {
|
||||
switch recordType {
|
||||
case "MX":
|
||||
case route53types.RRTypeMx:
|
||||
return true
|
||||
default:
|
||||
return provider.SupportedRecordType(recordType)
|
||||
return provider.SupportedRecordType(string(recordType))
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -17,13 +17,16 @@ limitations under the License.
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
awsv2 "github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
stscredsv2 "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
|
||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||
"github.com/linki/instrumented_http"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
@ -38,8 +41,8 @@ type AWSSessionConfig struct {
|
||||
Profile string
|
||||
}
|
||||
|
||||
func CreateDefaultSession(cfg *externaldns.Config) *session.Session {
|
||||
result, err := newSession(
|
||||
func CreateDefaultV2Config(cfg *externaldns.Config) awsv2.Config {
|
||||
result, err := newV2Config(
|
||||
AWSSessionConfig{
|
||||
AssumeRole: cfg.AWSAssumeRole,
|
||||
AssumeRoleExternalID: cfg.AWSAssumeRoleExternalID,
|
||||
@ -52,24 +55,14 @@ func CreateDefaultSession(cfg *externaldns.Config) *session.Session {
|
||||
return result
|
||||
}
|
||||
|
||||
func CreateSessions(cfg *externaldns.Config) map[string]*session.Session {
|
||||
result := make(map[string]*session.Session)
|
||||
|
||||
func CreateV2Configs(cfg *externaldns.Config) map[string]awsv2.Config {
|
||||
result := make(map[string]awsv2.Config)
|
||||
if len(cfg.AWSProfiles) == 0 || (len(cfg.AWSProfiles) == 1 && cfg.AWSProfiles[0] == "") {
|
||||
session, err := newSession(
|
||||
AWSSessionConfig{
|
||||
AssumeRole: cfg.AWSAssumeRole,
|
||||
AssumeRoleExternalID: cfg.AWSAssumeRoleExternalID,
|
||||
APIRetries: cfg.AWSAPIRetries,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
result[defaultAWSProfile] = session
|
||||
cfg := CreateDefaultV2Config(cfg)
|
||||
result[defaultAWSProfile] = cfg
|
||||
} else {
|
||||
for _, profile := range cfg.AWSProfiles {
|
||||
session, err := newSession(
|
||||
cfg, err := newV2Config(
|
||||
AWSSessionConfig{
|
||||
AssumeRole: cfg.AWSAssumeRole,
|
||||
AssumeRoleExternalID: cfg.AWSAssumeRoleExternalID,
|
||||
@ -80,46 +73,48 @@ func CreateSessions(cfg *externaldns.Config) map[string]*session.Session {
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
result[profile] = session
|
||||
result[profile] = cfg
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func newSession(awsConfig AWSSessionConfig) (*session.Session, error) {
|
||||
config := aws.NewConfig().WithMaxRetries(awsConfig.APIRetries)
|
||||
|
||||
config.WithHTTPClient(
|
||||
instrumented_http.NewClient(config.HTTPClient, &instrumented_http.Callbacks{
|
||||
func newV2Config(awsConfig AWSSessionConfig) (awsv2.Config, error) {
|
||||
defaultOpts := []func(*config.LoadOptions) error{
|
||||
config.WithRetryer(func() awsv2.Retryer {
|
||||
return retry.AddWithMaxAttempts(retry.NewStandard(), awsConfig.APIRetries)
|
||||
}),
|
||||
config.WithHTTPClient(instrumented_http.NewClient(&http.Client{}, &instrumented_http.Callbacks{
|
||||
PathProcessor: func(path string) string {
|
||||
parts := strings.Split(path, "/")
|
||||
return parts[len(parts)-1]
|
||||
},
|
||||
}),
|
||||
)
|
||||
})),
|
||||
config.WithSharedConfigProfile(awsConfig.Profile),
|
||||
}
|
||||
|
||||
session, err := session.NewSessionWithOptions(session.Options{
|
||||
Config: *config,
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
Profile: awsConfig.Profile,
|
||||
})
|
||||
cfg, err := config.LoadDefaultConfig(context.Background(), defaultOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("instantiating AWS session: %w", err)
|
||||
return awsv2.Config{}, fmt.Errorf("instantiating AWS config: %w", err)
|
||||
}
|
||||
|
||||
if awsConfig.AssumeRole != "" {
|
||||
stsSvc := sts.NewFromConfig(cfg)
|
||||
var assumeRoleOpts []func(*stscredsv2.AssumeRoleOptions)
|
||||
if awsConfig.AssumeRoleExternalID != "" {
|
||||
logrus.Infof("Assuming role: %s with external id %s", awsConfig.AssumeRole, awsConfig.AssumeRoleExternalID)
|
||||
session.Config.WithCredentials(stscreds.NewCredentials(session, awsConfig.AssumeRole, func(p *stscreds.AssumeRoleProvider) {
|
||||
p.ExternalID = &awsConfig.AssumeRoleExternalID
|
||||
}))
|
||||
logrus.Infof("Assuming role %s with external id", awsConfig.AssumeRole)
|
||||
logrus.Debugf("External id: %s", awsConfig.AssumeRoleExternalID)
|
||||
assumeRoleOpts = []func(*stscredsv2.AssumeRoleOptions){
|
||||
func(opts *stscredsv2.AssumeRoleOptions) {
|
||||
opts.ExternalID = &awsConfig.AssumeRoleExternalID
|
||||
},
|
||||
}
|
||||
} else {
|
||||
logrus.Infof("Assuming role: %s", awsConfig.AssumeRole)
|
||||
session.Config.WithCredentials(stscreds.NewCredentials(session, awsConfig.AssumeRole))
|
||||
}
|
||||
creds := stscredsv2.NewAssumeRoleProvider(stsSvc, awsConfig.AssumeRole, assumeRoleOpts...)
|
||||
cfg.Credentials = awsv2.NewCredentialsCache(creds)
|
||||
}
|
||||
|
||||
session.Handlers.Build.PushBack(request.MakeAddToUserAgentHandler("ExternalDNS", externaldns.Version))
|
||||
|
||||
return session, nil
|
||||
return cfg, nil
|
||||
}
|
||||
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@ -24,7 +25,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_newSession(t *testing.T) {
|
||||
func Test_newV2Config(t *testing.T) {
|
||||
t.Run("should use profile from credentials file", func(t *testing.T) {
|
||||
// setup
|
||||
credsFile, err := prepareCredentialsFile(t)
|
||||
@ -34,9 +35,9 @@ func Test_newSession(t *testing.T) {
|
||||
defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE")
|
||||
|
||||
// when
|
||||
s, err := newSession(AWSSessionConfig{Profile: "profile2"})
|
||||
cfg, err := newV2Config(AWSSessionConfig{Profile: "profile2"})
|
||||
require.NoError(t, err)
|
||||
creds, err := s.Config.Credentials.Get()
|
||||
creds, err := cfg.Credentials.Retrieve(context.Background())
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
@ -52,9 +53,9 @@ func Test_newSession(t *testing.T) {
|
||||
defer os.Unsetenv("AWS_SECRET_ACCESS_KEY")
|
||||
|
||||
// when
|
||||
s, err := newSession(AWSSessionConfig{})
|
||||
cfg, err := newV2Config(AWSSessionConfig{})
|
||||
require.NoError(t, err)
|
||||
creds, err := s.Config.Credentials.Get()
|
||||
creds, err := cfg.Credentials.Retrieve(context.Background())
|
||||
|
||||
// then
|
||||
assert.NoError(t, err)
|
||||
@ -24,9 +24,9 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
sd "github.com/aws/aws-sdk-go/service/servicediscovery"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
sd "github.com/aws/aws-sdk-go-v2/service/servicediscovery"
|
||||
sdtypes "github.com/aws/aws-sdk-go-v2/service/servicediscovery/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
@ -41,6 +41,7 @@ const (
|
||||
sdNamespaceTypePrivate = "private"
|
||||
|
||||
sdInstanceAttrIPV4 = "AWS_INSTANCE_IPV4"
|
||||
sdInstanceAttrIPV6 = "AWS_INSTANCE_IPV6"
|
||||
sdInstanceAttrCname = "AWS_INSTANCE_CNAME"
|
||||
sdInstanceAttrAlias = "AWS_ALIAS_DNS_NAME"
|
||||
)
|
||||
@ -54,16 +55,16 @@ var (
|
||||
)
|
||||
|
||||
// AWSSDClient is the subset of the AWS Cloud Map API that we actually use. Add methods as required.
|
||||
// Signatures must match exactly. Taken from https://github.com/aws/aws-sdk-go/blob/HEAD/service/servicediscovery/api.go
|
||||
// Signatures must match exactly. Taken from https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/servicediscovery
|
||||
type AWSSDClient interface {
|
||||
CreateService(input *sd.CreateServiceInput) (*sd.CreateServiceOutput, error)
|
||||
DeregisterInstance(input *sd.DeregisterInstanceInput) (*sd.DeregisterInstanceOutput, error)
|
||||
DiscoverInstancesWithContext(ctx aws.Context, input *sd.DiscoverInstancesInput, opts ...request.Option) (*sd.DiscoverInstancesOutput, 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)
|
||||
CreateService(ctx context.Context, params *sd.CreateServiceInput, optFns ...func(*sd.Options)) (*sd.CreateServiceOutput, error)
|
||||
DeregisterInstance(ctx context.Context, params *sd.DeregisterInstanceInput, optFns ...func(*sd.Options)) (*sd.DeregisterInstanceOutput, error)
|
||||
DiscoverInstances(ctx context.Context, params *sd.DiscoverInstancesInput, optFns ...func(*sd.Options)) (*sd.DiscoverInstancesOutput, error)
|
||||
ListNamespaces(ctx context.Context, params *sd.ListNamespacesInput, optFns ...func(*sd.Options)) (*sd.ListNamespacesOutput, error)
|
||||
ListServices(ctx context.Context, params *sd.ListServicesInput, optFns ...func(*sd.Options)) (*sd.ListServicesOutput, error)
|
||||
RegisterInstance(ctx context.Context, params *sd.RegisterInstanceInput, optFns ...func(*sd.Options)) (*sd.RegisterInstanceOutput, error)
|
||||
UpdateService(ctx context.Context, params *sd.UpdateServiceInput, optFns ...func(*sd.Options)) (*sd.UpdateServiceOutput, error)
|
||||
DeleteService(ctx context.Context, params *sd.DeleteServiceInput, optFns ...func(*sd.Options)) (*sd.DeleteServiceOutput, error)
|
||||
}
|
||||
|
||||
// AWSSDProvider is an implementation of Provider for AWS Cloud Map.
|
||||
@ -74,60 +75,72 @@ type AWSSDProvider struct {
|
||||
// only consider namespaces ending in this suffix
|
||||
namespaceFilter endpoint.DomainFilter
|
||||
// filter namespace by type (private or public)
|
||||
namespaceTypeFilter *sd.NamespaceFilter
|
||||
namespaceTypeFilter sdtypes.NamespaceFilter
|
||||
// enables service without instances cleanup
|
||||
cleanEmptyService bool
|
||||
// filter services for removal
|
||||
ownerID string
|
||||
// tags to be added to the service
|
||||
tags []sdtypes.Tag
|
||||
}
|
||||
|
||||
// NewAWSSDProvider initializes a new AWS Cloud Map based Provider.
|
||||
func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, dryRun, cleanEmptyService bool, ownerID string, client AWSSDClient) (*AWSSDProvider, error) {
|
||||
provider := &AWSSDProvider{
|
||||
func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, dryRun, cleanEmptyService bool, ownerID string, tags map[string]string, client AWSSDClient) (*AWSSDProvider, error) {
|
||||
p := &AWSSDProvider{
|
||||
client: client,
|
||||
dryRun: dryRun,
|
||||
namespaceFilter: domainFilter,
|
||||
namespaceTypeFilter: newSdNamespaceFilter(namespaceType),
|
||||
cleanEmptyService: cleanEmptyService,
|
||||
ownerID: ownerID,
|
||||
tags: awsTags(tags),
|
||||
}
|
||||
|
||||
return provider, nil
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// newSdNamespaceFilter initialized AWS SD Namespace Filter based on given string config
|
||||
func newSdNamespaceFilter(namespaceTypeConfig string) *sd.NamespaceFilter {
|
||||
func newSdNamespaceFilter(namespaceTypeConfig string) sdtypes.NamespaceFilter {
|
||||
switch namespaceTypeConfig {
|
||||
case sdNamespaceTypePublic:
|
||||
return &sd.NamespaceFilter{
|
||||
Name: aws.String(sd.NamespaceFilterNameType),
|
||||
Values: []*string{aws.String(sd.NamespaceTypeDnsPublic)},
|
||||
return sdtypes.NamespaceFilter{
|
||||
Name: sdtypes.NamespaceFilterNameType,
|
||||
Values: []string{string(sdtypes.NamespaceTypeDnsPublic)},
|
||||
}
|
||||
case sdNamespaceTypePrivate:
|
||||
return &sd.NamespaceFilter{
|
||||
Name: aws.String(sd.NamespaceFilterNameType),
|
||||
Values: []*string{aws.String(sd.NamespaceTypeDnsPrivate)},
|
||||
return sdtypes.NamespaceFilter{
|
||||
Name: sdtypes.NamespaceFilterNameType,
|
||||
Values: []string{string(sdtypes.NamespaceTypeDnsPrivate)},
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
return sdtypes.NamespaceFilter{}
|
||||
}
|
||||
}
|
||||
|
||||
// awsTags converts user supplied tags to AWS format
|
||||
func awsTags(tags map[string]string) []sdtypes.Tag {
|
||||
awsTags := make([]sdtypes.Tag, 0, len(tags))
|
||||
for k, v := range tags {
|
||||
awsTags = append(awsTags, sdtypes.Tag{Key: aws.String(k), Value: aws.String(v)})
|
||||
}
|
||||
return awsTags
|
||||
}
|
||||
|
||||
// Records returns list of all endpoints.
|
||||
func (p *AWSSDProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, err error) {
|
||||
namespaces, err := p.ListNamespaces()
|
||||
namespaces, err := p.ListNamespaces(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, ns := range namespaces {
|
||||
services, err := p.ListServicesByNamespaceID(ns.Id)
|
||||
services, err := p.ListServicesByNamespaceID(ctx, ns.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, srv := range services {
|
||||
resp, err := p.client.DiscoverInstancesWithContext(ctx, &sd.DiscoverInstancesInput{
|
||||
resp, err := p.client.DiscoverInstances(ctx, &sd.DiscoverInstancesInput{
|
||||
NamespaceName: ns.Name,
|
||||
ServiceName: srv.Name,
|
||||
})
|
||||
@ -136,8 +149,8 @@ func (p *AWSSDProvider) Records(ctx context.Context) (endpoints []*endpoint.Endp
|
||||
}
|
||||
|
||||
if len(resp.Instances) == 0 {
|
||||
if err := p.DeleteService(srv); err != nil {
|
||||
log.Errorf("Failed to delete service %q, error: %s", aws.StringValue(srv.Name), err)
|
||||
if err := p.DeleteService(ctx, srv); err != nil {
|
||||
log.Errorf("Failed to delete service %q, error: %s", *srv.Name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -149,35 +162,40 @@ func (p *AWSSDProvider) Records(ctx context.Context) (endpoints []*endpoint.Endp
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (p *AWSSDProvider) instancesToEndpoint(ns *sd.NamespaceSummary, srv *sd.Service, instances []*sd.HttpInstanceSummary) *endpoint.Endpoint {
|
||||
func (p *AWSSDProvider) instancesToEndpoint(ns *sdtypes.NamespaceSummary, srv *sdtypes.Service, instances []sdtypes.HttpInstanceSummary) *endpoint.Endpoint {
|
||||
// DNS name of the record is a concatenation of service and namespace
|
||||
recordName := *srv.Name + "." + *ns.Name
|
||||
|
||||
labels := endpoint.NewLabels()
|
||||
labels[endpoint.AWSSDDescriptionLabel] = aws.StringValue(srv.Description)
|
||||
labels[endpoint.AWSSDDescriptionLabel] = *srv.Description
|
||||
|
||||
newEndpoint := &endpoint.Endpoint{
|
||||
DNSName: recordName,
|
||||
RecordTTL: endpoint.TTL(aws.Int64Value(srv.DnsConfig.DnsRecords[0].TTL)),
|
||||
RecordTTL: endpoint.TTL(*srv.DnsConfig.DnsRecords[0].TTL),
|
||||
Targets: make(endpoint.Targets, 0, len(instances)),
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
for _, inst := range instances {
|
||||
// CNAME
|
||||
if inst.Attributes[sdInstanceAttrCname] != nil && aws.StringValue(srv.DnsConfig.DnsRecords[0].Type) == sd.RecordTypeCname {
|
||||
if inst.Attributes[sdInstanceAttrCname] != "" && srv.DnsConfig.DnsRecords[0].Type == sdtypes.RecordTypeCname {
|
||||
newEndpoint.RecordType = endpoint.RecordTypeCNAME
|
||||
newEndpoint.Targets = append(newEndpoint.Targets, aws.StringValue(inst.Attributes[sdInstanceAttrCname]))
|
||||
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrCname])
|
||||
|
||||
// ALIAS
|
||||
} else if inst.Attributes[sdInstanceAttrAlias] != nil {
|
||||
} else if inst.Attributes[sdInstanceAttrAlias] != "" {
|
||||
newEndpoint.RecordType = endpoint.RecordTypeCNAME
|
||||
newEndpoint.Targets = append(newEndpoint.Targets, aws.StringValue(inst.Attributes[sdInstanceAttrAlias]))
|
||||
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrAlias])
|
||||
|
||||
// IP-based target
|
||||
} else if inst.Attributes[sdInstanceAttrIPV4] != nil {
|
||||
// IPv4-based target
|
||||
} else if inst.Attributes[sdInstanceAttrIPV4] != "" {
|
||||
newEndpoint.RecordType = endpoint.RecordTypeA
|
||||
newEndpoint.Targets = append(newEndpoint.Targets, aws.StringValue(inst.Attributes[sdInstanceAttrIPV4]))
|
||||
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrIPV4])
|
||||
|
||||
// IPv6-based target
|
||||
} else if inst.Attributes[sdInstanceAttrIPV6] != "" {
|
||||
newEndpoint.RecordType = endpoint.RecordTypeAAAA
|
||||
newEndpoint.Targets = append(newEndpoint.Targets, inst.Attributes[sdInstanceAttrIPV6])
|
||||
} else {
|
||||
log.Warnf("Invalid instance \"%v\" found in service \"%v\"", inst, srv.Name)
|
||||
}
|
||||
@ -199,7 +217,7 @@ func (p *AWSSDProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
||||
changes.Delete = append(changes.Delete, deletes...)
|
||||
changes.Create = append(changes.Create, creates...)
|
||||
|
||||
namespaces, err := p.ListNamespaces()
|
||||
namespaces, err := p.ListNamespaces(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -211,12 +229,12 @@ func (p *AWSSDProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
||||
// creates = [1.2.3.4, 1.2.3.5]
|
||||
// ```
|
||||
// then when deletes are executed after creates it will miss the `1.2.3.4` instance.
|
||||
err = p.submitDeletes(namespaces, changes.Delete)
|
||||
err = p.submitDeletes(ctx, namespaces, changes.Delete)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.submitCreates(namespaces, changes.Create)
|
||||
err = p.submitCreates(ctx, namespaces, changes.Create)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -245,11 +263,11 @@ func (p *AWSSDProvider) updatesToCreates(changes *plan.Changes) (creates []*endp
|
||||
return creates, deletes
|
||||
}
|
||||
|
||||
func (p *AWSSDProvider) submitCreates(namespaces []*sd.NamespaceSummary, changes []*endpoint.Endpoint) error {
|
||||
func (p *AWSSDProvider) submitCreates(ctx context.Context, namespaces []*sdtypes.NamespaceSummary, changes []*endpoint.Endpoint) error {
|
||||
changesByNamespaceID := p.changesByNamespaceID(namespaces, changes)
|
||||
|
||||
for nsID, changeList := range changesByNamespaceID {
|
||||
services, err := p.ListServicesByNamespaceID(aws.String(nsID))
|
||||
services, err := p.ListServicesByNamespaceID(ctx, aws.String(nsID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -260,7 +278,7 @@ func (p *AWSSDProvider) submitCreates(namespaces []*sd.NamespaceSummary, changes
|
||||
srv := services[srvName]
|
||||
if srv == nil {
|
||||
// when service is missing create a new one
|
||||
srv, err = p.CreateService(&nsID, &srvName, ch)
|
||||
srv, err = p.CreateService(ctx, &nsID, &srvName, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -268,13 +286,13 @@ func (p *AWSSDProvider) submitCreates(namespaces []*sd.NamespaceSummary, changes
|
||||
services[*srv.Name] = srv
|
||||
} else if ch.RecordTTL.IsConfigured() && *srv.DnsConfig.DnsRecords[0].TTL != int64(ch.RecordTTL) {
|
||||
// update service when TTL differ
|
||||
err = p.UpdateService(srv, ch)
|
||||
err = p.UpdateService(ctx, srv, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = p.RegisterInstance(srv, ch)
|
||||
err = p.RegisterInstance(ctx, srv, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -284,11 +302,11 @@ func (p *AWSSDProvider) submitCreates(namespaces []*sd.NamespaceSummary, changes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *AWSSDProvider) submitDeletes(namespaces []*sd.NamespaceSummary, changes []*endpoint.Endpoint) error {
|
||||
func (p *AWSSDProvider) submitDeletes(ctx context.Context, namespaces []*sdtypes.NamespaceSummary, changes []*endpoint.Endpoint) error {
|
||||
changesByNamespaceID := p.changesByNamespaceID(namespaces, changes)
|
||||
|
||||
for nsID, changeList := range changesByNamespaceID {
|
||||
services, err := p.ListServicesByNamespaceID(aws.String(nsID))
|
||||
services, err := p.ListServicesByNamespaceID(ctx, aws.String(nsID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -302,7 +320,7 @@ func (p *AWSSDProvider) submitDeletes(namespaces []*sd.NamespaceSummary, changes
|
||||
return fmt.Errorf("service \"%s\" is missing when trying to delete \"%v\"", srvName, hostname)
|
||||
}
|
||||
|
||||
err := p.DeregisterInstance(srv, ch)
|
||||
err := p.DeregisterInstance(ctx, srv, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -313,53 +331,51 @@ func (p *AWSSDProvider) submitDeletes(namespaces []*sd.NamespaceSummary, changes
|
||||
}
|
||||
|
||||
// ListNamespaces returns all namespaces matching defined namespace filter
|
||||
func (p *AWSSDProvider) ListNamespaces() ([]*sd.NamespaceSummary, error) {
|
||||
namespaces := make([]*sd.NamespaceSummary, 0)
|
||||
func (p *AWSSDProvider) ListNamespaces(ctx context.Context) ([]*sdtypes.NamespaceSummary, error) {
|
||||
namespaces := make([]*sdtypes.NamespaceSummary, 0)
|
||||
|
||||
f := func(resp *sd.ListNamespacesOutput, lastPage bool) bool {
|
||||
for _, ns := range resp.Namespaces {
|
||||
if !p.namespaceFilter.Match(aws.StringValue(ns.Name)) {
|
||||
continue
|
||||
}
|
||||
namespaces = append(namespaces, ns)
|
||||
paginator := sd.NewListNamespacesPaginator(p.client, &sd.ListNamespacesInput{
|
||||
Filters: []sdtypes.NamespaceFilter{p.namespaceTypeFilter},
|
||||
})
|
||||
for paginator.HasMorePages() {
|
||||
resp, err := paginator.NextPage(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
err := p.client.ListNamespacesPages(&sd.ListNamespacesInput{
|
||||
Filters: []*sd.NamespaceFilter{p.namespaceTypeFilter},
|
||||
}, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for _, ns := range resp.Namespaces {
|
||||
if !p.namespaceFilter.Match(*ns.Name) {
|
||||
continue
|
||||
}
|
||||
namespaces = append(namespaces, &ns)
|
||||
}
|
||||
}
|
||||
|
||||
return namespaces, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
services := make([]*sd.ServiceSummary, 0)
|
||||
// ListServicesByNamespaceID returns list of services in given namespace.
|
||||
func (p *AWSSDProvider) ListServicesByNamespaceID(ctx context.Context, namespaceID *string) (map[string]*sdtypes.Service, error) {
|
||||
services := make([]sdtypes.ServiceSummary, 0)
|
||||
|
||||
f := func(resp *sd.ListServicesOutput, lastPage bool) bool {
|
||||
services = append(services, resp.Services...)
|
||||
return true
|
||||
}
|
||||
|
||||
err := p.client.ListServicesPages(&sd.ListServicesInput{
|
||||
Filters: []*sd.ServiceFilter{{
|
||||
Name: aws.String(sd.ServiceFilterNameNamespaceId),
|
||||
Values: []*string{namespaceID},
|
||||
paginator := sd.NewListServicesPaginator(p.client, &sd.ListServicesInput{
|
||||
Filters: []sdtypes.ServiceFilter{{
|
||||
Name: sdtypes.ServiceFilterNameNamespaceId,
|
||||
Values: []string{*namespaceID},
|
||||
}},
|
||||
MaxResults: aws.Int64(100),
|
||||
}, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
MaxResults: aws.Int32(100),
|
||||
})
|
||||
for paginator.HasMorePages() {
|
||||
resp, err := paginator.NextPage(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services = append(services, resp.Services...)
|
||||
}
|
||||
|
||||
servicesMap := make(map[string]*sd.Service)
|
||||
servicesMap := make(map[string]*sdtypes.Service)
|
||||
for _, serviceSummary := range services {
|
||||
service := &sd.Service{
|
||||
service := &sdtypes.Service{
|
||||
Arn: serviceSummary.Arn,
|
||||
CreateDate: serviceSummary.CreateDate,
|
||||
Description: serviceSummary.Description,
|
||||
@ -373,13 +389,13 @@ func (p *AWSSDProvider) ListServicesByNamespaceID(namespaceID *string) (map[stri
|
||||
Type: serviceSummary.Type,
|
||||
}
|
||||
|
||||
servicesMap[aws.StringValue(service.Name)] = service
|
||||
servicesMap[*service.Name] = service
|
||||
}
|
||||
return servicesMap, nil
|
||||
}
|
||||
|
||||
// CreateService creates a new service in AWS API. Returns the created service.
|
||||
func (p *AWSSDProvider) CreateService(namespaceID *string, srvName *string, ep *endpoint.Endpoint) (*sd.Service, error) {
|
||||
func (p *AWSSDProvider) CreateService(ctx context.Context, namespaceID *string, srvName *string, ep *endpoint.Endpoint) (*sdtypes.Service, error) {
|
||||
log.Infof("Creating a new service \"%s\" in \"%s\" namespace", *srvName, *namespaceID)
|
||||
|
||||
srvType := p.serviceTypeFromEndpoint(ep)
|
||||
@ -391,17 +407,18 @@ func (p *AWSSDProvider) CreateService(namespaceID *string, srvName *string, ep *
|
||||
}
|
||||
|
||||
if !p.dryRun {
|
||||
out, err := p.client.CreateService(&sd.CreateServiceInput{
|
||||
out, err := p.client.CreateService(ctx, &sd.CreateServiceInput{
|
||||
Name: srvName,
|
||||
Description: aws.String(ep.Labels[endpoint.AWSSDDescriptionLabel]),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
RoutingPolicy: aws.String(routingPolicy),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(srvType),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: routingPolicy,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: srvType,
|
||||
TTL: aws.Int64(ttl),
|
||||
}},
|
||||
},
|
||||
NamespaceId: namespaceID,
|
||||
Tags: p.tags,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -411,11 +428,11 @@ func (p *AWSSDProvider) CreateService(namespaceID *string, srvName *string, ep *
|
||||
}
|
||||
|
||||
// return mock service summary in case of dry run
|
||||
return &sd.Service{Id: aws.String("dry-run-service"), Name: aws.String("dry-run-service")}, nil
|
||||
return &sdtypes.Service{Id: aws.String("dry-run-service"), Name: aws.String("dry-run-service")}, nil
|
||||
}
|
||||
|
||||
// UpdateService updates the specified service with information from provided endpoint.
|
||||
func (p *AWSSDProvider) UpdateService(service *sd.Service, ep *endpoint.Endpoint) error {
|
||||
func (p *AWSSDProvider) UpdateService(ctx context.Context, service *sdtypes.Service, ep *endpoint.Endpoint) error {
|
||||
log.Infof("Updating service \"%s\"", *service.Name)
|
||||
|
||||
srvType := p.serviceTypeFromEndpoint(ep)
|
||||
@ -426,13 +443,13 @@ func (p *AWSSDProvider) UpdateService(service *sd.Service, ep *endpoint.Endpoint
|
||||
}
|
||||
|
||||
if !p.dryRun {
|
||||
_, err := p.client.UpdateService(&sd.UpdateServiceInput{
|
||||
_, err := p.client.UpdateService(ctx, &sd.UpdateServiceInput{
|
||||
Id: service.Id,
|
||||
Service: &sd.ServiceChange{
|
||||
Service: &sdtypes.ServiceChange{
|
||||
Description: aws.String(ep.Labels[endpoint.AWSSDDescriptionLabel]),
|
||||
DnsConfig: &sd.DnsConfigChange{
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(srvType),
|
||||
DnsConfig: &sdtypes.DnsConfigChange{
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: srvType,
|
||||
TTL: aws.Int64(ttl),
|
||||
}},
|
||||
},
|
||||
@ -447,7 +464,7 @@ func (p *AWSSDProvider) UpdateService(service *sd.Service, ep *endpoint.Endpoint
|
||||
}
|
||||
|
||||
// DeleteService deletes empty Service from AWS API if its owner id match
|
||||
func (p *AWSSDProvider) DeleteService(service *sd.Service) error {
|
||||
func (p *AWSSDProvider) DeleteService(ctx context.Context, service *sdtypes.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
|
||||
@ -455,39 +472,42 @@ func (p *AWSSDProvider) DeleteService(service *sd.Service) error {
|
||||
label[endpoint.OwnerLabelKey] = p.ownerID
|
||||
label[endpoint.AWSSDDescriptionLabel] = label.SerializePlain(false)
|
||||
|
||||
if strings.HasPrefix(aws.StringValue(service.Description), label[endpoint.AWSSDDescriptionLabel]) {
|
||||
if strings.HasPrefix(*service.Description, label[endpoint.AWSSDDescriptionLabel]) {
|
||||
log.Infof("Deleting service \"%s\"", *service.Name)
|
||||
_, err := p.client.DeleteService(&sd.DeleteServiceInput{
|
||||
_, err := p.client.DeleteService(ctx, &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])
|
||||
log.Debugf("Skipping service removal %s because owner id does not match, found: \"%s\", required: \"%s\"", *service.Name, *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 {
|
||||
func (p *AWSSDProvider) RegisterInstance(ctx context.Context, service *sdtypes.Service, ep *endpoint.Endpoint) error {
|
||||
for _, target := range ep.Targets {
|
||||
log.Infof("Registering a new instance \"%s\" for service \"%s\" (%s)", target, *service.Name, *service.Id)
|
||||
|
||||
attr := make(map[string]*string)
|
||||
attr := make(map[string]string)
|
||||
|
||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
switch ep.RecordType {
|
||||
case endpoint.RecordTypeCNAME:
|
||||
if p.isAWSLoadBalancer(target) {
|
||||
attr[sdInstanceAttrAlias] = aws.String(target)
|
||||
attr[sdInstanceAttrAlias] = target
|
||||
} else {
|
||||
attr[sdInstanceAttrCname] = aws.String(target)
|
||||
attr[sdInstanceAttrCname] = target
|
||||
}
|
||||
} else if ep.RecordType == endpoint.RecordTypeA {
|
||||
attr[sdInstanceAttrIPV4] = aws.String(target)
|
||||
} else {
|
||||
case endpoint.RecordTypeA:
|
||||
attr[sdInstanceAttrIPV4] = target
|
||||
case endpoint.RecordTypeAAAA:
|
||||
attr[sdInstanceAttrIPV6] = target
|
||||
default:
|
||||
return fmt.Errorf("invalid endpoint type (%v)", ep)
|
||||
}
|
||||
|
||||
if !p.dryRun {
|
||||
_, err := p.client.RegisterInstance(&sd.RegisterInstanceInput{
|
||||
_, err := p.client.RegisterInstance(ctx, &sd.RegisterInstanceInput{
|
||||
ServiceId: service.Id,
|
||||
Attributes: attr,
|
||||
InstanceId: aws.String(p.targetToInstanceID(target)),
|
||||
@ -502,12 +522,12 @@ func (p *AWSSDProvider) RegisterInstance(service *sd.Service, ep *endpoint.Endpo
|
||||
}
|
||||
|
||||
// DeregisterInstance removes an instance from given service.
|
||||
func (p *AWSSDProvider) DeregisterInstance(service *sd.Service, ep *endpoint.Endpoint) error {
|
||||
func (p *AWSSDProvider) DeregisterInstance(ctx context.Context, service *sdtypes.Service, ep *endpoint.Endpoint) error {
|
||||
for _, target := range ep.Targets {
|
||||
log.Infof("De-registering an instance \"%s\" for service \"%s\" (%s)", target, *service.Name, *service.Id)
|
||||
|
||||
if !p.dryRun {
|
||||
_, err := p.client.DeregisterInstance(&sd.DeregisterInstanceInput{
|
||||
_, err := p.client.DeregisterInstance(ctx, &sd.DeregisterInstanceInput{
|
||||
InstanceId: aws.String(p.targetToInstanceID(target)),
|
||||
ServiceId: service.Id,
|
||||
})
|
||||
@ -531,43 +551,7 @@ func (p *AWSSDProvider) targetToInstanceID(target string) string {
|
||||
return strings.ToLower(target)
|
||||
}
|
||||
|
||||
// nolint: deadcode
|
||||
// used from unit test
|
||||
func namespaceToNamespaceSummary(namespace *sd.Namespace) *sd.NamespaceSummary {
|
||||
if namespace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &sd.NamespaceSummary{
|
||||
Id: namespace.Id,
|
||||
Type: namespace.Type,
|
||||
Name: namespace.Name,
|
||||
Arn: namespace.Arn,
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: deadcode
|
||||
// used from unit test
|
||||
func serviceToServiceSummary(service *sd.Service) *sd.ServiceSummary {
|
||||
if service == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &sd.ServiceSummary{
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AWSSDProvider) changesByNamespaceID(namespaces []*sd.NamespaceSummary, changes []*endpoint.Endpoint) map[string][]*endpoint.Endpoint {
|
||||
func (p *AWSSDProvider) changesByNamespaceID(namespaces []*sdtypes.NamespaceSummary, changes []*endpoint.Endpoint) map[string][]*endpoint.Endpoint {
|
||||
changesByNsID := make(map[string][]*endpoint.Endpoint)
|
||||
|
||||
for _, ns := range namespaces {
|
||||
@ -600,8 +584,8 @@ func (p *AWSSDProvider) changesByNamespaceID(namespaces []*sd.NamespaceSummary,
|
||||
}
|
||||
|
||||
// returns list of all namespaces matching given hostname
|
||||
func matchingNamespaces(hostname string, namespaces []*sd.NamespaceSummary) []*sd.NamespaceSummary {
|
||||
matchingNamespaces := make([]*sd.NamespaceSummary, 0)
|
||||
func matchingNamespaces(hostname string, namespaces []*sdtypes.NamespaceSummary) []*sdtypes.NamespaceSummary {
|
||||
matchingNamespaces := make([]*sdtypes.NamespaceSummary, 0)
|
||||
|
||||
for _, ns := range namespaces {
|
||||
if *ns.Name == hostname {
|
||||
@ -621,26 +605,30 @@ func (p *AWSSDProvider) parseHostname(hostname string) (namespace string, servic
|
||||
}
|
||||
|
||||
// determine service routing policy based on endpoint type
|
||||
func (p *AWSSDProvider) routingPolicyFromEndpoint(ep *endpoint.Endpoint) string {
|
||||
if ep.RecordType == endpoint.RecordTypeA {
|
||||
return sd.RoutingPolicyMultivalue
|
||||
func (p *AWSSDProvider) routingPolicyFromEndpoint(ep *endpoint.Endpoint) sdtypes.RoutingPolicy {
|
||||
if ep.RecordType == endpoint.RecordTypeA || ep.RecordType == endpoint.RecordTypeAAAA {
|
||||
return sdtypes.RoutingPolicyMultivalue
|
||||
}
|
||||
|
||||
return sd.RoutingPolicyWeighted
|
||||
return sdtypes.RoutingPolicyWeighted
|
||||
}
|
||||
|
||||
// determine service type (A, CNAME) from given endpoint
|
||||
func (p *AWSSDProvider) serviceTypeFromEndpoint(ep *endpoint.Endpoint) string {
|
||||
if ep.RecordType == endpoint.RecordTypeCNAME {
|
||||
// determine service type (A, AAAA, CNAME) from given endpoint
|
||||
func (p *AWSSDProvider) serviceTypeFromEndpoint(ep *endpoint.Endpoint) sdtypes.RecordType {
|
||||
switch ep.RecordType {
|
||||
case endpoint.RecordTypeCNAME:
|
||||
// FIXME service type is derived from the first target only. Theoretically this may be problem.
|
||||
// But I don't see a scenario where one endpoint contains targets of different types.
|
||||
if p.isAWSLoadBalancer(ep.Targets[0]) {
|
||||
// ALIAS target uses DNS record type of A
|
||||
return sd.RecordTypeA
|
||||
// ALIAS target uses DNS record of type A
|
||||
return sdtypes.RecordTypeA
|
||||
}
|
||||
return sd.RecordTypeCname
|
||||
return sdtypes.RecordTypeCname
|
||||
case endpoint.RecordTypeAAAA:
|
||||
return sdtypes.RecordTypeAaaa
|
||||
default:
|
||||
return sdtypes.RecordTypeA
|
||||
}
|
||||
return sd.RecordTypeA
|
||||
}
|
||||
|
||||
// determine if a given hostname belongs to an AWS load balancer
|
||||
|
||||
@ -25,9 +25,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
sd "github.com/aws/aws-sdk-go/service/servicediscovery"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
sd "github.com/aws/aws-sdk-go-v2/service/servicediscovery"
|
||||
sdtypes "github.com/aws/aws-sdk-go-v2/service/servicediscovery/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -45,17 +45,17 @@ var (
|
||||
|
||||
type AWSSDClientStub struct {
|
||||
// map[namespace_id]namespace
|
||||
namespaces map[string]*sd.Namespace
|
||||
namespaces map[string]*sdtypes.Namespace
|
||||
|
||||
// map[namespace_id] => map[service_id]instance
|
||||
services map[string]map[string]*sd.Service
|
||||
services map[string]map[string]*sdtypes.Service
|
||||
|
||||
// map[service_id] => map[inst_id]instance
|
||||
instances map[string]map[string]*sd.Instance
|
||||
instances map[string]map[string]*sdtypes.Instance
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) CreateService(input *sd.CreateServiceInput) (*sd.CreateServiceOutput, error) {
|
||||
srv := &sd.Service{
|
||||
func (s *AWSSDClientStub) CreateService(ctx context.Context, input *sd.CreateServiceInput, optFns ...func(*sd.Options)) (*sd.CreateServiceOutput, error) {
|
||||
srv := &sdtypes.Service{
|
||||
Id: aws.String(strconv.Itoa(rand.Intn(10000))),
|
||||
DnsConfig: input.DnsConfig,
|
||||
Name: input.Name,
|
||||
@ -66,7 +66,7 @@ func (s *AWSSDClientStub) CreateService(input *sd.CreateServiceInput) (*sd.Creat
|
||||
|
||||
nsServices, ok := s.services[*input.NamespaceId]
|
||||
if !ok {
|
||||
nsServices = make(map[string]*sd.Service)
|
||||
nsServices = make(map[string]*sdtypes.Service)
|
||||
s.services[*input.NamespaceId] = nsServices
|
||||
}
|
||||
nsServices[*srv.Id] = srv
|
||||
@ -76,14 +76,14 @@ func (s *AWSSDClientStub) CreateService(input *sd.CreateServiceInput) (*sd.Creat
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) DeregisterInstance(input *sd.DeregisterInstanceInput) (*sd.DeregisterInstanceOutput, error) {
|
||||
func (s *AWSSDClientStub) DeregisterInstance(ctx context.Context, input *sd.DeregisterInstanceInput, optFns ...func(options *sd.Options)) (*sd.DeregisterInstanceOutput, error) {
|
||||
serviceInstances := s.instances[*input.ServiceId]
|
||||
delete(serviceInstances, *input.InstanceId)
|
||||
|
||||
return &sd.DeregisterInstanceOutput{}, nil
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) GetService(input *sd.GetServiceInput) (*sd.GetServiceOutput, error) {
|
||||
func (s *AWSSDClientStub) GetService(ctx context.Context, input *sd.GetServiceInput, optFns ...func(options *sd.Options)) (*sd.GetServiceOutput, error) {
|
||||
for _, entry := range s.services {
|
||||
srv, ok := entry[*input.Id]
|
||||
if ok {
|
||||
@ -96,18 +96,18 @@ func (s *AWSSDClientStub) GetService(input *sd.GetServiceInput) (*sd.GetServiceO
|
||||
return nil, errors.New("service not found")
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) DiscoverInstancesWithContext(ctx context.Context, input *sd.DiscoverInstancesInput, opts ...request.Option) (*sd.DiscoverInstancesOutput, error) {
|
||||
instances := make([]*sd.HttpInstanceSummary, 0)
|
||||
func (s *AWSSDClientStub) DiscoverInstances(ctx context.Context, input *sd.DiscoverInstancesInput, opts ...func(options *sd.Options)) (*sd.DiscoverInstancesOutput, error) {
|
||||
instances := make([]sdtypes.HttpInstanceSummary, 0)
|
||||
|
||||
var foundNs bool
|
||||
for _, ns := range s.namespaces {
|
||||
if aws.StringValue(ns.Name) == aws.StringValue(input.NamespaceName) {
|
||||
if *ns.Name == *input.NamespaceName {
|
||||
foundNs = true
|
||||
|
||||
for _, srv := range s.services[*ns.Id] {
|
||||
if aws.StringValue(srv.Name) == aws.StringValue(input.ServiceName) {
|
||||
if *srv.Name == *input.ServiceName {
|
||||
for _, inst := range s.instances[*srv.Id] {
|
||||
instances = append(instances, instanceToHTTPInstanceSummary(inst))
|
||||
instances = append(instances, *instanceToHTTPInstanceSummary(inst))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,57 +123,50 @@ func (s *AWSSDClientStub) DiscoverInstancesWithContext(ctx context.Context, inpu
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) ListNamespacesPages(input *sd.ListNamespacesInput, fn func(*sd.ListNamespacesOutput, bool) bool) error {
|
||||
namespaces := make([]*sd.NamespaceSummary, 0)
|
||||
|
||||
filter := input.Filters[0]
|
||||
func (s *AWSSDClientStub) ListNamespaces(ctx context.Context, input *sd.ListNamespacesInput, optFns ...func(options *sd.Options)) (*sd.ListNamespacesOutput, error) {
|
||||
namespaces := make([]sdtypes.NamespaceSummary, 0)
|
||||
|
||||
for _, ns := range s.namespaces {
|
||||
if filter != nil && *filter.Name == sd.NamespaceFilterNameType {
|
||||
if *ns.Type != *filter.Values[0] {
|
||||
if len(input.Filters) > 0 && input.Filters[0].Name == sdtypes.NamespaceFilterNameType {
|
||||
if ns.Type != sdtypes.NamespaceType(input.Filters[0].Values[0]) {
|
||||
// skip namespaces not matching filter
|
||||
continue
|
||||
}
|
||||
}
|
||||
namespaces = append(namespaces, namespaceToNamespaceSummary(ns))
|
||||
namespaces = append(namespaces, *namespaceToNamespaceSummary(ns))
|
||||
}
|
||||
|
||||
fn(&sd.ListNamespacesOutput{
|
||||
return &sd.ListNamespacesOutput{
|
||||
Namespaces: namespaces,
|
||||
}, true)
|
||||
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) ListServicesPages(input *sd.ListServicesInput, fn func(*sd.ListServicesOutput, bool) bool) error {
|
||||
services := make([]*sd.ServiceSummary, 0)
|
||||
func (s *AWSSDClientStub) ListServices(ctx context.Context, input *sd.ListServicesInput, optFns ...func(options *sd.Options)) (*sd.ListServicesOutput, error) {
|
||||
services := make([]sdtypes.ServiceSummary, 0)
|
||||
|
||||
// get namespace filter
|
||||
filter := input.Filters[0]
|
||||
if filter == nil || *filter.Name != sd.ServiceFilterNameNamespaceId {
|
||||
return errors.New("missing namespace filter")
|
||||
if len(input.Filters) == 0 || input.Filters[0].Name != sdtypes.ServiceFilterNameNamespaceId {
|
||||
return nil, errors.New("missing namespace filter")
|
||||
}
|
||||
nsID := filter.Values[0]
|
||||
nsID := input.Filters[0].Values[0]
|
||||
|
||||
for _, srv := range s.services[*nsID] {
|
||||
services = append(services, serviceToServiceSummary(srv))
|
||||
for _, srv := range s.services[nsID] {
|
||||
services = append(services, *serviceToServiceSummary(srv))
|
||||
}
|
||||
|
||||
fn(&sd.ListServicesOutput{
|
||||
return &sd.ListServicesOutput{
|
||||
Services: services,
|
||||
}, true)
|
||||
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) RegisterInstance(input *sd.RegisterInstanceInput) (*sd.RegisterInstanceOutput, error) {
|
||||
func (s *AWSSDClientStub) RegisterInstance(ctx context.Context, input *sd.RegisterInstanceInput, optFns ...func(options *sd.Options)) (*sd.RegisterInstanceOutput, error) {
|
||||
srvInstances, ok := s.instances[*input.ServiceId]
|
||||
if !ok {
|
||||
srvInstances = make(map[string]*sd.Instance)
|
||||
srvInstances = make(map[string]*sdtypes.Instance)
|
||||
s.instances[*input.ServiceId] = srvInstances
|
||||
}
|
||||
|
||||
srvInstances[*input.InstanceId] = &sd.Instance{
|
||||
srvInstances[*input.InstanceId] = &sdtypes.Instance{
|
||||
Id: input.InstanceId,
|
||||
Attributes: input.Attributes,
|
||||
CreatorRequestId: input.CreatorRequestId,
|
||||
@ -182,8 +175,8 @@ func (s *AWSSDClientStub) RegisterInstance(input *sd.RegisterInstanceInput) (*sd
|
||||
return &sd.RegisterInstanceOutput{}, nil
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) UpdateService(input *sd.UpdateServiceInput) (*sd.UpdateServiceOutput, error) {
|
||||
out, err := s.GetService(&sd.GetServiceInput{Id: input.Id})
|
||||
func (s *AWSSDClientStub) UpdateService(ctx context.Context, input *sd.UpdateServiceInput, optFns ...func(options *sd.Options)) (*sd.UpdateServiceOutput, error) {
|
||||
out, err := s.GetService(ctx, &sd.GetServiceInput{Id: input.Id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -197,8 +190,8 @@ func (s *AWSSDClientStub) UpdateService(input *sd.UpdateServiceInput) (*sd.Updat
|
||||
return &sd.UpdateServiceOutput{}, nil
|
||||
}
|
||||
|
||||
func (s *AWSSDClientStub) DeleteService(input *sd.DeleteServiceInput) (*sd.DeleteServiceOutput, error) {
|
||||
out, err := s.GetService(&sd.GetServiceInput{Id: input.Id})
|
||||
func (s *AWSSDClientStub) DeleteService(ctx context.Context, input *sd.DeleteServiceInput, optFns ...func(options *sd.Options)) (*sd.DeleteServiceOutput, error) {
|
||||
out, err := s.GetService(ctx, &sd.GetServiceInput{Id: input.Id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -221,39 +214,69 @@ func newTestAWSSDProvider(api AWSSDClient, domainFilter endpoint.DomainFilter, n
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: deadcode
|
||||
// used for unit test
|
||||
func instanceToHTTPInstanceSummary(instance *sd.Instance) *sd.HttpInstanceSummary {
|
||||
func instanceToHTTPInstanceSummary(instance *sdtypes.Instance) *sdtypes.HttpInstanceSummary {
|
||||
if instance == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &sd.HttpInstanceSummary{
|
||||
return &sdtypes.HttpInstanceSummary{
|
||||
InstanceId: instance.Id,
|
||||
Attributes: instance.Attributes,
|
||||
}
|
||||
}
|
||||
|
||||
func namespaceToNamespaceSummary(namespace *sdtypes.Namespace) *sdtypes.NamespaceSummary {
|
||||
if namespace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &sdtypes.NamespaceSummary{
|
||||
Id: namespace.Id,
|
||||
Type: namespace.Type,
|
||||
Name: namespace.Name,
|
||||
Arn: namespace.Arn,
|
||||
}
|
||||
}
|
||||
|
||||
func serviceToServiceSummary(service *sdtypes.Service) *sdtypes.ServiceSummary {
|
||||
if service == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &sdtypes.ServiceSummary{
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_Records(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
}
|
||||
|
||||
services := map[string]map[string]*sd.Service{
|
||||
services := map[string]map[string]*sdtypes.Service{
|
||||
"private": {
|
||||
"a-srv": {
|
||||
Id: aws.String("a-srv"),
|
||||
Name: aws.String("service1"),
|
||||
NamespaceId: aws.String("private"),
|
||||
Description: aws.String("owner-id"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeA),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeA,
|
||||
TTL: aws.Int64(100),
|
||||
}},
|
||||
},
|
||||
@ -261,12 +284,12 @@ func TestAWSSDProvider_Records(t *testing.T) {
|
||||
"alias-srv": {
|
||||
Id: aws.String("alias-srv"),
|
||||
Name: aws.String("service2"),
|
||||
NamespaceId: aws.String("private"),
|
||||
Description: aws.String("owner-id"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeA),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeA,
|
||||
TTL: aws.Int64(100),
|
||||
}},
|
||||
},
|
||||
@ -274,47 +297,68 @@ func TestAWSSDProvider_Records(t *testing.T) {
|
||||
"cname-srv": {
|
||||
Id: aws.String("cname-srv"),
|
||||
Name: aws.String("service3"),
|
||||
NamespaceId: aws.String("private"),
|
||||
Description: aws.String("owner-id"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeCname),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeCname,
|
||||
TTL: aws.Int64(80),
|
||||
}},
|
||||
},
|
||||
},
|
||||
"aaaa-srv": {
|
||||
Id: aws.String("aaaa-srv"),
|
||||
Name: aws.String("service4"),
|
||||
Description: aws.String("owner-id"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeAaaa,
|
||||
TTL: aws.Int64(100),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
instances := map[string]map[string]*sd.Instance{
|
||||
instances := map[string]map[string]*sdtypes.Instance{
|
||||
"a-srv": {
|
||||
"1.2.3.4": {
|
||||
Id: aws.String("1.2.3.4"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrIPV4: aws.String("1.2.3.4"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrIPV4: "1.2.3.4",
|
||||
},
|
||||
},
|
||||
"1.2.3.5": {
|
||||
Id: aws.String("1.2.3.5"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrIPV4: aws.String("1.2.3.5"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrIPV4: "1.2.3.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"alias-srv": {
|
||||
"load-balancer.us-east-1.elb.amazonaws.com": {
|
||||
Id: aws.String("load-balancer.us-east-1.elb.amazonaws.com"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrAlias: aws.String("load-balancer.us-east-1.elb.amazonaws.com"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrAlias: "load-balancer.us-east-1.elb.amazonaws.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
"cname-srv": {
|
||||
"cname.target.com": {
|
||||
Id: aws.String("cname.target.com"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrCname: aws.String("cname.target.com"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrCname: "cname.target.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
"aaaa-srv": {
|
||||
"0000:0000:0000:0000:abcd:abcd:abcd:abcd": {
|
||||
Id: aws.String("0000:0000:0000:0000:abcd:abcd:abcd:abcd"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrIPV6: "0000:0000:0000:0000:abcd:abcd:abcd:abcd",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -324,6 +368,7 @@ func TestAWSSDProvider_Records(t *testing.T) {
|
||||
{DNSName: "service1.private.com", Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5"}, RecordType: endpoint.RecordTypeA, RecordTTL: 100, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
|
||||
{DNSName: "service2.private.com", Targets: endpoint.Targets{"load-balancer.us-east-1.elb.amazonaws.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 100, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
|
||||
{DNSName: "service3.private.com", Targets: endpoint.Targets{"cname.target.com"}, RecordType: endpoint.RecordTypeCNAME, RecordTTL: 80, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
|
||||
{DNSName: "service4.private.com", Targets: endpoint.Targets{"0000:0000:0000:0000:abcd:abcd:abcd:abcd"}, RecordType: endpoint.RecordTypeAAAA, RecordTTL: 100, Labels: map[string]string{endpoint.AWSSDDescriptionLabel: "owner-id"}},
|
||||
}
|
||||
|
||||
api := &AWSSDClientStub{
|
||||
@ -340,18 +385,18 @@ func TestAWSSDProvider_Records(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_ApplyChanges(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
}
|
||||
|
||||
api := &AWSSDClientStub{
|
||||
namespaces: namespaces,
|
||||
services: make(map[string]map[string]*sd.Service),
|
||||
instances: make(map[string]map[string]*sd.Instance),
|
||||
services: make(map[string]map[string]*sdtypes.Service),
|
||||
instances: make(map[string]map[string]*sdtypes.Instance),
|
||||
}
|
||||
|
||||
expectedEndpoints := []*endpoint.Endpoint{
|
||||
@ -371,7 +416,7 @@ func TestAWSSDProvider_ApplyChanges(t *testing.T) {
|
||||
|
||||
// make sure services were created
|
||||
assert.Len(t, api.services["private"], 3)
|
||||
existingServices, _ := provider.ListServicesByNamespaceID(namespaces["private"].Id)
|
||||
existingServices, _ := provider.ListServicesByNamespaceID(context.Background(), namespaces["private"].Id)
|
||||
assert.NotNil(t, existingServices["service1"])
|
||||
assert.NotNil(t, existingServices["service2"])
|
||||
assert.NotNil(t, existingServices["service3"])
|
||||
@ -392,16 +437,16 @@ func TestAWSSDProvider_ApplyChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_ListNamespaces(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
"public": {
|
||||
Id: aws.String("public"),
|
||||
Name: aws.String("public.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPublic),
|
||||
Type: sdtypes.NamespaceTypeDnsPublic,
|
||||
},
|
||||
}
|
||||
|
||||
@ -413,20 +458,20 @@ func TestAWSSDProvider_ListNamespaces(t *testing.T) {
|
||||
msg string
|
||||
domainFilter endpoint.DomainFilter
|
||||
namespaceTypeFilter string
|
||||
expectedNamespaces []*sd.NamespaceSummary
|
||||
expectedNamespaces []*sdtypes.NamespaceSummary
|
||||
}{
|
||||
{"public filter", endpoint.NewDomainFilter([]string{}), "public", []*sd.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
|
||||
{"private filter", endpoint.NewDomainFilter([]string{}), "private", []*sd.NamespaceSummary{namespaceToNamespaceSummary(namespaces["private"])}},
|
||||
{"domain filter", endpoint.NewDomainFilter([]string{"public.com"}), "", []*sd.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
|
||||
{"non-existing domain", endpoint.NewDomainFilter([]string{"xxx.com"}), "", []*sd.NamespaceSummary{}},
|
||||
{"public filter", endpoint.NewDomainFilter([]string{}), "public", []*sdtypes.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
|
||||
{"private filter", endpoint.NewDomainFilter([]string{}), "private", []*sdtypes.NamespaceSummary{namespaceToNamespaceSummary(namespaces["private"])}},
|
||||
{"domain filter", endpoint.NewDomainFilter([]string{"public.com"}), "", []*sdtypes.NamespaceSummary{namespaceToNamespaceSummary(namespaces["public"])}},
|
||||
{"non-existing domain", endpoint.NewDomainFilter([]string{"xxx.com"}), "", []*sdtypes.NamespaceSummary{}},
|
||||
} {
|
||||
provider := newTestAWSSDProvider(api, tc.domainFilter, tc.namespaceTypeFilter, "")
|
||||
|
||||
result, err := provider.ListNamespaces()
|
||||
result, err := provider.ListNamespaces(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedMap := make(map[string]*sd.NamespaceSummary)
|
||||
resultMap := make(map[string]*sd.NamespaceSummary)
|
||||
expectedMap := make(map[string]*sdtypes.NamespaceSummary)
|
||||
resultMap := make(map[string]*sdtypes.NamespaceSummary)
|
||||
for _, ns := range tc.expectedNamespaces {
|
||||
expectedMap[*ns.Id] = ns
|
||||
}
|
||||
@ -441,20 +486,20 @@ func TestAWSSDProvider_ListNamespaces(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_ListServicesByNamespace(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
"public": {
|
||||
Id: aws.String("public"),
|
||||
Name: aws.String("public.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPublic),
|
||||
Type: sdtypes.NamespaceTypeDnsPublic,
|
||||
},
|
||||
}
|
||||
|
||||
services := map[string]map[string]*sd.Service{
|
||||
services := map[string]map[string]*sdtypes.Service{
|
||||
"private": {
|
||||
"srv1": {
|
||||
Id: aws.String("srv1"),
|
||||
@ -482,48 +527,74 @@ func TestAWSSDProvider_ListServicesByNamespace(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
expectedServices map[string]*sd.Service
|
||||
expectedServices map[string]*sdtypes.Service
|
||||
}{
|
||||
{map[string]*sd.Service{"service1": services["private"]["srv1"], "service2": services["private"]["srv2"]}},
|
||||
{map[string]*sdtypes.Service{"service1": services["private"]["srv1"], "service2": services["private"]["srv2"]}},
|
||||
} {
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
result, err := provider.ListServicesByNamespaceID(namespaces["private"].Id)
|
||||
result, err := provider.ListServicesByNamespaceID(context.Background(), namespaces["private"].Id)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedServices, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_CreateService(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
}
|
||||
|
||||
api := &AWSSDClientStub{
|
||||
namespaces: namespaces,
|
||||
services: make(map[string]map[string]*sd.Service),
|
||||
services: make(map[string]map[string]*sdtypes.Service),
|
||||
}
|
||||
|
||||
expectedServices := make(map[string]*sd.Service)
|
||||
expectedServices := make(map[string]*sdtypes.Service)
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
// A type
|
||||
provider.CreateService(aws.String("private"), aws.String("A-srv"), &endpoint.Endpoint{
|
||||
provider.CreateService(context.Background(), aws.String("private"), aws.String("A-srv"), &endpoint.Endpoint{
|
||||
Labels: map[string]string{
|
||||
endpoint.AWSSDDescriptionLabel: "A-srv",
|
||||
},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 60,
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
})
|
||||
expectedServices["A-srv"] = &sd.Service{
|
||||
Name: aws.String("A-srv"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyMultivalue),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeA),
|
||||
expectedServices["A-srv"] = &sdtypes.Service{
|
||||
Name: aws.String("A-srv"),
|
||||
Description: aws.String("A-srv"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyMultivalue,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeA,
|
||||
TTL: aws.Int64(60),
|
||||
}},
|
||||
},
|
||||
NamespaceId: aws.String("private"),
|
||||
}
|
||||
|
||||
// AAAA type
|
||||
provider.CreateService(context.Background(), aws.String("private"), aws.String("AAAA-srv"), &endpoint.Endpoint{
|
||||
Labels: map[string]string{
|
||||
endpoint.AWSSDDescriptionLabel: "AAAA-srv",
|
||||
},
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
RecordTTL: 60,
|
||||
Targets: endpoint.Targets{"::1234:5678:"},
|
||||
})
|
||||
expectedServices["AAAA-srv"] = &sdtypes.Service{
|
||||
Name: aws.String("AAAA-srv"),
|
||||
Description: aws.String("AAAA-srv"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyMultivalue,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeAaaa,
|
||||
TTL: aws.Int64(60),
|
||||
}},
|
||||
},
|
||||
@ -531,17 +602,21 @@ func TestAWSSDProvider_CreateService(t *testing.T) {
|
||||
}
|
||||
|
||||
// CNAME type
|
||||
provider.CreateService(aws.String("private"), aws.String("CNAME-srv"), &endpoint.Endpoint{
|
||||
provider.CreateService(context.Background(), aws.String("private"), aws.String("CNAME-srv"), &endpoint.Endpoint{
|
||||
Labels: map[string]string{
|
||||
endpoint.AWSSDDescriptionLabel: "CNAME-srv",
|
||||
},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
RecordTTL: 80,
|
||||
Targets: endpoint.Targets{"cname.target.com"},
|
||||
})
|
||||
expectedServices["CNAME-srv"] = &sd.Service{
|
||||
Name: aws.String("CNAME-srv"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeCname),
|
||||
expectedServices["CNAME-srv"] = &sdtypes.Service{
|
||||
Name: aws.String("CNAME-srv"),
|
||||
Description: aws.String("CNAME-srv"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeCname,
|
||||
TTL: aws.Int64(80),
|
||||
}},
|
||||
},
|
||||
@ -549,17 +624,21 @@ func TestAWSSDProvider_CreateService(t *testing.T) {
|
||||
}
|
||||
|
||||
// ALIAS type
|
||||
provider.CreateService(aws.String("private"), aws.String("ALIAS-srv"), &endpoint.Endpoint{
|
||||
provider.CreateService(context.Background(), aws.String("private"), aws.String("ALIAS-srv"), &endpoint.Endpoint{
|
||||
Labels: map[string]string{
|
||||
endpoint.AWSSDDescriptionLabel: "ALIAS-srv",
|
||||
},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
RecordTTL: 100,
|
||||
Targets: endpoint.Targets{"load-balancer.us-east-1.elb.amazonaws.com"},
|
||||
})
|
||||
expectedServices["ALIAS-srv"] = &sd.Service{
|
||||
Name: aws.String("ALIAS-srv"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeA),
|
||||
expectedServices["ALIAS-srv"] = &sdtypes.Service{
|
||||
Name: aws.String("ALIAS-srv"),
|
||||
Description: aws.String("ALIAS-srv"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeA,
|
||||
TTL: aws.Int64(100),
|
||||
}},
|
||||
},
|
||||
@ -569,7 +648,7 @@ func TestAWSSDProvider_CreateService(t *testing.T) {
|
||||
validateAWSSDServicesMapsEqual(t, expectedServices, api.services["private"])
|
||||
}
|
||||
|
||||
func validateAWSSDServicesMapsEqual(t *testing.T, expected map[string]*sd.Service, services map[string]*sd.Service) {
|
||||
func validateAWSSDServicesMapsEqual(t *testing.T, expected map[string]*sdtypes.Service, services map[string]*sdtypes.Service) {
|
||||
require.Len(t, services, len(expected))
|
||||
|
||||
for _, srv := range services {
|
||||
@ -577,31 +656,31 @@ func validateAWSSDServicesMapsEqual(t *testing.T, expected map[string]*sd.Servic
|
||||
}
|
||||
}
|
||||
|
||||
func validateAWSSDServicesEqual(t *testing.T, expected *sd.Service, srv *sd.Service) {
|
||||
assert.Equal(t, aws.StringValue(expected.Description), aws.StringValue(srv.Description))
|
||||
assert.Equal(t, aws.StringValue(expected.Name), aws.StringValue(srv.Name))
|
||||
func validateAWSSDServicesEqual(t *testing.T, expected *sdtypes.Service, srv *sdtypes.Service) {
|
||||
assert.Equal(t, *expected.Description, *srv.Description)
|
||||
assert.Equal(t, *expected.Name, *srv.Name)
|
||||
assert.True(t, reflect.DeepEqual(*expected.DnsConfig, *srv.DnsConfig))
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_UpdateService(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
}
|
||||
|
||||
services := map[string]map[string]*sd.Service{
|
||||
services := map[string]map[string]*sdtypes.Service{
|
||||
"private": {
|
||||
"srv1": {
|
||||
Id: aws.String("srv1"),
|
||||
Name: aws.String("service1"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyMultivalue),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeA),
|
||||
Id: aws.String("srv1"),
|
||||
Name: aws.String("service1"),
|
||||
NamespaceId: aws.String("private"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyMultivalue,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeA,
|
||||
TTL: aws.Int64(60),
|
||||
}},
|
||||
},
|
||||
@ -617,7 +696,7 @@ func TestAWSSDProvider_UpdateService(t *testing.T) {
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
// update service with different TTL
|
||||
provider.UpdateService(services["private"]["srv1"], &endpoint.Endpoint{
|
||||
provider.UpdateService(context.Background(), services["private"]["srv1"], &endpoint.Endpoint{
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 100,
|
||||
})
|
||||
@ -626,15 +705,15 @@ func TestAWSSDProvider_UpdateService(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_DeleteService(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
}
|
||||
|
||||
services := map[string]map[string]*sd.Service{
|
||||
services := map[string]map[string]*sdtypes.Service{
|
||||
"private": {
|
||||
"srv1": {
|
||||
Id: aws.String("srv1"),
|
||||
@ -665,16 +744,16 @@ func TestAWSSDProvider_DeleteService(t *testing.T) {
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "owner-id")
|
||||
|
||||
// delete first service
|
||||
err := provider.DeleteService(services["private"]["srv1"])
|
||||
err := provider.DeleteService(context.Background(), services["private"]["srv1"])
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, api.services["private"], 2)
|
||||
|
||||
// delete third service
|
||||
err1 := provider.DeleteService(services["private"]["srv3"])
|
||||
err1 := provider.DeleteService(context.Background(), services["private"]["srv3"])
|
||||
assert.NoError(t, err1)
|
||||
assert.Len(t, api.services["private"], 1)
|
||||
|
||||
expectedServices := map[string]*sd.Service{
|
||||
expectedServices := map[string]*sdtypes.Service{
|
||||
"srv2": {
|
||||
Id: aws.String("srv2"),
|
||||
Description: aws.String("heritage=external-dns,external-dns/owner=owner-id"),
|
||||
@ -687,130 +766,157 @@ func TestAWSSDProvider_DeleteService(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_RegisterInstance(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
}
|
||||
|
||||
services := map[string]map[string]*sd.Service{
|
||||
services := map[string]map[string]*sdtypes.Service{
|
||||
"private": {
|
||||
"a-srv": {
|
||||
Id: aws.String("a-srv"),
|
||||
Name: aws.String("service1"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeA),
|
||||
Id: aws.String("a-srv"),
|
||||
Name: aws.String("service1"),
|
||||
NamespaceId: aws.String("private"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeA,
|
||||
TTL: aws.Int64(60),
|
||||
}},
|
||||
},
|
||||
},
|
||||
"cname-srv": {
|
||||
Id: aws.String("cname-srv"),
|
||||
Name: aws.String("service2"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeCname),
|
||||
Id: aws.String("cname-srv"),
|
||||
Name: aws.String("service2"),
|
||||
NamespaceId: aws.String("private"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeCname,
|
||||
TTL: aws.Int64(60),
|
||||
}},
|
||||
},
|
||||
},
|
||||
"alias-srv": {
|
||||
Id: aws.String("alias-srv"),
|
||||
Name: aws.String("service3"),
|
||||
DnsConfig: &sd.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: aws.String(sd.RoutingPolicyWeighted),
|
||||
DnsRecords: []*sd.DnsRecord{{
|
||||
Type: aws.String(sd.RecordTypeA),
|
||||
Id: aws.String("alias-srv"),
|
||||
Name: aws.String("service3"),
|
||||
NamespaceId: aws.String("private"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeA,
|
||||
TTL: aws.Int64(60),
|
||||
}},
|
||||
},
|
||||
},
|
||||
"aaaa-srv": {
|
||||
Id: aws.String("aaaa-srv"),
|
||||
Name: aws.String("service4"),
|
||||
Description: aws.String("owner-id"),
|
||||
DnsConfig: &sdtypes.DnsConfig{
|
||||
NamespaceId: aws.String("private"),
|
||||
RoutingPolicy: sdtypes.RoutingPolicyWeighted,
|
||||
DnsRecords: []sdtypes.DnsRecord{{
|
||||
Type: sdtypes.RecordTypeAaaa,
|
||||
TTL: aws.Int64(100),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
api := &AWSSDClientStub{
|
||||
namespaces: namespaces,
|
||||
services: services,
|
||||
instances: make(map[string]map[string]*sd.Instance),
|
||||
instances: make(map[string]map[string]*sdtypes.Instance),
|
||||
}
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
expectedInstances := make(map[string]*sd.Instance)
|
||||
expectedInstances := make(map[string]*sdtypes.Instance)
|
||||
|
||||
// IP-based instance
|
||||
provider.RegisterInstance(services["private"]["a-srv"], &endpoint.Endpoint{
|
||||
// IPv4-based instance
|
||||
provider.RegisterInstance(context.Background(), services["private"]["a-srv"], &endpoint.Endpoint{
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
DNSName: "service1.private.com.",
|
||||
RecordTTL: 300,
|
||||
Targets: endpoint.Targets{"1.2.3.4", "1.2.3.5"},
|
||||
})
|
||||
expectedInstances["1.2.3.4"] = &sd.Instance{
|
||||
expectedInstances["1.2.3.4"] = &sdtypes.Instance{
|
||||
Id: aws.String("1.2.3.4"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrIPV4: aws.String("1.2.3.4"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrIPV4: "1.2.3.4",
|
||||
},
|
||||
}
|
||||
expectedInstances["1.2.3.5"] = &sd.Instance{
|
||||
expectedInstances["1.2.3.5"] = &sdtypes.Instance{
|
||||
Id: aws.String("1.2.3.5"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrIPV4: aws.String("1.2.3.5"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrIPV4: "1.2.3.5",
|
||||
},
|
||||
}
|
||||
|
||||
// AWS ELB instance (ALIAS)
|
||||
provider.RegisterInstance(services["private"]["alias-srv"], &endpoint.Endpoint{
|
||||
provider.RegisterInstance(context.Background(), services["private"]["alias-srv"], &endpoint.Endpoint{
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
DNSName: "service1.private.com.",
|
||||
RecordTTL: 300,
|
||||
Targets: endpoint.Targets{"load-balancer.us-east-1.elb.amazonaws.com", "load-balancer.us-west-2.elb.amazonaws.com"},
|
||||
})
|
||||
expectedInstances["load-balancer.us-east-1.elb.amazonaws.com"] = &sd.Instance{
|
||||
expectedInstances["load-balancer.us-east-1.elb.amazonaws.com"] = &sdtypes.Instance{
|
||||
Id: aws.String("load-balancer.us-east-1.elb.amazonaws.com"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrAlias: aws.String("load-balancer.us-east-1.elb.amazonaws.com"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrAlias: "load-balancer.us-east-1.elb.amazonaws.com",
|
||||
},
|
||||
}
|
||||
expectedInstances["load-balancer.us-west-2.elb.amazonaws.com"] = &sd.Instance{
|
||||
expectedInstances["load-balancer.us-west-2.elb.amazonaws.com"] = &sdtypes.Instance{
|
||||
Id: aws.String("load-balancer.us-west-2.elb.amazonaws.com"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrAlias: aws.String("load-balancer.us-west-2.elb.amazonaws.com"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrAlias: "load-balancer.us-west-2.elb.amazonaws.com",
|
||||
},
|
||||
}
|
||||
|
||||
// AWS NLB instance (ALIAS)
|
||||
provider.RegisterInstance(services["private"]["alias-srv"], &endpoint.Endpoint{
|
||||
provider.RegisterInstance(context.Background(), services["private"]["alias-srv"], &endpoint.Endpoint{
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
DNSName: "service1.private.com.",
|
||||
RecordTTL: 300,
|
||||
Targets: endpoint.Targets{"load-balancer.elb.us-west-2.amazonaws.com"},
|
||||
})
|
||||
expectedInstances["load-balancer.elb.us-west-2.amazonaws.com"] = &sd.Instance{
|
||||
expectedInstances["load-balancer.elb.us-west-2.amazonaws.com"] = &sdtypes.Instance{
|
||||
Id: aws.String("load-balancer.elb.us-west-2.amazonaws.com"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrAlias: aws.String("load-balancer.elb.us-west-2.amazonaws.com"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrAlias: "load-balancer.elb.us-west-2.amazonaws.com",
|
||||
},
|
||||
}
|
||||
|
||||
// CNAME instance
|
||||
provider.RegisterInstance(services["private"]["cname-srv"], &endpoint.Endpoint{
|
||||
provider.RegisterInstance(context.Background(), services["private"]["cname-srv"], &endpoint.Endpoint{
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
DNSName: "service2.private.com.",
|
||||
RecordTTL: 300,
|
||||
Targets: endpoint.Targets{"cname.target.com"},
|
||||
})
|
||||
expectedInstances["cname.target.com"] = &sd.Instance{
|
||||
expectedInstances["cname.target.com"] = &sdtypes.Instance{
|
||||
Id: aws.String("cname.target.com"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrCname: aws.String("cname.target.com"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrCname: "cname.target.com",
|
||||
},
|
||||
}
|
||||
|
||||
// IPv6-based instance
|
||||
provider.RegisterInstance(context.Background(), services["private"]["aaaa-srv"], &endpoint.Endpoint{
|
||||
RecordType: endpoint.RecordTypeAAAA,
|
||||
DNSName: "service4.private.com.",
|
||||
RecordTTL: 300,
|
||||
Targets: endpoint.Targets{"0000:0000:0000:0000:abcd:abcd:abcd:abcd"},
|
||||
})
|
||||
expectedInstances["0000:0000:0000:0000:abcd:abcd:abcd:abcd"] = &sdtypes.Instance{
|
||||
Id: aws.String("0000:0000:0000:0000:abcd:abcd:abcd:abcd"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrIPV6: "0000:0000:0000:0000:abcd:abcd:abcd:abcd",
|
||||
},
|
||||
}
|
||||
|
||||
@ -825,15 +931,15 @@ func TestAWSSDProvider_RegisterInstance(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_DeregisterInstance(t *testing.T) {
|
||||
namespaces := map[string]*sd.Namespace{
|
||||
namespaces := map[string]*sdtypes.Namespace{
|
||||
"private": {
|
||||
Id: aws.String("private"),
|
||||
Name: aws.String("private.com"),
|
||||
Type: aws.String(sd.NamespaceTypeDnsPrivate),
|
||||
Type: sdtypes.NamespaceTypeDnsPrivate,
|
||||
},
|
||||
}
|
||||
|
||||
services := map[string]map[string]*sd.Service{
|
||||
services := map[string]map[string]*sdtypes.Service{
|
||||
"private": {
|
||||
"srv1": {
|
||||
Id: aws.String("srv1"),
|
||||
@ -842,12 +948,12 @@ func TestAWSSDProvider_DeregisterInstance(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
instances := map[string]map[string]*sd.Instance{
|
||||
instances := map[string]map[string]*sdtypes.Instance{
|
||||
"srv1": {
|
||||
"1.2.3.4": {
|
||||
Id: aws.String("1.2.3.4"),
|
||||
Attributes: map[string]*string{
|
||||
sdInstanceAttrIPV4: aws.String("1.2.3.4"),
|
||||
Attributes: map[string]string{
|
||||
sdInstanceAttrIPV4: "1.2.3.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -861,7 +967,43 @@ func TestAWSSDProvider_DeregisterInstance(t *testing.T) {
|
||||
|
||||
provider := newTestAWSSDProvider(api, endpoint.NewDomainFilter([]string{}), "", "")
|
||||
|
||||
provider.DeregisterInstance(services["private"]["srv1"], endpoint.NewEndpoint("srv1.private.com.", endpoint.RecordTypeA, "1.2.3.4"))
|
||||
provider.DeregisterInstance(context.Background(), services["private"]["srv1"], endpoint.NewEndpoint("srv1.private.com.", endpoint.RecordTypeA, "1.2.3.4"))
|
||||
|
||||
assert.Len(t, instances["srv1"], 0)
|
||||
}
|
||||
|
||||
func TestAWSSDProvider_awsTags(t *testing.T) {
|
||||
tests := []struct {
|
||||
Expectation []sdtypes.Tag
|
||||
Input map[string]string
|
||||
}{
|
||||
{
|
||||
Expectation: []sdtypes.Tag{
|
||||
{
|
||||
Key: aws.String("key1"),
|
||||
Value: aws.String("value1"),
|
||||
},
|
||||
{
|
||||
Key: aws.String("key2"),
|
||||
Value: aws.String("value2"),
|
||||
},
|
||||
},
|
||||
Input: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
},
|
||||
{
|
||||
Expectation: []sdtypes.Tag{},
|
||||
Input: map[string]string{},
|
||||
},
|
||||
{
|
||||
Expectation: []sdtypes.Tag{},
|
||||
Input: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
require.ElementsMatch(t, test.Expectation, awsTags(test.Input))
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@ -60,13 +61,14 @@ type AzureProvider struct {
|
||||
userAssignedIdentityClientID string
|
||||
activeDirectoryAuthorityHost string
|
||||
zonesClient ZonesClient
|
||||
zonesCache *zonesCache[dns.Zone]
|
||||
recordSetsClient RecordSetsClient
|
||||
}
|
||||
|
||||
// NewAzureProvider creates a new Azure provider.
|
||||
//
|
||||
// Returns the provider or an error if a provider could not be created.
|
||||
func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, dryRun bool) (*AzureProvider, error) {
|
||||
func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, zonesCacheDuration time.Duration, dryRun bool) (*AzureProvider, error) {
|
||||
cfg, err := getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID, activeDirectoryAuthorityHost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||
@ -93,6 +95,7 @@ func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zon
|
||||
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
||||
activeDirectoryAuthorityHost: cfg.ActiveDirectoryAuthorityHost,
|
||||
zonesClient: zonesClient,
|
||||
zonesCache: &zonesCache[dns.Zone]{duration: zonesCacheDuration},
|
||||
recordSetsClient: recordSetsClient,
|
||||
}, nil
|
||||
}
|
||||
@ -167,6 +170,10 @@ func (p *AzureProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
||||
|
||||
func (p *AzureProvider) zones(ctx context.Context) ([]dns.Zone, error) {
|
||||
log.Debugf("Retrieving Azure DNS zones for resource group: %s.", p.resourceGroup)
|
||||
if !p.zonesCache.Expired() {
|
||||
log.Debugf("Using cached Azure DNS zones for resource group: %s zone count: %d.", p.resourceGroup, len(p.zonesCache.Get()))
|
||||
return p.zonesCache.Get(), nil
|
||||
}
|
||||
var zones []dns.Zone
|
||||
pager := p.zonesClient.NewListByResourceGroupPager(p.resourceGroup, &dns.ZonesClientListByResourceGroupOptions{Top: nil})
|
||||
for pager.More() {
|
||||
@ -183,7 +190,8 @@ func (p *AzureProvider) zones(ctx context.Context) ([]dns.Zone, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("Found %d Azure DNS zone(s).", len(zones))
|
||||
log.Debugf("Found %d Azure DNS zone(s). Updating zones cache", len(zones))
|
||||
p.zonesCache.Reset(zones)
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
azcoreruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
||||
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
|
||||
@ -55,13 +56,14 @@ type AzurePrivateDNSProvider struct {
|
||||
userAssignedIdentityClientID string
|
||||
activeDirectoryAuthorityHost string
|
||||
zonesClient PrivateZonesClient
|
||||
zonesCache *zonesCache[privatedns.PrivateZone]
|
||||
recordSetsClient PrivateRecordSetsClient
|
||||
}
|
||||
|
||||
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
||||
//
|
||||
// Returns the provider or an error if a provider could not be created.
|
||||
func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
||||
func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, subscriptionID string, resourceGroup string, userAssignedIdentityClientID string, activeDirectoryAuthorityHost string, zonesCacheDuration time.Duration, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
||||
cfg, err := getConfig(configFile, subscriptionID, resourceGroup, userAssignedIdentityClientID, activeDirectoryAuthorityHost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||
@ -88,6 +90,7 @@ func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainF
|
||||
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
||||
activeDirectoryAuthorityHost: cfg.ActiveDirectoryAuthorityHost,
|
||||
zonesClient: zonesClient,
|
||||
zonesCache: &zonesCache[privatedns.PrivateZone]{duration: zonesCacheDuration},
|
||||
recordSetsClient: recordSetsClient,
|
||||
}, nil
|
||||
}
|
||||
@ -177,7 +180,10 @@ func (p *AzurePrivateDNSProvider) ApplyChanges(ctx context.Context, changes *pla
|
||||
|
||||
func (p *AzurePrivateDNSProvider) zones(ctx context.Context) ([]privatedns.PrivateZone, error) {
|
||||
log.Debugf("Retrieving Azure Private DNS zones for Resource Group '%s'", p.resourceGroup)
|
||||
|
||||
if !p.zonesCache.Expired() {
|
||||
log.Debugf("Using cached Azure Private DNS zones for resource group: %s zone count: %d.", p.resourceGroup, len(p.zonesCache.Get()))
|
||||
return p.zonesCache.Get(), nil
|
||||
}
|
||||
var zones []privatedns.PrivateZone
|
||||
|
||||
pager := p.zonesClient.NewListByResourceGroupPager(p.resourceGroup, &privatedns.PrivateZonesClientListByResourceGroupOptions{Top: nil})
|
||||
@ -198,7 +204,8 @@ func (p *AzurePrivateDNSProvider) zones(ctx context.Context) ([]privatedns.Priva
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Found %d Azure Private DNS zone(s).", len(zones))
|
||||
log.Debugf("Found %d Azure Private DNS zone(s). Updating zones cache", len(zones))
|
||||
p.zonesCache.Reset(zones)
|
||||
return zones, nil
|
||||
}
|
||||
|
||||
|
||||
@ -238,6 +238,7 @@ func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneNameFilt
|
||||
dryRun: dryRun,
|
||||
resourceGroup: resourceGroup,
|
||||
zonesClient: privateZonesClient,
|
||||
zonesCache: &zonesCache[privatedns.PrivateZone]{duration: 0},
|
||||
recordSetsClient: privateRecordsClient,
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,6 +238,7 @@ func newAzureProvider(domainFilter endpoint.DomainFilter, zoneNameFilter endpoin
|
||||
userAssignedIdentityClientID: userAssignedIdentityClientID,
|
||||
activeDirectoryAuthorityHost: activeDirectoryAuthorityHost,
|
||||
zonesClient: zonesClient,
|
||||
zonesCache: &zonesCache[dns.Zone]{duration: 0},
|
||||
recordSetsClient: recordsClient,
|
||||
}
|
||||
}
|
||||
|
||||
50
provider/azure/cache.go
Normal file
50
provider/azure/cache.go
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2024 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 azure
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// zonesCache is a cache for Azure zones(private or public)
|
||||
type zonesCache[T any] struct {
|
||||
age time.Time
|
||||
duration time.Duration
|
||||
zones []T
|
||||
}
|
||||
|
||||
// Reset method to reset the zones and update the age. This will be used to update the cache
|
||||
// after making a new API call to get the zones.
|
||||
func (z *zonesCache[T]) Reset(zones []T) {
|
||||
if z.duration > time.Duration(0) {
|
||||
z.age = time.Now()
|
||||
z.zones = zones
|
||||
}
|
||||
}
|
||||
|
||||
// Get method to retrieve the cached zones. If cache is not expired, this will be used
|
||||
// instead of making a new API call to get the zones.
|
||||
func (z *zonesCache[T]) Get() []T {
|
||||
return z.zones
|
||||
}
|
||||
|
||||
// Expired method to check if the cache has expired based on duration or if zones are empty.
|
||||
// If cache is expired, a new API call will be made to get the zones. If zones are empty, a new
|
||||
// API call will be made to get the zones. This case comes in at the time of initialization.
|
||||
func (z *zonesCache[T]) Expired() bool {
|
||||
return len(z.zones) < 1 || time.Since(z.age) > z.duration
|
||||
}
|
||||
78
provider/azure/cache_test.go
Normal file
78
provider/azure/cache_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2024 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 azure
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dns "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestzonesCache(t *testing.T) {
|
||||
now := time.Now()
|
||||
zoneName := "example.com"
|
||||
var testCases = map[string]struct {
|
||||
z *zonesCache[dns.Zone]
|
||||
expired bool
|
||||
}{
|
||||
"inactive-zone-cache": {
|
||||
&zonesCache[dns.Zone]{
|
||||
duration: 0 * time.Second,
|
||||
},
|
||||
true,
|
||||
},
|
||||
"empty-active-zone-cache": {
|
||||
&zonesCache[dns.Zone]{
|
||||
duration: 30 * time.Second,
|
||||
},
|
||||
true,
|
||||
},
|
||||
"expired-zone-cache": {
|
||||
&zonesCache[dns.Zone]{
|
||||
age: now.Add(-300 * time.Second),
|
||||
duration: 30 * time.Second,
|
||||
},
|
||||
true,
|
||||
},
|
||||
"active-zone-cache": {
|
||||
&zonesCache[dns.Zone]{
|
||||
zones: []dns.Zone{{
|
||||
Name: &zoneName,
|
||||
}},
|
||||
duration: 30 * time.Second,
|
||||
age: now,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.expired, testCase.z.Expired())
|
||||
var resetZoneLength = 1
|
||||
if testCase.z.duration == 0 {
|
||||
resetZoneLength = 0
|
||||
}
|
||||
testCase.z.Reset([]dns.Zone{{
|
||||
Name: &zoneName,
|
||||
}})
|
||||
assert.Len(t, testCase.z.Get(), resetZoneLength)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -148,8 +148,8 @@ type RecordParamsTypes interface {
|
||||
cloudflare.UpdateDNSRecordParams | cloudflare.CreateDNSRecordParams
|
||||
}
|
||||
|
||||
// getUpdateDNSRecordParam is a function that returns the appropriate Record Param based on the cloudFlareChange passed in
|
||||
func getUpdateDNSRecordParam(cfc cloudFlareChange) cloudflare.UpdateDNSRecordParams {
|
||||
// updateDNSRecordParam is a function that returns the appropriate Record Param based on the cloudFlareChange passed in
|
||||
func updateDNSRecordParam(cfc cloudFlareChange) cloudflare.UpdateDNSRecordParams {
|
||||
return cloudflare.UpdateDNSRecordParams{
|
||||
Name: cfc.ResourceRecord.Name,
|
||||
TTL: cfc.ResourceRecord.TTL,
|
||||
@ -368,7 +368,7 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
|
||||
log.WithFields(logFields).Errorf("failed to find previous record: %v", change.ResourceRecord)
|
||||
continue
|
||||
}
|
||||
recordParam := getUpdateDNSRecordParam(*change)
|
||||
recordParam := updateDNSRecordParam(*change)
|
||||
regionalHostnameParam := updateDataLocalizationRegionalHostnameParams(*change)
|
||||
recordParam.ID = recordID
|
||||
err := p.Client.UpdateDNSRecord(ctx, resourceContainer, recordParam)
|
||||
|
||||
@ -54,7 +54,6 @@ type mockCloudFlareClient struct {
|
||||
var ExampleDomain = []cloudflare.DNSRecord{
|
||||
{
|
||||
ID: "1234567890",
|
||||
ZoneID: "001",
|
||||
Name: "foobar.bar.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
TTL: 120,
|
||||
@ -63,7 +62,6 @@ var ExampleDomain = []cloudflare.DNSRecord{
|
||||
},
|
||||
{
|
||||
ID: "2345678901",
|
||||
ZoneID: "001",
|
||||
Name: "foobar.bar.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
TTL: 120,
|
||||
@ -72,7 +70,6 @@ var ExampleDomain = []cloudflare.DNSRecord{
|
||||
},
|
||||
{
|
||||
ID: "1231231233",
|
||||
ZoneID: "002",
|
||||
Name: "bar.foo.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
TTL: 1,
|
||||
@ -340,7 +337,6 @@ func AssertActions(t *testing.T, provider *CloudFlareProvider, endpoints []*endp
|
||||
}
|
||||
|
||||
err = provider.ApplyChanges(context.Background(), changes)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("cannot apply changes, %s", err)
|
||||
}
|
||||
@ -1179,7 +1175,6 @@ func TestProviderPropertiesIdempotency(t *testing.T) {
|
||||
"001": {
|
||||
{
|
||||
ID: "1234567890",
|
||||
ZoneID: "001",
|
||||
Name: "foobar.bar.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
TTL: 120,
|
||||
@ -1283,7 +1278,6 @@ func TestCloudflareComplexUpdate(t *testing.T) {
|
||||
planned := plan.Calculate()
|
||||
|
||||
err = provider.ApplyChanges(context.Background(), planned.Changes)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
@ -1335,7 +1329,6 @@ func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) {
|
||||
"001": {
|
||||
{
|
||||
ID: "1234567890",
|
||||
ZoneID: "001",
|
||||
Name: "foobar.bar.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
TTL: 1,
|
||||
|
||||
@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/digitalocean/godo"
|
||||
@ -158,13 +159,15 @@ func (p *DigitalOceanProvider) Records(ctx context.Context) ([]*endpoint.Endpoin
|
||||
endpoints := []*endpoint.Endpoint{}
|
||||
for _, zone := range zones {
|
||||
records, err := p.fetchRecords(ctx, zone.Name)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range records {
|
||||
if provider.SupportedRecordType(r.Type) {
|
||||
if p.SupportedRecordType(r.Type) {
|
||||
name := r.Name + "." + zone.Name
|
||||
data := r.Data
|
||||
|
||||
// root name is identified by @ and should be
|
||||
// translated to zone name for the endpoint entry.
|
||||
@ -172,7 +175,11 @@ func (p *DigitalOceanProvider) Records(ctx context.Context) ([]*endpoint.Endpoin
|
||||
name = zone.Name
|
||||
}
|
||||
|
||||
ep := endpoint.NewEndpointWithTTL(name, r.Type, endpoint.TTL(r.TTL), r.Data)
|
||||
if r.Type == endpoint.RecordTypeMX {
|
||||
data = fmt.Sprintf("%d %s", r.Priority, r.Data)
|
||||
}
|
||||
|
||||
ep := endpoint.NewEndpointWithTTL(name, r.Type, endpoint.TTL(r.TTL), data)
|
||||
|
||||
endpoints = append(endpoints, ep)
|
||||
}
|
||||
@ -283,16 +290,33 @@ func makeDomainEditRequest(domain, name, recordType, data string, ttl int) *godo
|
||||
|
||||
// For some reason the DO API requires the '.' at the end of "data" in case of CNAME request.
|
||||
// Example: {"type":"CNAME","name":"hello","data":"www.example.com."}
|
||||
if recordType == endpoint.RecordTypeCNAME && !strings.HasSuffix(data, ".") {
|
||||
if (recordType == endpoint.RecordTypeCNAME || recordType == endpoint.RecordTypeMX) && !strings.HasSuffix(data, ".") {
|
||||
data += "."
|
||||
}
|
||||
|
||||
return &godo.DomainRecordEditRequest{
|
||||
request := &godo.DomainRecordEditRequest{
|
||||
Name: adjustedName,
|
||||
Type: recordType,
|
||||
Data: data,
|
||||
TTL: ttl,
|
||||
}
|
||||
|
||||
if recordType == endpoint.RecordTypeMX {
|
||||
priority, domain, err := parseMxTarget(data)
|
||||
if err == nil {
|
||||
request.Priority = int(priority)
|
||||
request.Data = provider.EnsureTrailingDot(domain)
|
||||
} else {
|
||||
log.WithFields(log.Fields{
|
||||
"domain": domain,
|
||||
"dnsName": name,
|
||||
"recordType": recordType,
|
||||
"data": data,
|
||||
}).Warn("Unable to parse MX target")
|
||||
}
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
// submitChanges applies an instance of `digitalOceanChanges` to the DigitalOcean API.
|
||||
@ -303,13 +327,19 @@ func (p *DigitalOceanProvider) submitChanges(ctx context.Context, changes *digit
|
||||
}
|
||||
|
||||
for _, c := range changes.Creates {
|
||||
log.WithFields(log.Fields{
|
||||
logFields := log.Fields{
|
||||
"domain": c.Domain,
|
||||
"dnsName": c.Options.Name,
|
||||
"recordType": c.Options.Type,
|
||||
"data": c.Options.Data,
|
||||
"ttl": c.Options.TTL,
|
||||
}).Debug("Creating domain record")
|
||||
}
|
||||
|
||||
if c.Options.Type == endpoint.RecordTypeMX {
|
||||
logFields["priority"] = c.Options.Priority
|
||||
}
|
||||
|
||||
log.WithFields(logFields).Debug("Creating domain record")
|
||||
|
||||
if p.DryRun {
|
||||
continue
|
||||
@ -322,13 +352,17 @@ func (p *DigitalOceanProvider) submitChanges(ctx context.Context, changes *digit
|
||||
}
|
||||
|
||||
for _, u := range changes.Updates {
|
||||
log.WithFields(log.Fields{
|
||||
logFields := log.Fields{
|
||||
"domain": u.Domain,
|
||||
"dnsName": u.Options.Name,
|
||||
"recordType": u.Options.Type,
|
||||
"data": u.Options.Data,
|
||||
"ttl": u.Options.TTL,
|
||||
}).Debug("Updating domain record")
|
||||
}
|
||||
if u.Options.Type == endpoint.RecordTypeMX {
|
||||
logFields["priority"] = u.Options.Priority
|
||||
}
|
||||
log.WithFields(logFields).Debug("Updating domain record")
|
||||
|
||||
if p.DryRun {
|
||||
continue
|
||||
@ -589,6 +623,16 @@ func processDeleteActions(
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportedRecordType returns true if the record type is supported by the provider
|
||||
func (p *DigitalOceanProvider) SupportedRecordType(recordType string) bool {
|
||||
switch recordType {
|
||||
case "MX":
|
||||
return true
|
||||
default:
|
||||
return provider.SupportedRecordType(recordType)
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyChanges applies the given set of generic changes to the provider.
|
||||
func (p *DigitalOceanProvider) ApplyChanges(ctx context.Context, planChanges *plan.Changes) error {
|
||||
// TODO: This should only retrieve zones affected by the given `planChanges`.
|
||||
@ -617,3 +661,18 @@ func (p *DigitalOceanProvider) ApplyChanges(ctx context.Context, planChanges *pl
|
||||
|
||||
return p.submitChanges(ctx, &changes)
|
||||
}
|
||||
|
||||
func parseMxTarget(mxTarget string) (priority int64, exchange string, err error) {
|
||||
targetParts := strings.SplitN(mxTarget, " ", 2)
|
||||
if len(targetParts) != 2 {
|
||||
return priority, exchange, fmt.Errorf("mx target needs to be of form '10 example.com'")
|
||||
}
|
||||
|
||||
priorityRaw, exchange := targetParts[0], targetParts[1]
|
||||
priority, err = strconv.ParseInt(priorityRaw, 10, 32)
|
||||
if err != nil {
|
||||
return priority, exchange, fmt.Errorf("invalid priority specified")
|
||||
}
|
||||
|
||||
return priority, exchange, nil
|
||||
}
|
||||
|
||||
@ -100,6 +100,9 @@ func (m *mockDigitalOceanClient) Records(ctx context.Context, domain string, opt
|
||||
{ID: 1, Name: "foo.ext-dns-test", Type: "CNAME"},
|
||||
{ID: 2, Name: "bar.ext-dns-test", Type: "CNAME"},
|
||||
{ID: 3, Name: "@", Type: endpoint.RecordTypeCNAME},
|
||||
{ID: 4, Name: "@", Type: endpoint.RecordTypeMX, Priority: 10, Data: "mx1.foo.com."},
|
||||
{ID: 5, Name: "@", Type: endpoint.RecordTypeMX, Priority: 10, Data: "mx2.foo.com."},
|
||||
{ID: 6, Name: "@", Type: endpoint.RecordTypeTXT, Data: "SOME-TXT-TEXT"},
|
||||
}, &godo.Response{
|
||||
Links: &godo.Links{
|
||||
Pages: &godo.Pages{
|
||||
@ -344,6 +347,28 @@ func TestDigitalOceanMakeDomainEditRequest(t *testing.T) {
|
||||
Data: "bar.example.com.",
|
||||
TTL: customTTL,
|
||||
}, r4)
|
||||
|
||||
// Ensure that MX records have `.` appended.
|
||||
r5 := makeDomainEditRequest("example.com", "foo.example.com", endpoint.RecordTypeMX,
|
||||
"10 mx.example.com", digitalOceanRecordTTL)
|
||||
assert.Equal(t, &godo.DomainRecordEditRequest{
|
||||
Type: endpoint.RecordTypeMX,
|
||||
Name: "foo",
|
||||
Data: "mx.example.com.",
|
||||
Priority: 10,
|
||||
TTL: digitalOceanRecordTTL,
|
||||
}, r5)
|
||||
|
||||
// Ensure that MX records do not have an extra `.` appended if they already have a `.`
|
||||
r6 := makeDomainEditRequest("example.com", "foo.example.com", endpoint.RecordTypeMX,
|
||||
"10 mx.example.com.", digitalOceanRecordTTL)
|
||||
assert.Equal(t, &godo.DomainRecordEditRequest{
|
||||
Type: endpoint.RecordTypeMX,
|
||||
Name: "foo",
|
||||
Data: "mx.example.com.",
|
||||
Priority: 10,
|
||||
TTL: digitalOceanRecordTTL,
|
||||
}, r6)
|
||||
}
|
||||
|
||||
func TestDigitalOceanApplyChanges(t *testing.T) {
|
||||
@ -375,6 +400,8 @@ func TestDigitalOceanProcessCreateActions(t *testing.T) {
|
||||
"example.com": {
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeCNAME, "foo.example.com"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeMX, "10 mx.example.com"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "SOME-TXT-TEXT"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -382,7 +409,7 @@ func TestDigitalOceanProcessCreateActions(t *testing.T) {
|
||||
err := processCreateActions(recordsByDomain, createsByDomain, &changes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(changes.Creates))
|
||||
assert.Equal(t, 4, len(changes.Creates))
|
||||
assert.Equal(t, 0, len(changes.Updates))
|
||||
assert.Equal(t, 0, len(changes.Deletes))
|
||||
|
||||
@ -405,6 +432,25 @@ func TestDigitalOceanProcessCreateActions(t *testing.T) {
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
Options: &godo.DomainRecordEditRequest{
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeMX,
|
||||
Priority: 10,
|
||||
Data: "mx.example.com.",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
Options: &godo.DomainRecordEditRequest{
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Data: "SOME-TXT-TEXT",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !elementsMatch(t, expectedCreates, changes.Creates) {
|
||||
@ -436,6 +482,29 @@ func TestDigitalOceanProcessUpdateActions(t *testing.T) {
|
||||
Data: "foo.example.com.",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeMX,
|
||||
Data: "mx1.example.com.",
|
||||
Priority: 10,
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
{
|
||||
ID: 5,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeMX,
|
||||
Data: "mx2.example.com.",
|
||||
Priority: 10,
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
{
|
||||
ID: 6,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Data: "SOME_TXTX_TEXT",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -443,6 +512,8 @@ func TestDigitalOceanProcessUpdateActions(t *testing.T) {
|
||||
"example.com": {
|
||||
endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "10.11.12.13"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeCNAME, "bar.example.com"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeMX, "10 mx3.example.com"),
|
||||
endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "ANOTHER-TXT"),
|
||||
},
|
||||
}
|
||||
|
||||
@ -450,9 +521,9 @@ func TestDigitalOceanProcessUpdateActions(t *testing.T) {
|
||||
err := processUpdateActions(recordsByDomain, updatesByDomain, &changes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 2, len(changes.Creates))
|
||||
assert.Equal(t, 4, len(changes.Creates))
|
||||
assert.Equal(t, 0, len(changes.Updates))
|
||||
assert.Equal(t, 3, len(changes.Deletes))
|
||||
assert.Equal(t, 6, len(changes.Deletes))
|
||||
|
||||
expectedCreates := []*digitalOceanChangeCreate{
|
||||
{
|
||||
@ -473,6 +544,25 @@ func TestDigitalOceanProcessUpdateActions(t *testing.T) {
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
Options: &godo.DomainRecordEditRequest{
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeMX,
|
||||
Data: "mx3.example.com.",
|
||||
Priority: 10,
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
Options: &godo.DomainRecordEditRequest{
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Data: "ANOTHER-TXT",
|
||||
TTL: digitalOceanRecordTTL,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if !elementsMatch(t, expectedCreates, changes.Creates) {
|
||||
@ -492,6 +582,18 @@ func TestDigitalOceanProcessUpdateActions(t *testing.T) {
|
||||
Domain: "example.com",
|
||||
RecordID: 3,
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
RecordID: 4,
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
RecordID: 5,
|
||||
},
|
||||
{
|
||||
Domain: "example.com",
|
||||
RecordID: 6,
|
||||
},
|
||||
}
|
||||
|
||||
if !elementsMatch(t, expectedDeletes, changes.Deletes) {
|
||||
@ -597,6 +699,26 @@ func TestDigitalOceanGetMatchingDomainRecords(t *testing.T) {
|
||||
Type: endpoint.RecordTypeA,
|
||||
Data: "9.10.11.12",
|
||||
},
|
||||
{
|
||||
ID: 5,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeMX,
|
||||
Priority: 10,
|
||||
Data: "mx1.foo.com.",
|
||||
},
|
||||
{
|
||||
ID: 6,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeMX,
|
||||
Priority: 10,
|
||||
Data: "mx2.foo.com.",
|
||||
},
|
||||
{
|
||||
ID: 7,
|
||||
Name: "@",
|
||||
Type: endpoint.RecordTypeTXT,
|
||||
Data: "MYTXT",
|
||||
},
|
||||
}
|
||||
|
||||
ep1 := endpoint.NewEndpoint("foo.com", endpoint.RecordTypeCNAME)
|
||||
@ -627,6 +749,17 @@ func TestDigitalOceanGetMatchingDomainRecords(t *testing.T) {
|
||||
r2 := getMatchingDomainRecords(records, "example.com", ep4)
|
||||
assert.Equal(t, 1, len(r2))
|
||||
assert.Equal(t, "9.10.11.12", r2[0].Data)
|
||||
|
||||
ep5 := endpoint.NewEndpoint("example.com", endpoint.RecordTypeMX)
|
||||
r3 := getMatchingDomainRecords(records, "example.com", ep5)
|
||||
assert.Equal(t, 2, len(r3))
|
||||
assert.Equal(t, "mx1.foo.com.", r3[0].Data)
|
||||
assert.Equal(t, "mx2.foo.com.", r3[1].Data)
|
||||
|
||||
ep6 := endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT)
|
||||
r4 := getMatchingDomainRecords(records, "example.com", ep6)
|
||||
assert.Equal(t, 1, len(r4))
|
||||
assert.Equal(t, "MYTXT", r4[0].Data)
|
||||
}
|
||||
|
||||
func validateDigitalOceanZones(t *testing.T, zones []godo.Domain, expected []godo.Domain) {
|
||||
@ -663,7 +796,7 @@ func TestDigitalOceanAllRecords(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
require.Equal(t, 5, len(records))
|
||||
require.Equal(t, 7, len(records))
|
||||
|
||||
provider.Client = &mockDigitalOceanRecordsFail{}
|
||||
_, err = provider.Records(ctx)
|
||||
@ -678,11 +811,15 @@ func TestDigitalOceanMergeRecordsByNameType(t *testing.T) {
|
||||
endpoint.NewEndpoint("bar.example.com", "A", "1.2.3.4"),
|
||||
endpoint.NewEndpoint("foo.example.com", "A", "5.6.7.8"),
|
||||
endpoint.NewEndpoint("foo.example.com", "CNAME", "somewhere.out.there.com"),
|
||||
endpoint.NewEndpoint("bar.example.com", "MX", "10 bar.mx1.com"),
|
||||
endpoint.NewEndpoint("bar.example.com", "MX", "10 bar.mx2.com"),
|
||||
endpoint.NewEndpoint("foo.example.com", "TXT", "txtone"),
|
||||
endpoint.NewEndpoint("foo.example.com", "TXT", "txttwo"),
|
||||
}
|
||||
|
||||
merged := mergeEndpointsByNameType(xs)
|
||||
|
||||
assert.Equal(t, 3, len(merged))
|
||||
assert.Equal(t, 5, len(merged))
|
||||
sort.SliceStable(merged, func(i, j int) bool {
|
||||
if merged[i].DNSName != merged[j].DNSName {
|
||||
return merged[i].DNSName < merged[j].DNSName
|
||||
@ -693,14 +830,22 @@ func TestDigitalOceanMergeRecordsByNameType(t *testing.T) {
|
||||
assert.Equal(t, "A", merged[0].RecordType)
|
||||
assert.Equal(t, 1, len(merged[0].Targets))
|
||||
assert.Equal(t, "1.2.3.4", merged[0].Targets[0])
|
||||
|
||||
assert.Equal(t, "foo.example.com", merged[1].DNSName)
|
||||
assert.Equal(t, "A", merged[1].RecordType)
|
||||
assert.Equal(t, "MX", merged[1].RecordType)
|
||||
assert.Equal(t, 2, len(merged[1].Targets))
|
||||
assert.ElementsMatch(t, []string{"1.2.3.4", "5.6.7.8"}, merged[1].Targets)
|
||||
assert.ElementsMatch(t, []string{"10 bar.mx1.com", "10 bar.mx2.com"}, merged[1].Targets)
|
||||
|
||||
assert.Equal(t, "foo.example.com", merged[2].DNSName)
|
||||
assert.Equal(t, "CNAME", merged[2].RecordType)
|
||||
assert.Equal(t, 1, len(merged[2].Targets))
|
||||
assert.Equal(t, "somewhere.out.there.com", merged[2].Targets[0])
|
||||
assert.Equal(t, "A", merged[2].RecordType)
|
||||
assert.Equal(t, 2, len(merged[2].Targets))
|
||||
assert.ElementsMatch(t, []string{"1.2.3.4", "5.6.7.8"}, merged[2].Targets)
|
||||
|
||||
assert.Equal(t, "foo.example.com", merged[3].DNSName)
|
||||
assert.Equal(t, "CNAME", merged[3].RecordType)
|
||||
assert.Equal(t, 1, len(merged[3].Targets))
|
||||
assert.Equal(t, "somewhere.out.there.com", merged[3].Targets[0])
|
||||
|
||||
assert.Equal(t, "foo.example.com", merged[4].DNSName)
|
||||
assert.Equal(t, "TXT", merged[4].RecordType)
|
||||
assert.Equal(t, 2, len(merged[4].Targets))
|
||||
assert.ElementsMatch(t, []string{"txtone", "txttwo"}, merged[4].Targets)
|
||||
}
|
||||
|
||||
@ -194,7 +194,7 @@ func (p *GoogleProvider) Zones(ctx context.Context) (map[string]*dns.ManagedZone
|
||||
|
||||
log.Debugf("Matching zones against domain filters: %v", p.domainFilter)
|
||||
if err := p.managedZonesClient.List(p.project).Pages(ctx, f); err != nil {
|
||||
return nil, err
|
||||
return nil, provider.NewSoftError(fmt.Errorf("failed to list zones: %w", err))
|
||||
}
|
||||
|
||||
if len(zones) == 0 {
|
||||
@ -228,7 +228,7 @@ func (p *GoogleProvider) Records(ctx context.Context) (endpoints []*endpoint.End
|
||||
|
||||
for _, z := range zones {
|
||||
if err := p.resourceRecordSetsClient.List(p.project, z.Name).Pages(ctx, f); err != nil {
|
||||
return nil, err
|
||||
return nil, provider.NewSoftError(fmt.Errorf("failed to list records in zone %s: %w", z.Name, err))
|
||||
}
|
||||
}
|
||||
|
||||
@ -302,7 +302,7 @@ func (p *GoogleProvider) submitChange(ctx context.Context, change *dns.Change) e
|
||||
}
|
||||
|
||||
if _, err := p.changesClient.Create(p.project, zone, c).Do(); err != nil {
|
||||
return err
|
||||
return provider.NewSoftError(fmt.Errorf("failed to create changes: %w", err))
|
||||
}
|
||||
|
||||
time.Sleep(p.batchChangeInterval)
|
||||
|
||||
@ -59,7 +59,8 @@ func (m *mockManagedZonesCreateCall) Do(opts ...googleapi.CallOption) (*dns.Mana
|
||||
}
|
||||
|
||||
type mockManagedZonesListCall struct {
|
||||
project string
|
||||
project string
|
||||
zonesListSoftErr error
|
||||
}
|
||||
|
||||
func (m *mockManagedZonesListCall) Pages(ctx context.Context, f func(*dns.ManagedZonesListResponse) error) error {
|
||||
@ -71,22 +72,29 @@ func (m *mockManagedZonesListCall) Pages(ctx context.Context, f func(*dns.Manage
|
||||
}
|
||||
}
|
||||
|
||||
if m.zonesListSoftErr != nil {
|
||||
return m.zonesListSoftErr
|
||||
}
|
||||
|
||||
return f(&dns.ManagedZonesListResponse{ManagedZones: zones})
|
||||
}
|
||||
|
||||
type mockManagedZonesClient struct{}
|
||||
type mockManagedZonesClient struct {
|
||||
zonesErr error
|
||||
}
|
||||
|
||||
func (m *mockManagedZonesClient) Create(project string, managedZone *dns.ManagedZone) managedZonesCreateCallInterface {
|
||||
return &mockManagedZonesCreateCall{project: project, managedZone: managedZone}
|
||||
}
|
||||
|
||||
func (m *mockManagedZonesClient) List(project string) managedZonesListCallInterface {
|
||||
return &mockManagedZonesListCall{project: project}
|
||||
return &mockManagedZonesListCall{project: project, zonesListSoftErr: m.zonesErr}
|
||||
}
|
||||
|
||||
type mockResourceRecordSetsListCall struct {
|
||||
project string
|
||||
managedZone string
|
||||
project string
|
||||
managedZone string
|
||||
recordsListSoftErr error
|
||||
}
|
||||
|
||||
func (m *mockResourceRecordSetsListCall) Pages(ctx context.Context, f func(*dns.ResourceRecordSetsListResponse) error) error {
|
||||
@ -102,13 +110,19 @@ func (m *mockResourceRecordSetsListCall) Pages(ctx context.Context, f func(*dns.
|
||||
resp = append(resp, v)
|
||||
}
|
||||
|
||||
if m.recordsListSoftErr != nil {
|
||||
return m.recordsListSoftErr
|
||||
}
|
||||
|
||||
return f(&dns.ResourceRecordSetsListResponse{Rrsets: resp})
|
||||
}
|
||||
|
||||
type mockResourceRecordSetsClient struct{}
|
||||
type mockResourceRecordSetsClient struct {
|
||||
recordsErr error
|
||||
}
|
||||
|
||||
func (m *mockResourceRecordSetsClient) List(project string, managedZone string) resourceRecordSetsListCallInterface {
|
||||
return &mockResourceRecordSetsListCall{project: project, managedZone: managedZone}
|
||||
return &mockResourceRecordSetsListCall{project: project, managedZone: managedZone, recordsListSoftErr: m.recordsErr}
|
||||
}
|
||||
|
||||
type mockChangesCreateCall struct {
|
||||
@ -242,25 +256,12 @@ func TestGoogleZonesVisibilityFilterPrivatePeering(t *testing.T) {
|
||||
|
||||
zones, err := provider.Zones(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
validateZones(t, zones, map[string]*dns.ManagedZone{
|
||||
"svc-local": {Name: "svc-local", DnsName: "svc.local.", Id: 1005, Visibility: "private"},
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleZones(t *testing.T) {
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
|
||||
zones, err := provider.Zones(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
validateZones(t, zones, map[string]*dns.ManagedZone{
|
||||
"zone-1-ext-dns-test-2-gcp-zalan-do": {Name: "zone-1-ext-dns-test-2-gcp-zalan-do", DnsName: "zone-1.ext-dns-test-2.gcp.zalan.do."},
|
||||
"zone-2-ext-dns-test-2-gcp-zalan-do": {Name: "zone-2-ext-dns-test-2-gcp-zalan-do", DnsName: "zone-2.ext-dns-test-2.gcp.zalan.do."},
|
||||
"zone-3-ext-dns-test-2-gcp-zalan-do": {Name: "zone-3-ext-dns-test-2-gcp-zalan-do", DnsName: "zone-3.ext-dns-test-2.gcp.zalan.do."},
|
||||
})
|
||||
}
|
||||
|
||||
func TestGoogleRecords(t *testing.T) {
|
||||
originalEndpoints := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, endpoint.TTL(1), "1.2.3.4"),
|
||||
@ -268,7 +269,7 @@ func TestGoogleRecords(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("list-test-alias.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(3), "foo.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, originalEndpoints)
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, originalEndpoints, nil, nil)
|
||||
|
||||
records, err := provider.Records(context.Background())
|
||||
require.NoError(t, err)
|
||||
@ -299,6 +300,8 @@ func TestGoogleRecordsFilter(t *testing.T) {
|
||||
provider.NewZoneIDFilter([]string{""}),
|
||||
false,
|
||||
originalEndpoints,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
// these records should be filtered out since they don't match a hosted zone or domain filter.
|
||||
@ -343,6 +346,8 @@ func TestGoogleApplyChanges(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, googleRecordTTL, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, googleRecordTTL, "qux.elb.amazonaws.com"),
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
@ -407,7 +412,7 @@ func TestGoogleApplyChangesDryRun(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeCNAME, googleRecordTTL, "qux.elb.amazonaws.com"),
|
||||
}
|
||||
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), true, originalEndpoints)
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), true, originalEndpoints, nil, nil)
|
||||
|
||||
createRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("create-test.zone-1.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
@ -449,12 +454,12 @@ func TestGoogleApplyChangesDryRun(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGoogleApplyChangesEmpty(t *testing.T) {
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{}, nil, nil)
|
||||
assert.NoError(t, provider.ApplyChanges(context.Background(), &plan.Changes{}))
|
||||
}
|
||||
|
||||
func TestNewFilteredRecords(t *testing.T) {
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{})
|
||||
provider := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{""}), false, []*endpoint.Endpoint{}, nil, nil)
|
||||
|
||||
records := provider.newFilteredRecords([]*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.gcp.zalan.do", endpoint.RecordTypeA, 1, "8.8.4.4"),
|
||||
@ -603,6 +608,26 @@ func TestGoogleBatchChangeSetExceedingNameChange(t *testing.T) {
|
||||
require.Equal(t, 0, len(batchCs))
|
||||
}
|
||||
|
||||
func TestSoftErrListZonesConflict(t *testing.T) {
|
||||
p := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{}), false, []*endpoint.Endpoint{}, provider.NewSoftError(fmt.Errorf("failed to list zones")), nil)
|
||||
|
||||
zones, err := p.Zones(context.Background())
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, provider.SoftError)
|
||||
|
||||
require.Empty(t, zones)
|
||||
}
|
||||
|
||||
func TestSoftErrListRecordsConflict(t *testing.T) {
|
||||
p := newGoogleProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.gcp.zalan.do."}), provider.NewZoneIDFilter([]string{}), false, []*endpoint.Endpoint{}, nil, provider.NewSoftError(fmt.Errorf("failed to list records in zone")))
|
||||
|
||||
records, err := p.Records(context.Background())
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, provider.SoftError)
|
||||
|
||||
require.Empty(t, records)
|
||||
}
|
||||
|
||||
func sortChangesByName(cs *dns.Change) {
|
||||
sort.SliceStable(cs.Additions, func(i, j int) bool {
|
||||
return cs.Additions[i].Name < cs.Additions[j].Name
|
||||
@ -647,7 +672,7 @@ func validateChangeRecord(t *testing.T, record *dns.ResourceRecordSet, expected
|
||||
assert.Equal(t, expected.Type, record.Type)
|
||||
}
|
||||
|
||||
func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
|
||||
func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, zoneTypeFilter provider.ZoneTypeFilter, dryRun bool, _ []*endpoint.Endpoint) *GoogleProvider {
|
||||
provider := &GoogleProvider{
|
||||
project: "zalando-external-dns-test",
|
||||
dryRun: false,
|
||||
@ -694,7 +719,6 @@ func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilt
|
||||
Visibility: "private",
|
||||
})
|
||||
|
||||
|
||||
createZone(t, provider, &dns.ManagedZone{
|
||||
Name: "svc-local",
|
||||
DnsName: "svc.local.",
|
||||
@ -703,27 +727,31 @@ func newGoogleProviderZoneOverlap(t *testing.T, domainFilter endpoint.DomainFilt
|
||||
})
|
||||
|
||||
createZone(t, provider, &dns.ManagedZone{
|
||||
Name: "svc-local-peer",
|
||||
DnsName: "svc.local.",
|
||||
Id: 10006,
|
||||
Visibility: "private",
|
||||
Name: "svc-local-peer",
|
||||
DnsName: "svc.local.",
|
||||
Id: 10006,
|
||||
Visibility: "private",
|
||||
PeeringConfig: &dns.ManagedZonePeeringConfig{TargetNetwork: nil},
|
||||
})
|
||||
|
||||
|
||||
provider.dryRun = dryRun
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func newGoogleProvider(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint) *GoogleProvider {
|
||||
func newGoogleProvider(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, dryRun bool, records []*endpoint.Endpoint, zonesErr, recordsErr error) *GoogleProvider {
|
||||
provider := &GoogleProvider{
|
||||
project: "zalando-external-dns-test",
|
||||
dryRun: false,
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
resourceRecordSetsClient: &mockResourceRecordSetsClient{},
|
||||
managedZonesClient: &mockManagedZonesClient{},
|
||||
changesClient: &mockChangesClient{},
|
||||
project: "zalando-external-dns-test",
|
||||
dryRun: false,
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
resourceRecordSetsClient: &mockResourceRecordSetsClient{
|
||||
recordsErr: recordsErr,
|
||||
},
|
||||
managedZonesClient: &mockManagedZonesClient{
|
||||
zonesErr: zonesErr,
|
||||
},
|
||||
changesClient: &mockChangesClient{},
|
||||
}
|
||||
|
||||
createZone(t, provider, &dns.ManagedZone{
|
||||
@ -754,10 +782,10 @@ func newGoogleProvider(t *testing.T, domainFilter endpoint.DomainFilter, zoneIDF
|
||||
return provider
|
||||
}
|
||||
|
||||
func createZone(t *testing.T, provider *GoogleProvider, zone *dns.ManagedZone) {
|
||||
func createZone(t *testing.T, p *GoogleProvider, zone *dns.ManagedZone) {
|
||||
zone.Description = "Testing zone for kubernetes.io/external-dns"
|
||||
|
||||
if _, err := provider.managedZonesClient.Create("zalando-external-dns-test", zone).Do(); err != nil {
|
||||
if _, err := p.managedZonesClient.Create("zalando-external-dns-test", zone).Do(); err != nil {
|
||||
if err, ok := err.(*googleapi.Error); !ok || err.Code != http.StatusConflict {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
@ -770,8 +798,7 @@ func setupGoogleRecords(t *testing.T, provider *GoogleProvider, endpoints []*end
|
||||
clearGoogleRecords(t, provider, "zone-3-ext-dns-test-2-gcp-zalan-do")
|
||||
|
||||
ctx := context.Background()
|
||||
records, err := provider.Records(ctx)
|
||||
require.NoError(t, err)
|
||||
records, _ := provider.Records(ctx)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{})
|
||||
|
||||
@ -779,15 +806,15 @@ func setupGoogleRecords(t *testing.T, provider *GoogleProvider, endpoints []*end
|
||||
Create: endpoints,
|
||||
}))
|
||||
|
||||
records, err = provider.Records(ctx)
|
||||
require.NoError(t, err)
|
||||
records, _ = provider.Records(ctx)
|
||||
|
||||
validateEndpoints(t, records, endpoints)
|
||||
}
|
||||
|
||||
func clearGoogleRecords(t *testing.T, provider *GoogleProvider, zone string) {
|
||||
recordSets := []*dns.ResourceRecordSet{}
|
||||
require.NoError(t, provider.resourceRecordSetsClient.List(provider.project, zone).Pages(context.Background(), func(resp *dns.ResourceRecordSetsListResponse) error {
|
||||
|
||||
provider.resourceRecordSetsClient.List(provider.project, zone).Pages(context.Background(), func(resp *dns.ResourceRecordSetsListResponse) error {
|
||||
for _, r := range resp.Rrsets {
|
||||
switch r.Type {
|
||||
case endpoint.RecordTypeA, endpoint.RecordTypeCNAME:
|
||||
@ -795,7 +822,7 @@ func clearGoogleRecords(t *testing.T, provider *GoogleProvider, zone string) {
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
})
|
||||
|
||||
if len(recordSets) != 0 {
|
||||
_, err := provider.changesClient.Create(provider.project, zone, &dns.Change{
|
||||
|
||||
@ -22,6 +22,7 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -43,9 +44,7 @@ type pdnsChangeType string
|
||||
const (
|
||||
apiBase = "/api/v1"
|
||||
|
||||
// Unless we use something like pdnsproxy (discontinued upstream), this value will _always_ be localhost
|
||||
defaultServerID = "localhost"
|
||||
defaultTTL = 300
|
||||
defaultTTL = 300
|
||||
|
||||
// PdnsDelete and PdnsReplace are effectively an enum for "pgo.RrSet.changetype"
|
||||
// TODO: Can we somehow get this from the pgo swagger client library itself?
|
||||
@ -66,6 +65,7 @@ type PDNSConfig struct {
|
||||
DomainFilter endpoint.DomainFilter
|
||||
DryRun bool
|
||||
Server string
|
||||
ServerID string
|
||||
APIKey string
|
||||
TLSConfig TLSConfig
|
||||
}
|
||||
@ -137,6 +137,7 @@ type PDNSAPIProvider interface {
|
||||
// PDNSAPIClient : Struct that encapsulates all the PowerDNS specific implementation details
|
||||
type PDNSAPIClient struct {
|
||||
dryRun bool
|
||||
serverID string
|
||||
authCtx context.Context
|
||||
client *pgo.APIClient
|
||||
domainFilter endpoint.DomainFilter
|
||||
@ -146,7 +147,7 @@ type PDNSAPIClient struct {
|
||||
// ref: https://doc.powerdns.com/authoritative/http-api/zone.html#get--servers-server_id-zones
|
||||
func (c *PDNSAPIClient) ListZones() (zones []pgo.Zone, resp *http.Response, err error) {
|
||||
for i := 0; i < retryLimit; i++ {
|
||||
zones, resp, err = c.client.ZonesApi.ListZones(c.authCtx, defaultServerID)
|
||||
zones, resp, err = c.client.ZonesApi.ListZones(c.authCtx, c.serverID)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to fetch zones %v", err)
|
||||
log.Debugf("Retrying ListZones() ... %d", i)
|
||||
@ -156,8 +157,7 @@ func (c *PDNSAPIClient) ListZones() (zones []pgo.Zone, resp *http.Response, err
|
||||
return zones, resp, err
|
||||
}
|
||||
|
||||
log.Errorf("Unable to fetch zones. %v", err)
|
||||
return zones, resp, err
|
||||
return zones, resp, provider.NewSoftError(fmt.Errorf("unable to list zones: %v", err))
|
||||
}
|
||||
|
||||
// PartitionZones : Method returns a slice of zones that adhere to the domain filter and a slice of ones that does not adhere to the filter
|
||||
@ -180,7 +180,7 @@ func (c *PDNSAPIClient) PartitionZones(zones []pgo.Zone) (filteredZones []pgo.Zo
|
||||
// ref: https://doc.powerdns.com/authoritative/http-api/zone.html#get--servers-server_id-zones-zone_id
|
||||
func (c *PDNSAPIClient) ListZone(zoneID string) (zone pgo.Zone, resp *http.Response, err error) {
|
||||
for i := 0; i < retryLimit; i++ {
|
||||
zone, resp, err = c.client.ZonesApi.ListZone(c.authCtx, defaultServerID, zoneID)
|
||||
zone, resp, err = c.client.ZonesApi.ListZone(c.authCtx, c.serverID, zoneID)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to fetch zone %v", err)
|
||||
log.Debugf("Retrying ListZone() ... %d", i)
|
||||
@ -190,15 +190,14 @@ func (c *PDNSAPIClient) ListZone(zoneID string) (zone pgo.Zone, resp *http.Respo
|
||||
return zone, resp, err
|
||||
}
|
||||
|
||||
log.Errorf("Unable to list zone. %v", err)
|
||||
return zone, resp, err
|
||||
return zone, resp, provider.NewSoftError(fmt.Errorf("unable to list zone: %v", err))
|
||||
}
|
||||
|
||||
// PatchZone : Method used to update the contents of a particular zone from PowerDNS
|
||||
// ref: https://doc.powerdns.com/authoritative/http-api/zone.html#patch--servers-server_id-zones-zone_id
|
||||
func (c *PDNSAPIClient) PatchZone(zoneID string, zoneStruct pgo.Zone) (resp *http.Response, err error) {
|
||||
for i := 0; i < retryLimit; i++ {
|
||||
resp, err = c.client.ZonesApi.PatchZone(c.authCtx, defaultServerID, zoneID, zoneStruct)
|
||||
resp, err = c.client.ZonesApi.PatchZone(c.authCtx, c.serverID, zoneID, zoneStruct)
|
||||
if err != nil {
|
||||
log.Debugf("Unable to patch zone %v", err)
|
||||
log.Debugf("Retrying PatchZone() ... %d", i)
|
||||
@ -208,8 +207,7 @@ func (c *PDNSAPIClient) PatchZone(zoneID string, zoneStruct pgo.Zone) (resp *htt
|
||||
return resp, err
|
||||
}
|
||||
|
||||
log.Errorf("Unable to patch zone. %v", err)
|
||||
return resp, err
|
||||
return resp, provider.NewSoftError(fmt.Errorf("unable to patch zone: %v", err))
|
||||
}
|
||||
|
||||
// PDNSProvider is an implementation of the Provider interface for PowerDNS
|
||||
@ -245,6 +243,7 @@ func NewPDNSProvider(ctx context.Context, config PDNSConfig) (*PDNSProvider, err
|
||||
provider := &PDNSProvider{
|
||||
client: &PDNSAPIClient{
|
||||
dryRun: config.DryRun,
|
||||
serverID: config.ServerID,
|
||||
authCtx: context.WithValue(ctx, pgo.ContextAPIKey, pgo.APIKey{Key: config.APIKey}),
|
||||
client: pgo.NewAPIClient(pdnsClientConfig),
|
||||
domainFilter: config.DomainFilter,
|
||||
@ -314,7 +313,7 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
|
||||
records := []pgo.Record{}
|
||||
RecordType_ := ep.RecordType
|
||||
for _, t := range ep.Targets {
|
||||
if ep.RecordType == "CNAME" || ep.RecordType == "ALIAS" {
|
||||
if ep.RecordType == "CNAME" || ep.RecordType == "ALIAS" || ep.RecordType == "MX" || ep.RecordType == "SRV" {
|
||||
t = provider.EnsureTrailingDot(t)
|
||||
}
|
||||
records = append(records, pgo.Record{Content: t})
|
||||
@ -335,7 +334,7 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
|
||||
// DELETEs explicitly forbid a TTL, therefore only PATCHes need the TTL
|
||||
if changetype == PdnsReplace {
|
||||
if int64(ep.RecordTTL) > int64(math.MaxInt32) {
|
||||
return nil, errors.New("value of record TTL overflows, limited to int32")
|
||||
return nil, provider.NewSoftError(fmt.Errorf("value of record TTL overflows, limited to int32"))
|
||||
}
|
||||
if ep.RecordTTL == 0 {
|
||||
// No TTL was specified for the record, we use the default
|
||||
@ -418,8 +417,7 @@ func (p *PDNSProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpo
|
||||
for _, zone := range filteredZones {
|
||||
z, _, err := p.client.ListZone(zone.Id)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to fetch Records")
|
||||
return nil, err
|
||||
return nil, provider.NewSoftError(fmt.Errorf("unable to fetch records: %v", err))
|
||||
}
|
||||
|
||||
for _, rr := range z.Rrsets {
|
||||
|
||||
@ -18,7 +18,7 @@ package pdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -29,6 +29,7 @@ import (
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// FIXME: What do we do about labels?
|
||||
@ -117,6 +118,27 @@ var (
|
||||
},
|
||||
}
|
||||
|
||||
// RRSet with MX record
|
||||
RRSetMXRecord = pgo.RrSet{
|
||||
Name: "example.com.",
|
||||
Type_: "MX",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "10 mailhost1.example.com", Disabled: false, SetPtr: false},
|
||||
{Content: "10 mailhost2.example.com", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
|
||||
// RRSet with SRV record
|
||||
RRSetSRVRecord = pgo.RrSet{
|
||||
Name: "_service._tls.example.com.",
|
||||
Type_: "SRV",
|
||||
Ttl: 300,
|
||||
Records: []pgo.Record{
|
||||
{Content: "100 1 443 service.example.com", Disabled: false, SetPtr: false},
|
||||
},
|
||||
}
|
||||
|
||||
endpointsDisabledRecord = []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"),
|
||||
}
|
||||
@ -144,6 +166,8 @@ var (
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "'would smell as sweet'"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8", "8.8.4.4", "4.4.4.4"),
|
||||
endpoint.NewEndpointWithTTL("alias.example.com", endpoint.RecordTypeCNAME, endpoint.TTL(300), "example.by.any.other.name.com"),
|
||||
endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeMX, endpoint.TTL(300), "10 mailhost1.example.com", "10 mailhost2.example.com"),
|
||||
endpoint.NewEndpointWithTTL("_service._tls.example.com", endpoint.RecordTypeSRV, endpoint.TTL(300), "100 1 443 service.example.com"),
|
||||
}
|
||||
|
||||
endpointsMultipleZones = []*endpoint.Endpoint{
|
||||
@ -233,7 +257,7 @@ var (
|
||||
Type_: "Zone",
|
||||
Url: "/api/v1/servers/localhost/zones/example.com.",
|
||||
Kind: "Native",
|
||||
Rrsets: []pgo.RrSet{RRSetCNAMERecord, RRSetTXTRecord, RRSetMultipleRecords, RRSetALIASRecord},
|
||||
Rrsets: []pgo.RrSet{RRSetCNAMERecord, RRSetTXTRecord, RRSetMultipleRecords, RRSetALIASRecord, RRSetMXRecord, RRSetSRVRecord},
|
||||
}
|
||||
|
||||
ZoneEmptyToSimplePatch = pgo.Zone{
|
||||
@ -691,7 +715,7 @@ type PDNSAPIClientStubPatchZoneFailure struct {
|
||||
|
||||
// Just overwrite the PatchZone method to introduce a failure
|
||||
func (c *PDNSAPIClientStubPatchZoneFailure) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error) {
|
||||
return nil, errors.New("Generic PDNS Error")
|
||||
return nil, provider.NewSoftError(fmt.Errorf("Generic PDNS Error"))
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
@ -703,7 +727,7 @@ type PDNSAPIClientStubListZoneFailure struct {
|
||||
|
||||
// Just overwrite the ListZone method to introduce a failure
|
||||
func (c *PDNSAPIClientStubListZoneFailure) ListZone(zoneID string) (pgo.Zone, *http.Response, error) {
|
||||
return pgo.Zone{}, nil, errors.New("Generic PDNS Error")
|
||||
return pgo.Zone{}, nil, provider.NewSoftError(fmt.Errorf("Generic PDNS Error"))
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
@ -715,7 +739,7 @@ type PDNSAPIClientStubListZonesFailure struct {
|
||||
|
||||
// Just overwrite the ListZones method to introduce a failure
|
||||
func (c *PDNSAPIClientStubListZonesFailure) ListZones() ([]pgo.Zone, *http.Response, error) {
|
||||
return []pgo.Zone{}, nil, errors.New("Generic PDNS Error")
|
||||
return []pgo.Zone{}, nil, provider.NewSoftError(fmt.Errorf("Generic PDNS Error"))
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
@ -879,12 +903,14 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSRecords() {
|
||||
}
|
||||
_, err = p.Records(ctx)
|
||||
assert.NotNil(suite.T(), err)
|
||||
assert.ErrorIs(suite.T(), err, provider.SoftError)
|
||||
|
||||
p = &PDNSProvider{
|
||||
client: &PDNSAPIClientStubListZonesFailure{},
|
||||
}
|
||||
_, err = p.Records(ctx)
|
||||
assert.NotNil(suite.T(), err)
|
||||
assert.ErrorIs(suite.T(), err, provider.SoftError)
|
||||
}
|
||||
|
||||
func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZones() {
|
||||
@ -944,6 +970,20 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZones() {
|
||||
}
|
||||
}
|
||||
|
||||
// Check endpoints of type MX and SRV always have their values end with a trailing dot.
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsMixedRecords, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
|
||||
for _, z := range zlist {
|
||||
for _, rs := range z.Rrsets {
|
||||
if rs.Type_ == "MX" || rs.Type_ == "SRV" {
|
||||
for _, r := range rs.Records {
|
||||
assert.Equal(suite.T(), uint8(0x2e), r.Content[len(r.Content)-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check endpoints of type CNAME are converted to ALIAS on the domain apex
|
||||
zlist, err = p.ConvertEndpointsToZones(endpointsApexRecords, PdnsReplace)
|
||||
assert.Nil(suite.T(), err)
|
||||
@ -1024,6 +1064,7 @@ func (suite *NewPDNSProviderTestSuite) TestPDNSmutateRecords() {
|
||||
// Check inserting endpoints from a single zone
|
||||
err = p.mutateRecords(endpointsSimpleRecord, pdnsChangeType("REPLACE"))
|
||||
assert.NotNil(suite.T(), err)
|
||||
assert.ErrorIs(suite.T(), err, provider.SoftError)
|
||||
}
|
||||
|
||||
func (suite *NewPDNSProviderTestSuite) TestPDNSClientPartitionZones() {
|
||||
|
||||
@ -1,551 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 rdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
const (
|
||||
etcdTimeout = 5 * time.Second
|
||||
rdnsMaxHosts = 10
|
||||
rdnsOriginalLabel = "originalText"
|
||||
rdnsPrefix = "/rdnsv3"
|
||||
rdnsTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
|
||||
// RDNSClient is an interface to work with Rancher DNS(RDNS) records in etcdv3 backend.
|
||||
type RDNSClient interface {
|
||||
Get(key string) ([]RDNSRecord, error)
|
||||
List(rootDomain string) ([]RDNSRecord, error)
|
||||
Set(value RDNSRecord) error
|
||||
Delete(key string) error
|
||||
}
|
||||
|
||||
// RDNSConfig contains configuration to create a new Rancher DNS(RDNS) provider.
|
||||
type RDNSConfig struct {
|
||||
DryRun bool
|
||||
DomainFilter endpoint.DomainFilter
|
||||
RootDomain string
|
||||
}
|
||||
|
||||
// RDNSProvider is an implementation of Provider for Rancher DNS(RDNS).
|
||||
type RDNSProvider struct {
|
||||
provider.BaseProvider
|
||||
client RDNSClient
|
||||
dryRun bool
|
||||
domainFilter endpoint.DomainFilter
|
||||
rootDomain string
|
||||
}
|
||||
|
||||
// RDNSRecord represents Rancher DNS(RDNS) etcdv3 record.
|
||||
type RDNSRecord struct {
|
||||
AggregationHosts []string `json:"aggregation_hosts,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
TTL uint32 `json:"ttl,omitempty"`
|
||||
Key string `json:"-"`
|
||||
}
|
||||
|
||||
// RDNSRecordType represents Rancher DNS(RDNS) etcdv3 record type.
|
||||
type RDNSRecordType struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Domain string `json:"domain,omitempty"`
|
||||
}
|
||||
|
||||
type etcdv3Client struct {
|
||||
client *clientv3.Client
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
var _ RDNSClient = etcdv3Client{}
|
||||
|
||||
// NewRDNSProvider initializes a new Rancher DNS(RDNS) based Provider.
|
||||
func NewRDNSProvider(config RDNSConfig) (*RDNSProvider, error) {
|
||||
client, err := newEtcdv3Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domain := os.Getenv("RDNS_ROOT_DOMAIN")
|
||||
if domain == "" {
|
||||
return nil, errors.New("needed root domain environment")
|
||||
}
|
||||
return &RDNSProvider{
|
||||
client: client,
|
||||
dryRun: config.DryRun,
|
||||
domainFilter: config.DomainFilter,
|
||||
rootDomain: domain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Records returns all DNS records found in Rancher DNS(RDNS) etcdv3 backend. Depending on the record fields
|
||||
// it may be mapped to one or two records of type A, TXT, A+TXT.
|
||||
func (p RDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
var result []*endpoint.Endpoint
|
||||
|
||||
rs, err := p.client.List(p.rootDomain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range rs {
|
||||
domains := strings.Split(strings.TrimPrefix(r.Key, rdnsPrefix+"/"), "/")
|
||||
keyToDNSNameSplits(domains)
|
||||
dnsName := strings.Join(domains, ".")
|
||||
if !p.domainFilter.Match(dnsName) {
|
||||
continue
|
||||
}
|
||||
|
||||
// only return rdnsMaxHosts at most
|
||||
if len(r.AggregationHosts) > 0 {
|
||||
if len(r.AggregationHosts) > rdnsMaxHosts {
|
||||
r.AggregationHosts = r.AggregationHosts[:rdnsMaxHosts]
|
||||
}
|
||||
ep := endpoint.NewEndpointWithTTL(
|
||||
dnsName,
|
||||
endpoint.RecordTypeA,
|
||||
endpoint.TTL(r.TTL),
|
||||
r.AggregationHosts...,
|
||||
)
|
||||
ep.Labels[rdnsOriginalLabel] = r.Text
|
||||
result = append(result, ep)
|
||||
}
|
||||
if r.Text != "" {
|
||||
ep := endpoint.NewEndpoint(
|
||||
dnsName,
|
||||
endpoint.RecordTypeTXT,
|
||||
r.Text,
|
||||
)
|
||||
result = append(result, ep)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ApplyChanges stores changes back to etcdv3 converting them to Rancher DNS(RDNS) format and aggregating A and TXT records.
|
||||
func (p RDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
grouped := map[string][]*endpoint.Endpoint{}
|
||||
|
||||
for _, ep := range changes.Create {
|
||||
grouped[ep.DNSName] = append(grouped[ep.DNSName], ep)
|
||||
}
|
||||
|
||||
for _, ep := range changes.UpdateNew {
|
||||
if ep.RecordType == endpoint.RecordTypeA {
|
||||
// append useless domain records to the changes.Delete
|
||||
if err := p.filterAndRemoveUseless(ep, changes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
grouped[ep.DNSName] = append(grouped[ep.DNSName], ep)
|
||||
}
|
||||
|
||||
for dnsName, group := range grouped {
|
||||
if !p.domainFilter.Match(dnsName) {
|
||||
log.Debugf("Skipping record %s because it was filtered out by the specified --domain-filter", dnsName)
|
||||
continue
|
||||
}
|
||||
|
||||
var rs []RDNSRecord
|
||||
|
||||
for _, ep := range group {
|
||||
if ep.RecordType == endpoint.RecordTypeTXT {
|
||||
continue
|
||||
}
|
||||
for _, target := range ep.Targets {
|
||||
rs = append(rs, RDNSRecord{
|
||||
Host: target,
|
||||
Text: ep.Labels[rdnsOriginalLabel],
|
||||
Key: keyFor(ep.DNSName) + "/" + formatKey(target),
|
||||
TTL: uint32(ep.RecordTTL),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add the TXT attribute to the existing A record
|
||||
for _, ep := range group {
|
||||
if ep.RecordType != endpoint.RecordTypeTXT {
|
||||
continue
|
||||
}
|
||||
for i, r := range rs {
|
||||
if strings.Contains(r.Key, keyFor(ep.DNSName)) {
|
||||
r.Text = ep.Targets[0]
|
||||
rs[i] = r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range rs {
|
||||
log.Infof("Add/set key %s to Host=%s, Text=%s, TTL=%d", r.Key, r.Host, r.Text, r.TTL)
|
||||
if !p.dryRun {
|
||||
err := p.client.Set(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ep := range changes.Delete {
|
||||
key := keyFor(ep.DNSName)
|
||||
log.Infof("Delete key %s", key)
|
||||
if !p.dryRun {
|
||||
err := p.client.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// filterAndRemoveUseless filter and remove useless records.
|
||||
func (p *RDNSProvider) filterAndRemoveUseless(ep *endpoint.Endpoint, changes *plan.Changes) error {
|
||||
rs, err := p.client.Get(keyFor(ep.DNSName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range rs {
|
||||
exist := false
|
||||
for _, target := range ep.Targets {
|
||||
if strings.Contains(r.Key, formatKey(target)) {
|
||||
exist = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
ds := strings.Split(strings.TrimPrefix(r.Key, rdnsPrefix+"/"), "/")
|
||||
keyToDNSNameSplits(ds)
|
||||
changes.Delete = append(changes.Delete, &endpoint.Endpoint{
|
||||
DNSName: strings.Join(ds, "."),
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newEtcdv3Client is an etcdv3 client constructor.
|
||||
func newEtcdv3Client() (RDNSClient, error) {
|
||||
cfg := &clientv3.Config{}
|
||||
|
||||
endpoints := os.Getenv("ETCD_URLS")
|
||||
ca := os.Getenv("ETCD_CA_FILE")
|
||||
cert := os.Getenv("ETCD_CERT_FILE")
|
||||
key := os.Getenv("ETCD_KEY_FILE")
|
||||
name := os.Getenv("ETCD_TLS_SERVER_NAME")
|
||||
insecure := os.Getenv("ETCD_TLS_INSECURE")
|
||||
|
||||
if endpoints == "" {
|
||||
endpoints = "http://localhost:2379"
|
||||
}
|
||||
|
||||
urls := strings.Split(endpoints, ",")
|
||||
scheme := strings.ToLower(urls[0])[0:strings.Index(strings.ToLower(urls[0]), "://")]
|
||||
|
||||
switch scheme {
|
||||
case "http":
|
||||
cfg.Endpoints = urls
|
||||
case "https":
|
||||
var certificates []tls.Certificate
|
||||
|
||||
insecure = strings.ToLower(insecure)
|
||||
isInsecure := insecure == "true" || insecure == "yes" || insecure == "1"
|
||||
|
||||
if ca != "" && key == "" || cert == "" && key != "" {
|
||||
return nil, errors.New("either both cert and key or none must be provided")
|
||||
}
|
||||
|
||||
if cert != "" {
|
||||
cert, err := tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load TLS cert: %w", err)
|
||||
}
|
||||
certificates = append(certificates, cert)
|
||||
}
|
||||
|
||||
config := &tls.Config{
|
||||
Certificates: certificates,
|
||||
InsecureSkipVerify: isInsecure,
|
||||
ServerName: name,
|
||||
}
|
||||
|
||||
if ca != "" {
|
||||
roots := x509.NewCertPool()
|
||||
pem, err := os.ReadFile(ca)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %w", ca, err)
|
||||
}
|
||||
ok := roots.AppendCertsFromPEM(pem)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not read root certs: %w", err)
|
||||
}
|
||||
config.RootCAs = roots
|
||||
}
|
||||
|
||||
cfg.Endpoints = urls
|
||||
cfg.TLS = config
|
||||
default:
|
||||
return nil, errors.New("etcdv3 URLs must start with either http:// or https://")
|
||||
}
|
||||
|
||||
c, err := clientv3.New(*cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return etcdv3Client{c, context.Background()}, nil
|
||||
}
|
||||
|
||||
// Get return A records stored in etcdv3 stored anywhere under the given key (recursively).
|
||||
func (c etcdv3Client) Get(key string) ([]RDNSRecord, error) {
|
||||
ctx, cancel := context.WithTimeout(c.ctx, rdnsTimeout)
|
||||
defer cancel()
|
||||
|
||||
result, err := c.client.Get(ctx, key, clientv3.WithPrefix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs := make([]RDNSRecord, 0)
|
||||
for _, v := range result.Kvs {
|
||||
r := new(RDNSRecord)
|
||||
if err := json.Unmarshal(v.Value, r); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", v.Key, err)
|
||||
}
|
||||
r.Key = string(v.Key)
|
||||
rs = append(rs, *r)
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// List return all records stored in etcdv3 stored anywhere under the given rootDomain (recursively).
|
||||
func (c etcdv3Client) List(rootDomain string) ([]RDNSRecord, error) {
|
||||
ctx, cancel := context.WithTimeout(c.ctx, rdnsTimeout)
|
||||
defer cancel()
|
||||
|
||||
path := keyFor(rootDomain)
|
||||
|
||||
result, err := c.client.Get(ctx, path, clientv3.WithPrefix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.aggregationRecords(result)
|
||||
}
|
||||
|
||||
// Set persists records data into etcdv3.
|
||||
func (c etcdv3Client) Set(r RDNSRecord) error {
|
||||
ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
|
||||
defer cancel()
|
||||
|
||||
v, err := json.Marshal(&r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.Text == "" && r.Host == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = c.client.Put(ctx, r.Key, string(v))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes record from etcdv3.
|
||||
func (c etcdv3Client) Delete(key string) error {
|
||||
ctx, cancel := context.WithTimeout(c.ctx, etcdTimeout)
|
||||
defer cancel()
|
||||
|
||||
_, err := c.client.Delete(ctx, key, clientv3.WithPrefix())
|
||||
return err
|
||||
}
|
||||
|
||||
// aggregationRecords will aggregation multi A records under the given path.
|
||||
// e.g. A: 1_1_1_1.xxx.lb.rancher.cloud & 2_2_2_2.sample.lb.rancher.cloud => sample.lb.rancher.cloud {"aggregation_hosts": ["1.1.1.1", "2.2.2.2"]}
|
||||
// e.g. TXT: sample.lb.rancher.cloud => sample.lb.rancher.cloud => {"text": "xxx"}
|
||||
func (c etcdv3Client) aggregationRecords(result *clientv3.GetResponse) ([]RDNSRecord, error) {
|
||||
var rs []RDNSRecord
|
||||
bx := make(map[RDNSRecordType]RDNSRecord)
|
||||
|
||||
for _, n := range result.Kvs {
|
||||
r := new(RDNSRecord)
|
||||
if err := json.Unmarshal(n.Value, r); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", n.Key, err)
|
||||
}
|
||||
|
||||
r.Key = string(n.Key)
|
||||
|
||||
if r.Host == "" && r.Text == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.Host != "" {
|
||||
c := RDNSRecord{
|
||||
AggregationHosts: r.AggregationHosts,
|
||||
Host: r.Host,
|
||||
Text: r.Text,
|
||||
TTL: r.TTL,
|
||||
Key: r.Key,
|
||||
}
|
||||
n, isContinue := appendRecords(c, endpoint.RecordTypeA, bx, rs)
|
||||
if isContinue {
|
||||
continue
|
||||
}
|
||||
rs = n
|
||||
}
|
||||
|
||||
if r.Text != "" && r.Host == "" {
|
||||
c := RDNSRecord{
|
||||
AggregationHosts: []string{},
|
||||
Host: r.Host,
|
||||
Text: r.Text,
|
||||
TTL: r.TTL,
|
||||
Key: r.Key,
|
||||
}
|
||||
n, isContinue := appendRecords(c, endpoint.RecordTypeTXT, bx, rs)
|
||||
if isContinue {
|
||||
continue
|
||||
}
|
||||
rs = n
|
||||
}
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// appendRecords append record to an array
|
||||
func appendRecords(r RDNSRecord, dnsType string, bx map[RDNSRecordType]RDNSRecord, rs []RDNSRecord) ([]RDNSRecord, bool) {
|
||||
dnsName := keyToParentDNSName(r.Key)
|
||||
bt := RDNSRecordType{Domain: dnsName, Type: dnsType}
|
||||
if v, ok := bx[bt]; ok {
|
||||
// skip the TXT records if already added to record list.
|
||||
// append A record if dnsName already added to record list but not found the value.
|
||||
// the same record might be found in multiple etcdv3 nodes.
|
||||
if bt.Type == endpoint.RecordTypeA {
|
||||
exist := false
|
||||
for _, h := range v.AggregationHosts {
|
||||
if h == r.Host {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
for i, t := range rs {
|
||||
if !strings.HasPrefix(r.Key, t.Key) {
|
||||
continue
|
||||
}
|
||||
t.Host = ""
|
||||
t.AggregationHosts = append(t.AggregationHosts, r.Host)
|
||||
bx[bt] = t
|
||||
rs[i] = t
|
||||
}
|
||||
}
|
||||
}
|
||||
return rs, true
|
||||
}
|
||||
|
||||
if bt.Type == endpoint.RecordTypeA {
|
||||
r.AggregationHosts = append(r.AggregationHosts, r.Host)
|
||||
}
|
||||
|
||||
r.Key = rdnsPrefix + dnsNameToKey(dnsName)
|
||||
r.Host = ""
|
||||
bx[bt] = r
|
||||
rs = append(rs, r)
|
||||
return rs, false
|
||||
}
|
||||
|
||||
// keyFor used to get a path as etcdv3 preferred.
|
||||
// e.g. sample.lb.rancher.cloud => /rdnsv3/cloud/rancher/lb/sample
|
||||
func keyFor(fqdn string) string {
|
||||
return rdnsPrefix + dnsNameToKey(fqdn)
|
||||
}
|
||||
|
||||
// keyToParentDNSName used to get dnsName.
|
||||
// e.g. /rdnsv3/cloud/rancher/lb/sample/xxx => xxx.sample.lb.rancher.cloud
|
||||
// e.g. /rdnsv3/cloud/rancher/lb/sample/xxx/1_1_1_1 => xxx.sample.lb.rancher.cloud
|
||||
func keyToParentDNSName(key string) string {
|
||||
ds := strings.Split(strings.TrimPrefix(key, rdnsPrefix+"/"), "/")
|
||||
keyToDNSNameSplits(ds)
|
||||
|
||||
dns := strings.Join(ds, ".")
|
||||
prefix := strings.Split(dns, ".")[0]
|
||||
|
||||
p := `^\d{1,3}_\d{1,3}_\d{1,3}_\d{1,3}$`
|
||||
m, _ := regexp.MatchString(p, prefix)
|
||||
if prefix != "" && strings.Contains(prefix, "_") && m {
|
||||
// 1_1_1_1.xxx.sample.lb.rancher.cloud => xxx.sample.lb.rancher.cloud
|
||||
return strings.Join(strings.Split(dns, ".")[1:], ".")
|
||||
}
|
||||
|
||||
return dns
|
||||
}
|
||||
|
||||
// dnsNameToKey used to convert domain to a path as etcdv3 preferred.
|
||||
// e.g. sample.lb.rancher.cloud => /cloud/rancher/lb/sample
|
||||
func dnsNameToKey(domain string) string {
|
||||
ss := strings.Split(domain, ".")
|
||||
last := len(ss) - 1
|
||||
for i := 0; i < len(ss)/2; i++ {
|
||||
ss[i], ss[last-i] = ss[last-i], ss[i]
|
||||
}
|
||||
return "/" + strings.Join(ss, "/")
|
||||
}
|
||||
|
||||
// keyToDNSNameSplits used to reverse etcdv3 path to domain splits.
|
||||
// e.g. /cloud/rancher/lb/sample => [sample lb rancher cloud]
|
||||
func keyToDNSNameSplits(ss []string) {
|
||||
for i := 0; i < len(ss)/2; i++ {
|
||||
j := len(ss) - i - 1
|
||||
ss[i], ss[j] = ss[j], ss[i]
|
||||
}
|
||||
}
|
||||
|
||||
// formatKey used to format a key as etcdv3 preferred
|
||||
// e.g. 1.1.1.1 => 1_1_1_1
|
||||
// e.g. sample.lb.rancher.cloud => sample_lb_rancher_cloud
|
||||
func formatKey(key string) string {
|
||||
return strings.Replace(key, ".", "_", -1)
|
||||
}
|
||||
@ -1,355 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 rdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
)
|
||||
|
||||
type fakeEtcdv3Client struct {
|
||||
rs map[string]RDNSRecord
|
||||
}
|
||||
|
||||
func (c fakeEtcdv3Client) Get(key string) ([]RDNSRecord, error) {
|
||||
rs := make([]RDNSRecord, 0)
|
||||
for k, v := range c.rs {
|
||||
if strings.Contains(k, key) {
|
||||
rs = append(rs, v)
|
||||
}
|
||||
}
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (c fakeEtcdv3Client) List(rootDomain string) ([]RDNSRecord, error) {
|
||||
var result []RDNSRecord
|
||||
for key, value := range c.rs {
|
||||
rootPath := rdnsPrefix + dnsNameToKey(rootDomain)
|
||||
if strings.HasPrefix(key, rootPath) {
|
||||
value.Key = key
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
|
||||
r := &clientv3.GetResponse{}
|
||||
|
||||
for _, v := range result {
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k := &mvccpb.KeyValue{
|
||||
Key: []byte(v.Key),
|
||||
Value: b,
|
||||
}
|
||||
|
||||
r.Kvs = append(r.Kvs, k)
|
||||
}
|
||||
|
||||
return c.aggregationRecords(r)
|
||||
}
|
||||
|
||||
func (c fakeEtcdv3Client) Set(r RDNSRecord) error {
|
||||
c.rs[r.Key] = r
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c fakeEtcdv3Client) Delete(key string) error {
|
||||
ks := make([]string, 0)
|
||||
for k := range c.rs {
|
||||
if strings.Contains(k, key) {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range ks {
|
||||
delete(c.rs, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestARecordTranslation(t *testing.T) {
|
||||
expectedTarget1 := "1.2.3.4"
|
||||
expectedTarget2 := "2.3.4.5"
|
||||
expectedTargets := []string{expectedTarget1, expectedTarget2}
|
||||
expectedDNSName := "p1xaf1.lb.rancher.cloud"
|
||||
expectedRecordType := endpoint.RecordTypeA
|
||||
|
||||
client := fakeEtcdv3Client{
|
||||
map[string]RDNSRecord{
|
||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/1_2_3_4": {Host: expectedTarget1},
|
||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/2_3_4_5": {Host: expectedTarget2},
|
||||
},
|
||||
}
|
||||
|
||||
provider := RDNSProvider{
|
||||
client: client,
|
||||
rootDomain: "lb.rancher.cloud",
|
||||
}
|
||||
|
||||
endpoints, err := provider.Records(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(endpoints) != 1 {
|
||||
t.Fatalf("got unexpected number of endpoints: %d", len(endpoints))
|
||||
}
|
||||
|
||||
ep := endpoints[0]
|
||||
if ep.DNSName != expectedDNSName {
|
||||
t.Errorf("got unexpected DNS name: %s != %s", ep.DNSName, expectedDNSName)
|
||||
}
|
||||
assert.Contains(t, expectedTargets, ep.Targets[0])
|
||||
assert.Contains(t, expectedTargets, ep.Targets[1])
|
||||
if ep.RecordType != expectedRecordType {
|
||||
t.Errorf("got unexpected DNS record type: %s != %s", ep.RecordType, expectedRecordType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTXTRecordTranslation(t *testing.T) {
|
||||
expectedTarget := "string"
|
||||
expectedDNSName := "p1xaf1.lb.rancher.cloud"
|
||||
expectedRecordType := endpoint.RecordTypeTXT
|
||||
|
||||
client := fakeEtcdv3Client{
|
||||
map[string]RDNSRecord{
|
||||
"/rdnsv3/cloud/rancher/lb/p1xaf1": {Text: expectedTarget},
|
||||
},
|
||||
}
|
||||
|
||||
provider := RDNSProvider{
|
||||
client: client,
|
||||
rootDomain: "lb.rancher.cloud",
|
||||
}
|
||||
|
||||
endpoints, err := provider.Records(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(endpoints) != 1 {
|
||||
t.Fatalf("got unexpected number of endpoints: %d", len(endpoints))
|
||||
}
|
||||
if endpoints[0].DNSName != expectedDNSName {
|
||||
t.Errorf("got unexpected DNS name: %s != %s", endpoints[0].DNSName, expectedDNSName)
|
||||
}
|
||||
if endpoints[0].Targets[0] != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", endpoints[0].Targets[0], expectedTarget)
|
||||
}
|
||||
if endpoints[0].RecordType != expectedRecordType {
|
||||
t.Errorf("got unexpected DNS record type: %s != %s", endpoints[0].RecordType, expectedRecordType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWithTXTRecordTranslation(t *testing.T) {
|
||||
expectedTargets := map[string]string{
|
||||
endpoint.RecordTypeA: "1.2.3.4",
|
||||
endpoint.RecordTypeTXT: "string",
|
||||
}
|
||||
expectedDNSName := "p1xaf1.lb.rancher.cloud"
|
||||
|
||||
client := fakeEtcdv3Client{
|
||||
map[string]RDNSRecord{
|
||||
"/rdnsv3/cloud/rancher/lb/p1xaf1": {Host: "1.2.3.4", Text: "string"},
|
||||
},
|
||||
}
|
||||
|
||||
provider := RDNSProvider{
|
||||
client: client,
|
||||
rootDomain: "lb.rancher.cloud",
|
||||
}
|
||||
|
||||
endpoints, err := provider.Records(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(endpoints) != len(expectedTargets) {
|
||||
t.Fatalf("got unexpected number of endpoints: %d", len(endpoints))
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
expectedTarget := expectedTargets[ep.RecordType]
|
||||
if expectedTarget == "" {
|
||||
t.Errorf("got unexpected DNS record type: %s", ep.RecordType)
|
||||
continue
|
||||
}
|
||||
delete(expectedTargets, ep.RecordType)
|
||||
|
||||
if ep.DNSName != expectedDNSName {
|
||||
t.Errorf("got unexpected DNS name: %s != %s", ep.DNSName, expectedDNSName)
|
||||
}
|
||||
|
||||
if ep.Targets[0] != expectedTarget {
|
||||
t.Errorf("got unexpected DNS target: %s != %s", ep.Targets[0], expectedTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRDNSApplyChanges(t *testing.T) {
|
||||
client := fakeEtcdv3Client{
|
||||
map[string]RDNSRecord{},
|
||||
}
|
||||
|
||||
provider := RDNSProvider{
|
||||
client: client,
|
||||
rootDomain: "lb.rancher.cloud",
|
||||
}
|
||||
|
||||
changes1 := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("p1xaf1.lb.rancher.cloud", endpoint.RecordTypeA, "5.5.5.5", "6.6.6.6"),
|
||||
endpoint.NewEndpoint("p1xaf1.lb.rancher.cloud", endpoint.RecordTypeTXT, "string1"),
|
||||
},
|
||||
}
|
||||
|
||||
if err := provider.ApplyChanges(context.Background(), changes1); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
expectedRecords1 := map[string]RDNSRecord{
|
||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/5_5_5_5": {Host: "5.5.5.5", Text: "string1"},
|
||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/6_6_6_6": {Host: "6.6.6.6", Text: "string1"},
|
||||
}
|
||||
|
||||
client.validateRecords(client.rs, expectedRecords1, t)
|
||||
|
||||
changes2 := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("abx1v1.lb.rancher.cloud", endpoint.RecordTypeA, "7.7.7.7"),
|
||||
},
|
||||
UpdateNew: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("p1xaf1.lb.rancher.cloud", endpoint.RecordTypeA, "8.8.8.8", "9.9.9.9"),
|
||||
},
|
||||
}
|
||||
|
||||
records, _ := provider.Records(context.Background())
|
||||
for _, ep := range records {
|
||||
if ep.DNSName == "p1xaf1.lb.rancher.cloud" {
|
||||
changes2.UpdateOld = append(changes2.UpdateOld, ep)
|
||||
}
|
||||
}
|
||||
|
||||
if err := provider.ApplyChanges(context.Background(), changes2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
expectedRecords2 := map[string]RDNSRecord{
|
||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/8_8_8_8": {Host: "8.8.8.8"},
|
||||
"/rdnsv3/cloud/rancher/lb/p1xaf1/9_9_9_9": {Host: "9.9.9.9"},
|
||||
"/rdnsv3/cloud/rancher/lb/abx1v1/7_7_7_7": {Host: "7.7.7.7"},
|
||||
}
|
||||
|
||||
client.validateRecords(client.rs, expectedRecords2, t)
|
||||
|
||||
changes3 := &plan.Changes{
|
||||
Delete: []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("p1xaf1.lb.rancher.cloud", endpoint.RecordTypeA, "8.8.8.8", "9.9.9.9"),
|
||||
},
|
||||
}
|
||||
|
||||
if err := provider.ApplyChanges(context.Background(), changes3); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
expectedRecords3 := map[string]RDNSRecord{
|
||||
"/rdnsv3/cloud/rancher/lb/abx1v1/7_7_7_7": {Host: "7.7.7.7"},
|
||||
}
|
||||
|
||||
client.validateRecords(client.rs, expectedRecords3, t)
|
||||
}
|
||||
|
||||
func (c fakeEtcdv3Client) aggregationRecords(result *clientv3.GetResponse) ([]RDNSRecord, error) {
|
||||
var rs []RDNSRecord
|
||||
bx := make(map[RDNSRecordType]RDNSRecord)
|
||||
|
||||
for _, n := range result.Kvs {
|
||||
r := new(RDNSRecord)
|
||||
if err := json.Unmarshal(n.Value, r); err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
|
||||
}
|
||||
|
||||
r.Key = string(n.Key)
|
||||
|
||||
if r.Host == "" && r.Text == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if r.Host != "" {
|
||||
c := RDNSRecord{
|
||||
AggregationHosts: r.AggregationHosts,
|
||||
Host: r.Host,
|
||||
Text: r.Text,
|
||||
TTL: r.TTL,
|
||||
Key: r.Key,
|
||||
}
|
||||
n, isContinue := appendRecords(c, endpoint.RecordTypeA, bx, rs)
|
||||
if isContinue {
|
||||
continue
|
||||
}
|
||||
rs = n
|
||||
}
|
||||
|
||||
if r.Text != "" && r.Host == "" {
|
||||
c := RDNSRecord{
|
||||
AggregationHosts: []string{},
|
||||
Host: r.Host,
|
||||
Text: r.Text,
|
||||
TTL: r.TTL,
|
||||
Key: r.Key,
|
||||
}
|
||||
n, isContinue := appendRecords(c, endpoint.RecordTypeTXT, bx, rs)
|
||||
if isContinue {
|
||||
continue
|
||||
}
|
||||
rs = n
|
||||
}
|
||||
}
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (c fakeEtcdv3Client) validateRecords(rs, expectedRs map[string]RDNSRecord, t *testing.T) {
|
||||
if len(rs) != len(expectedRs) {
|
||||
t.Errorf("wrong number of records: %d != %d", len(rs), len(expectedRs))
|
||||
}
|
||||
for key, value := range rs {
|
||||
if _, ok := expectedRs[key]; !ok {
|
||||
t.Errorf("unexpected record %s", key)
|
||||
continue
|
||||
}
|
||||
expected := expectedRs[key]
|
||||
delete(expectedRs, key)
|
||||
if value.Host != expected.Host {
|
||||
t.Errorf("wrong host for record %s: %s != %s", key, value.Host, expected.Host)
|
||||
}
|
||||
if value.Text != expected.Text {
|
||||
t.Errorf("wrong text for record %s: %s != %s", key, value.Text, expected.Text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -17,8 +17,7 @@ limitations under the License.
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/route53"
|
||||
route53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -52,7 +51,7 @@ func (f ZoneTypeFilter) Match(rawZoneType interface{}) bool {
|
||||
case zoneTypePrivate:
|
||||
return zoneType == zoneTypePrivate
|
||||
}
|
||||
case *route53.HostedZone:
|
||||
case route53types.HostedZone:
|
||||
// If the zone has no config we assume it's a public zone since the config's field
|
||||
// `PrivateZone` is false by default in go.
|
||||
if zoneType.Config == nil {
|
||||
@ -61,9 +60,9 @@ func (f ZoneTypeFilter) Match(rawZoneType interface{}) bool {
|
||||
|
||||
switch f.zoneType {
|
||||
case zoneTypePublic:
|
||||
return !aws.BoolValue(zoneType.Config.PrivateZone)
|
||||
return !zoneType.Config.PrivateZone
|
||||
case zoneTypePrivate:
|
||||
return aws.BoolValue(zoneType.Config.PrivateZone)
|
||||
return zoneType.Config.PrivateZone
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,8 +19,7 @@ package provider
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/route53"
|
||||
route53types "github.com/aws/aws-sdk-go-v2/service/route53/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -28,8 +27,8 @@ import (
|
||||
func TestZoneTypeFilterMatch(t *testing.T) {
|
||||
publicZoneStr := "public"
|
||||
privateZoneStr := "private"
|
||||
publicZoneAWS := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(false)}}
|
||||
privateZoneAWS := &route53.HostedZone{Config: &route53.HostedZoneConfig{PrivateZone: aws.Bool(true)}}
|
||||
publicZoneAWS := route53types.HostedZone{Config: &route53types.HostedZoneConfig{PrivateZone: false}}
|
||||
privateZoneAWS := route53types.HostedZone{Config: &route53types.HostedZoneConfig{PrivateZone: true}}
|
||||
|
||||
for _, tc := range []struct {
|
||||
zoneTypeFilter string
|
||||
@ -37,10 +36,10 @@ func TestZoneTypeFilterMatch(t *testing.T) {
|
||||
zones []interface{}
|
||||
}{
|
||||
{
|
||||
"", true, []interface{}{publicZoneStr, privateZoneStr, &route53.HostedZone{}},
|
||||
"", true, []interface{}{publicZoneStr, privateZoneStr, route53types.HostedZone{}},
|
||||
},
|
||||
{
|
||||
"public", true, []interface{}{publicZoneStr, publicZoneAWS, &route53.HostedZone{}},
|
||||
"public", true, []interface{}{publicZoneStr, publicZoneAWS, route53types.HostedZone{}},
|
||||
},
|
||||
{
|
||||
"public", false, []interface{}{privateZoneStr, privateZoneAWS},
|
||||
@ -49,15 +48,17 @@ func TestZoneTypeFilterMatch(t *testing.T) {
|
||||
"private", true, []interface{}{privateZoneStr, privateZoneAWS},
|
||||
},
|
||||
{
|
||||
"private", false, []interface{}{publicZoneStr, publicZoneAWS, &route53.HostedZone{}},
|
||||
"private", false, []interface{}{publicZoneStr, publicZoneAWS, route53types.HostedZone{}},
|
||||
},
|
||||
{
|
||||
"unknown", false, []interface{}{publicZoneStr},
|
||||
},
|
||||
} {
|
||||
zoneTypeFilter := NewZoneTypeFilter(tc.zoneTypeFilter)
|
||||
for _, zone := range tc.zones {
|
||||
assert.Equal(t, tc.matches, zoneTypeFilter.Match(zone))
|
||||
}
|
||||
t.Run(tc.zoneTypeFilter, func(t *testing.T) {
|
||||
zoneTypeFilter := NewZoneTypeFilter(tc.zoneTypeFilter)
|
||||
for _, zone := range tc.zones {
|
||||
assert.Equal(t, tc.matches, zoneTypeFilter.Match(zone))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,9 +23,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
dynamodbtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
@ -34,11 +35,11 @@ import (
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
|
||||
// DynamoDBAPI is the subset of the AWS Route53 API that we actually use. Add methods as required. Signatures must match exactly.
|
||||
// DynamoDBAPI is the subset of the AWS DynamoDB API that we actually use. Add methods as required. Signatures must match exactly.
|
||||
type DynamoDBAPI interface {
|
||||
DescribeTableWithContext(ctx aws.Context, input *dynamodb.DescribeTableInput, opts ...request.Option) (*dynamodb.DescribeTableOutput, error)
|
||||
ScanPagesWithContext(ctx aws.Context, input *dynamodb.ScanInput, fn func(*dynamodb.ScanOutput, bool) bool, opts ...request.Option) error
|
||||
BatchExecuteStatementWithContext(aws.Context, *dynamodb.BatchExecuteStatementInput, ...request.Option) (*dynamodb.BatchExecuteStatementOutput, error)
|
||||
DescribeTable(context.Context, *dynamodb.DescribeTableInput, ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error)
|
||||
Scan(context.Context, *dynamodb.ScanInput, ...func(*dynamodb.Options)) (*dynamodb.ScanOutput, error)
|
||||
BatchExecuteStatement(context.Context, *dynamodb.BatchExecuteStatementInput, ...func(*dynamodb.Options)) (*dynamodb.BatchExecuteStatementOutput, error)
|
||||
}
|
||||
|
||||
// DynamoDBRegistry implements registry interface with ownership implemented via an AWS DynamoDB table.
|
||||
@ -225,7 +226,7 @@ func (im *DynamoDBRegistry) ApplyChanges(ctx context.Context, changes *plan.Chan
|
||||
Delete: endpoint.FilterEndpointsByOwnerID(im.ownerID, changes.Delete),
|
||||
}
|
||||
|
||||
statements := make([]*dynamodb.BatchStatementRequest, 0, len(filteredChanges.Create)+len(filteredChanges.UpdateNew))
|
||||
statements := make([]dynamodbtypes.BatchStatementRequest, 0, len(filteredChanges.Create)+len(filteredChanges.UpdateNew))
|
||||
for _, r := range filteredChanges.Create {
|
||||
if r.Labels == nil {
|
||||
r.Labels = make(map[string]string)
|
||||
@ -286,12 +287,15 @@ func (im *DynamoDBRegistry) ApplyChanges(ctx context.Context, changes *plan.Chan
|
||||
}
|
||||
}
|
||||
|
||||
err := im.executeStatements(ctx, statements, func(request *dynamodb.BatchStatementRequest, response *dynamodb.BatchStatementResponse) error {
|
||||
err := im.executeStatements(ctx, statements, func(request dynamodbtypes.BatchStatementRequest, response dynamodbtypes.BatchStatementResponse) error {
|
||||
var context string
|
||||
if strings.HasPrefix(*request.Statement, "INSERT") {
|
||||
if aws.StringValue(response.Error.Code) == "DuplicateItem" {
|
||||
if response.Error.Code == dynamodbtypes.BatchStatementErrorCodeEnumDuplicateItem {
|
||||
// We lost a race with a different owner or another owner has an orphaned ownership record.
|
||||
key := fromDynamoKey(request.Parameters[0])
|
||||
key, err := fromDynamoKey(request.Parameters[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, endpoint := range filteredChanges.Create {
|
||||
if endpoint.Key() == key {
|
||||
log.Infof("Skipping endpoint %v because owner does not match", endpoint)
|
||||
@ -303,11 +307,19 @@ func (im *DynamoDBRegistry) ApplyChanges(ctx context.Context, changes *plan.Chan
|
||||
}
|
||||
}
|
||||
}
|
||||
context = fmt.Sprintf("inserting dynamodb record %q", aws.StringValue(request.Parameters[0].S))
|
||||
var record string
|
||||
if err := attributevalue.Unmarshal(request.Parameters[0], &record); err != nil {
|
||||
return fmt.Errorf("inserting dynamodb record: %w", err)
|
||||
}
|
||||
context = fmt.Sprintf("inserting dynamodb record %q", record)
|
||||
} else {
|
||||
context = fmt.Sprintf("updating dynamodb record %q", aws.StringValue(request.Parameters[1].S))
|
||||
var record string
|
||||
if err := attributevalue.Unmarshal(request.Parameters[1], &record); err != nil {
|
||||
return fmt.Errorf("inserting dynamodb record: %w", err)
|
||||
}
|
||||
context = fmt.Sprintf("updating dynamodb record %q", record)
|
||||
}
|
||||
return fmt.Errorf("%s: %s: %s", context, aws.StringValue(response.Error.Code), aws.StringValue(response.Error.Message))
|
||||
return fmt.Errorf("%s: %s: %s", context, response.Error.Code, *response.Error.Message)
|
||||
})
|
||||
if err != nil {
|
||||
im.recordsCache = nil
|
||||
@ -326,7 +338,7 @@ func (im *DynamoDBRegistry) ApplyChanges(ctx context.Context, changes *plan.Chan
|
||||
return err
|
||||
}
|
||||
|
||||
statements = make([]*dynamodb.BatchStatementRequest, 0, len(filteredChanges.Delete)+len(im.orphanedLabels))
|
||||
statements = make([]dynamodbtypes.BatchStatementRequest, 0, len(filteredChanges.Delete)+len(im.orphanedLabels))
|
||||
for _, r := range filteredChanges.Delete {
|
||||
statements = im.appendDelete(statements, r.Key())
|
||||
}
|
||||
@ -335,9 +347,13 @@ func (im *DynamoDBRegistry) ApplyChanges(ctx context.Context, changes *plan.Chan
|
||||
delete(im.labels, r)
|
||||
}
|
||||
im.orphanedLabels = nil
|
||||
return im.executeStatements(ctx, statements, func(request *dynamodb.BatchStatementRequest, response *dynamodb.BatchStatementResponse) error {
|
||||
return im.executeStatements(ctx, statements, func(request dynamodbtypes.BatchStatementRequest, response dynamodbtypes.BatchStatementResponse) error {
|
||||
im.labels = nil
|
||||
return fmt.Errorf("deleting dynamodb record %q: %s: %s", aws.StringValue(request.Parameters[0].S), aws.StringValue(response.Error.Code), aws.StringValue(response.Error.Message))
|
||||
record, err := fromDynamoKey(request.Parameters[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting dynamodb record: %w", err)
|
||||
}
|
||||
return fmt.Errorf("deleting dynamodb record %q: %s: %s", record, response.Error.Code, *response.Error.Message)
|
||||
})
|
||||
}
|
||||
|
||||
@ -347,7 +363,7 @@ func (im *DynamoDBRegistry) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*
|
||||
}
|
||||
|
||||
func (im *DynamoDBRegistry) readLabels(ctx context.Context) error {
|
||||
table, err := im.dynamodbAPI.DescribeTableWithContext(ctx, &dynamodb.DescribeTableInput{
|
||||
table, err := im.dynamodbAPI.DescribeTable(ctx, &dynamodb.DescribeTableInput{
|
||||
TableName: aws.String(im.table),
|
||||
})
|
||||
if err != nil {
|
||||
@ -356,8 +372,8 @@ func (im *DynamoDBRegistry) readLabels(ctx context.Context) error {
|
||||
|
||||
foundKey := false
|
||||
for _, def := range table.Table.AttributeDefinitions {
|
||||
if aws.StringValue(def.AttributeName) == "k" {
|
||||
if aws.StringValue(def.AttributeType) != "S" {
|
||||
if *def.AttributeName == "k" {
|
||||
if def.AttributeType != dynamodbtypes.ScalarAttributeTypeS {
|
||||
return fmt.Errorf("table %q attribute \"k\" must have type \"S\"", im.table)
|
||||
}
|
||||
foundKey = true
|
||||
@ -367,7 +383,7 @@ func (im *DynamoDBRegistry) readLabels(ctx context.Context) error {
|
||||
return fmt.Errorf("table %q must have attribute \"k\" of type \"S\"", im.table)
|
||||
}
|
||||
|
||||
if aws.StringValue(table.Table.KeySchema[0].AttributeName) != "k" {
|
||||
if *table.Table.KeySchema[0].AttributeName != "k" {
|
||||
return fmt.Errorf("table %q must have hash key \"k\"", im.table)
|
||||
}
|
||||
if len(table.Table.KeySchema) > 1 {
|
||||
@ -375,76 +391,92 @@ func (im *DynamoDBRegistry) readLabels(ctx context.Context) error {
|
||||
}
|
||||
|
||||
labels := map[endpoint.EndpointKey]endpoint.Labels{}
|
||||
err = im.dynamodbAPI.ScanPagesWithContext(ctx, &dynamodb.ScanInput{
|
||||
scanPaginator := dynamodb.NewScanPaginator(im.dynamodbAPI, &dynamodb.ScanInput{
|
||||
TableName: aws.String(im.table),
|
||||
FilterExpression: aws.String("o = :ownerval"),
|
||||
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
|
||||
":ownerval": {S: aws.String(im.ownerID)},
|
||||
ExpressionAttributeValues: map[string]dynamodbtypes.AttributeValue{
|
||||
":ownerval": &dynamodbtypes.AttributeValueMemberS{Value: im.ownerID},
|
||||
},
|
||||
ProjectionExpression: aws.String("k,l"),
|
||||
ConsistentRead: aws.Bool(true),
|
||||
}, func(output *dynamodb.ScanOutput, last bool) bool {
|
||||
for _, item := range output.Items {
|
||||
labels[fromDynamoKey(item["k"])] = fromDynamoLabels(item["l"], im.ownerID)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("querying dynamodb: %w", err)
|
||||
for scanPaginator.HasMorePages() {
|
||||
output, err := scanPaginator.NextPage(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("scanning table %q: %w", im.table, err)
|
||||
}
|
||||
for _, item := range output.Items {
|
||||
k, err := fromDynamoKey(item["k"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("querying dynamodb for key: %w", err)
|
||||
}
|
||||
l, err := fromDynamoLabels(item["l"], im.ownerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("querying dynamodb for labels: %w", err)
|
||||
}
|
||||
|
||||
labels[k] = l
|
||||
}
|
||||
}
|
||||
|
||||
im.labels = labels
|
||||
return nil
|
||||
}
|
||||
|
||||
func fromDynamoKey(key *dynamodb.AttributeValue) endpoint.EndpointKey {
|
||||
split := strings.SplitN(aws.StringValue(key.S), "#", 3)
|
||||
func fromDynamoKey(key dynamodbtypes.AttributeValue) (endpoint.EndpointKey, error) {
|
||||
var ep string
|
||||
if err := attributevalue.Unmarshal(key, &ep); err != nil {
|
||||
return endpoint.EndpointKey{}, fmt.Errorf("unmarshalling endpoint key: %w", err)
|
||||
}
|
||||
split := strings.SplitN(ep, "#", 3)
|
||||
return endpoint.EndpointKey{
|
||||
DNSName: split[0],
|
||||
RecordType: split[1],
|
||||
SetIdentifier: split[2],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toDynamoKey(key endpoint.EndpointKey) dynamodbtypes.AttributeValue {
|
||||
return &dynamodbtypes.AttributeValueMemberS{
|
||||
Value: fmt.Sprintf("%s#%s#%s", key.DNSName, key.RecordType, key.SetIdentifier),
|
||||
}
|
||||
}
|
||||
|
||||
func toDynamoKey(key endpoint.EndpointKey) *dynamodb.AttributeValue {
|
||||
return &dynamodb.AttributeValue{
|
||||
S: aws.String(fmt.Sprintf("%s#%s#%s", key.DNSName, key.RecordType, key.SetIdentifier)),
|
||||
}
|
||||
}
|
||||
|
||||
func fromDynamoLabels(label *dynamodb.AttributeValue, owner string) endpoint.Labels {
|
||||
func fromDynamoLabels(label dynamodbtypes.AttributeValue, owner string) (endpoint.Labels, error) {
|
||||
labels := endpoint.NewLabels()
|
||||
for k, v := range label.M {
|
||||
labels[k] = aws.StringValue(v.S)
|
||||
if err := attributevalue.Unmarshal(label, &labels); err != nil {
|
||||
return endpoint.Labels{}, fmt.Errorf("unmarshalling labels: %w", err)
|
||||
}
|
||||
labels[endpoint.OwnerLabelKey] = owner
|
||||
return labels
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func toDynamoLabels(labels endpoint.Labels) *dynamodb.AttributeValue {
|
||||
labelMap := make(map[string]*dynamodb.AttributeValue, len(labels))
|
||||
func toDynamoLabels(labels endpoint.Labels) dynamodbtypes.AttributeValue {
|
||||
labelMap := make(map[string]dynamodbtypes.AttributeValue, len(labels))
|
||||
for k, v := range labels {
|
||||
if k == endpoint.OwnerLabelKey {
|
||||
continue
|
||||
}
|
||||
labelMap[k] = &dynamodb.AttributeValue{S: aws.String(v)}
|
||||
labelMap[k] = &dynamodbtypes.AttributeValueMemberS{Value: v}
|
||||
}
|
||||
return &dynamodb.AttributeValue{M: labelMap}
|
||||
return &dynamodbtypes.AttributeValueMemberM{Value: labelMap}
|
||||
}
|
||||
|
||||
func (im *DynamoDBRegistry) appendInsert(statements []*dynamodb.BatchStatementRequest, key endpoint.EndpointKey, new endpoint.Labels) []*dynamodb.BatchStatementRequest {
|
||||
return append(statements, &dynamodb.BatchStatementRequest{
|
||||
Statement: aws.String(fmt.Sprintf("INSERT INTO %q VALUE {'k':?, 'o':?, 'l':?}", im.table)),
|
||||
Parameters: []*dynamodb.AttributeValue{
|
||||
func (im *DynamoDBRegistry) appendInsert(statements []dynamodbtypes.BatchStatementRequest, key endpoint.EndpointKey, new endpoint.Labels) []dynamodbtypes.BatchStatementRequest {
|
||||
return append(statements, dynamodbtypes.BatchStatementRequest{
|
||||
Statement: aws.String(fmt.Sprintf("INSERT INTO %q VALUE {'k':?, 'o':?, 'l':?}", im.table)),
|
||||
ConsistentRead: aws.Bool(true),
|
||||
Parameters: []dynamodbtypes.AttributeValue{
|
||||
toDynamoKey(key),
|
||||
{S: aws.String(im.ownerID)},
|
||||
&dynamodbtypes.AttributeValueMemberS{
|
||||
Value: im.ownerID,
|
||||
},
|
||||
toDynamoLabels(new),
|
||||
},
|
||||
ConsistentRead: aws.Bool(true),
|
||||
})
|
||||
}
|
||||
|
||||
func (im *DynamoDBRegistry) appendUpdate(statements []*dynamodb.BatchStatementRequest, key endpoint.EndpointKey, old endpoint.Labels, new endpoint.Labels) []*dynamodb.BatchStatementRequest {
|
||||
func (im *DynamoDBRegistry) appendUpdate(statements []dynamodbtypes.BatchStatementRequest, key endpoint.EndpointKey, old endpoint.Labels, new endpoint.Labels) []dynamodbtypes.BatchStatementRequest {
|
||||
if len(old) == len(new) {
|
||||
equal := true
|
||||
for k, v := range old {
|
||||
@ -458,28 +490,28 @@ func (im *DynamoDBRegistry) appendUpdate(statements []*dynamodb.BatchStatementRe
|
||||
}
|
||||
}
|
||||
|
||||
return append(statements, &dynamodb.BatchStatementRequest{
|
||||
return append(statements, dynamodbtypes.BatchStatementRequest{
|
||||
Statement: aws.String(fmt.Sprintf("UPDATE %q SET \"l\"=? WHERE \"k\"=?", im.table)),
|
||||
Parameters: []*dynamodb.AttributeValue{
|
||||
Parameters: []dynamodbtypes.AttributeValue{
|
||||
toDynamoLabels(new),
|
||||
toDynamoKey(key),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (im *DynamoDBRegistry) appendDelete(statements []*dynamodb.BatchStatementRequest, key endpoint.EndpointKey) []*dynamodb.BatchStatementRequest {
|
||||
return append(statements, &dynamodb.BatchStatementRequest{
|
||||
func (im *DynamoDBRegistry) appendDelete(statements []dynamodbtypes.BatchStatementRequest, key endpoint.EndpointKey) []dynamodbtypes.BatchStatementRequest {
|
||||
return append(statements, dynamodbtypes.BatchStatementRequest{
|
||||
Statement: aws.String(fmt.Sprintf("DELETE FROM %q WHERE \"k\"=? AND \"o\"=?", im.table)),
|
||||
Parameters: []*dynamodb.AttributeValue{
|
||||
Parameters: []dynamodbtypes.AttributeValue{
|
||||
toDynamoKey(key),
|
||||
{S: aws.String(im.ownerID)},
|
||||
&dynamodbtypes.AttributeValueMemberS{Value: im.ownerID},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (im *DynamoDBRegistry) executeStatements(ctx context.Context, statements []*dynamodb.BatchStatementRequest, handleErr func(request *dynamodb.BatchStatementRequest, response *dynamodb.BatchStatementResponse) error) error {
|
||||
func (im *DynamoDBRegistry) executeStatements(ctx context.Context, statements []dynamodbtypes.BatchStatementRequest, handleErr func(request dynamodbtypes.BatchStatementRequest, response dynamodbtypes.BatchStatementResponse) error) error {
|
||||
for len(statements) > 0 {
|
||||
var chunk []*dynamodb.BatchStatementRequest
|
||||
var chunk []dynamodbtypes.BatchStatementRequest
|
||||
if len(statements) > int(dynamodbMaxBatchSize) {
|
||||
chunk = statements[:dynamodbMaxBatchSize]
|
||||
statements = statements[dynamodbMaxBatchSize:]
|
||||
@ -488,7 +520,7 @@ func (im *DynamoDBRegistry) executeStatements(ctx context.Context, statements []
|
||||
statements = nil
|
||||
}
|
||||
|
||||
output, err := im.dynamodbAPI.BatchExecuteStatementWithContext(ctx, &dynamodb.BatchExecuteStatementInput{
|
||||
output, err := im.dynamodbAPI.BatchExecuteStatement(ctx, &dynamodb.BatchExecuteStatementInput{
|
||||
Statements: chunk,
|
||||
})
|
||||
if err != nil {
|
||||
@ -501,9 +533,13 @@ func (im *DynamoDBRegistry) executeStatements(ctx context.Context, statements []
|
||||
op, _, _ := strings.Cut(*request.Statement, " ")
|
||||
var key string
|
||||
if op == "UPDATE" {
|
||||
key = *request.Parameters[1].S
|
||||
if err := attributevalue.Unmarshal(request.Parameters[1], &key); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
key = *request.Parameters[0].S
|
||||
if err := attributevalue.Unmarshal(request.Parameters[0], &key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Infof("%s dynamodb record %q", op, key)
|
||||
} else {
|
||||
|
||||
@ -22,9 +22,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/service/dynamodb"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
|
||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||
dynamodbtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
@ -69,40 +70,40 @@ func TestDynamoDBRegistryNew(t *testing.T) {
|
||||
func TestDynamoDBRegistryRecordsBadTable(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
setup func(desc *dynamodb.TableDescription)
|
||||
setup func(desc *dynamodbtypes.TableDescription)
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "missing attribute k",
|
||||
setup: func(desc *dynamodb.TableDescription) {
|
||||
setup: func(desc *dynamodbtypes.TableDescription) {
|
||||
desc.AttributeDefinitions[0].AttributeName = aws.String("wrong")
|
||||
},
|
||||
expected: "table \"test-table\" must have attribute \"k\" of type \"S\"",
|
||||
},
|
||||
{
|
||||
name: "wrong attribute type",
|
||||
setup: func(desc *dynamodb.TableDescription) {
|
||||
desc.AttributeDefinitions[0].AttributeType = aws.String("SS")
|
||||
setup: func(desc *dynamodbtypes.TableDescription) {
|
||||
desc.AttributeDefinitions[0].AttributeType = "SS"
|
||||
},
|
||||
expected: "table \"test-table\" attribute \"k\" must have type \"S\"",
|
||||
},
|
||||
{
|
||||
name: "wrong key",
|
||||
setup: func(desc *dynamodb.TableDescription) {
|
||||
setup: func(desc *dynamodbtypes.TableDescription) {
|
||||
desc.KeySchema[0].AttributeName = aws.String("wrong")
|
||||
},
|
||||
expected: "table \"test-table\" must have hash key \"k\"",
|
||||
},
|
||||
{
|
||||
name: "has range key",
|
||||
setup: func(desc *dynamodb.TableDescription) {
|
||||
desc.AttributeDefinitions = append(desc.AttributeDefinitions, &dynamodb.AttributeDefinition{
|
||||
setup: func(desc *dynamodbtypes.TableDescription) {
|
||||
desc.AttributeDefinitions = append(desc.AttributeDefinitions, dynamodbtypes.AttributeDefinition{
|
||||
AttributeName: aws.String("o"),
|
||||
AttributeType: aws.String("S"),
|
||||
AttributeType: dynamodbtypes.ScalarAttributeTypeS,
|
||||
})
|
||||
desc.KeySchema = append(desc.KeySchema, &dynamodb.KeySchemaElement{
|
||||
desc.KeySchema = append(desc.KeySchema, dynamodbtypes.KeySchemaElement{
|
||||
AttributeName: aws.String("o"),
|
||||
KeyType: aws.String("RANGE"),
|
||||
KeyType: dynamodbtypes.KeyTypeRange,
|
||||
})
|
||||
},
|
||||
expected: "table \"test-table\" must not have a range key",
|
||||
@ -559,8 +560,8 @@ func TestDynamoDBRegistryApplyChanges(t *testing.T) {
|
||||
},
|
||||
},
|
||||
stubConfig: DynamoDBStubConfig{
|
||||
ExpectInsertError: map[string]string{
|
||||
"new.test-zone.example.org#CNAME#set-new": "DuplicateItem",
|
||||
ExpectInsertError: map[string]dynamodbtypes.BatchStatementErrorCodeEnum{
|
||||
"new.test-zone.example.org#CNAME#set-new": dynamodbtypes.BatchStatementErrorCodeEnumDuplicateItem,
|
||||
},
|
||||
ExpectDelete: sets.New("quux.test-zone.example.org#A#set-2"),
|
||||
},
|
||||
@ -620,7 +621,7 @@ func TestDynamoDBRegistryApplyChanges(t *testing.T) {
|
||||
},
|
||||
},
|
||||
stubConfig: DynamoDBStubConfig{
|
||||
ExpectInsertError: map[string]string{
|
||||
ExpectInsertError: map[string]dynamodbtypes.BatchStatementErrorCodeEnum{
|
||||
"new.test-zone.example.org#CNAME#set-new": "TestingError",
|
||||
},
|
||||
},
|
||||
@ -928,7 +929,7 @@ func TestDynamoDBRegistryApplyChanges(t *testing.T) {
|
||||
},
|
||||
},
|
||||
stubConfig: DynamoDBStubConfig{
|
||||
ExpectUpdateError: map[string]string{
|
||||
ExpectUpdateError: map[string]dynamodbtypes.BatchStatementErrorCodeEnum{
|
||||
"bar.test-zone.example.org#CNAME#": "TestingError",
|
||||
},
|
||||
},
|
||||
@ -1073,15 +1074,15 @@ func TestDynamoDBRegistryApplyChanges(t *testing.T) {
|
||||
type DynamoDBStub struct {
|
||||
t *testing.T
|
||||
stubConfig *DynamoDBStubConfig
|
||||
tableDescription dynamodb.TableDescription
|
||||
tableDescription dynamodbtypes.TableDescription
|
||||
changesApplied bool
|
||||
}
|
||||
|
||||
type DynamoDBStubConfig struct {
|
||||
ExpectInsert map[string]map[string]string
|
||||
ExpectInsertError map[string]string
|
||||
ExpectInsertError map[string]dynamodbtypes.BatchStatementErrorCodeEnum
|
||||
ExpectUpdate map[string]map[string]string
|
||||
ExpectUpdateError map[string]string
|
||||
ExpectUpdateError map[string]dynamodbtypes.BatchStatementErrorCodeEnum
|
||||
ExpectDelete sets.Set[string]
|
||||
}
|
||||
|
||||
@ -1100,17 +1101,17 @@ func newDynamoDBAPIStub(t *testing.T, stubConfig *DynamoDBStubConfig) (*DynamoDB
|
||||
stub := &DynamoDBStub{
|
||||
t: t,
|
||||
stubConfig: stubConfig,
|
||||
tableDescription: dynamodb.TableDescription{
|
||||
AttributeDefinitions: []*dynamodb.AttributeDefinition{
|
||||
tableDescription: dynamodbtypes.TableDescription{
|
||||
AttributeDefinitions: []dynamodbtypes.AttributeDefinition{
|
||||
{
|
||||
AttributeName: aws.String("k"),
|
||||
AttributeType: aws.String("S"),
|
||||
AttributeType: dynamodbtypes.ScalarAttributeTypeS,
|
||||
},
|
||||
},
|
||||
KeySchema: []*dynamodb.KeySchemaElement{
|
||||
KeySchema: []dynamodbtypes.KeySchemaElement{
|
||||
{
|
||||
AttributeName: aws.String("k"),
|
||||
KeyType: aws.String("HASH"),
|
||||
KeyType: dynamodbtypes.KeyTypeHash,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1131,7 +1132,7 @@ func newDynamoDBAPIStub(t *testing.T, stubConfig *DynamoDBStubConfig) (*DynamoDB
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DynamoDBStub) DescribeTableWithContext(ctx aws.Context, input *dynamodb.DescribeTableInput, opts ...request.Option) (*dynamodb.DescribeTableOutput, error) {
|
||||
func (r *DynamoDBStub) DescribeTable(ctx context.Context, input *dynamodb.DescribeTableInput, opts ...func(*dynamodb.Options)) (*dynamodb.DescribeTableOutput, error) {
|
||||
assert.NotNil(r.t, ctx)
|
||||
assert.Equal(r.t, "test-table", *input.TableName, "table name")
|
||||
return &dynamodb.DescribeTableOutput{
|
||||
@ -1139,75 +1140,80 @@ func (r *DynamoDBStub) DescribeTableWithContext(ctx aws.Context, input *dynamodb
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *DynamoDBStub) ScanPagesWithContext(ctx aws.Context, input *dynamodb.ScanInput, fn func(*dynamodb.ScanOutput, bool) bool, opts ...request.Option) error {
|
||||
func (r *DynamoDBStub) Scan(ctx context.Context, input *dynamodb.ScanInput, opts ...func(*dynamodb.Options)) (*dynamodb.ScanOutput, error) {
|
||||
assert.NotNil(r.t, ctx)
|
||||
assert.Equal(r.t, "test-table", *input.TableName, "table name")
|
||||
assert.Equal(r.t, "o = :ownerval", *input.FilterExpression)
|
||||
assert.Len(r.t, input.ExpressionAttributeValues, 1)
|
||||
assert.Equal(r.t, "test-owner", *input.ExpressionAttributeValues[":ownerval"].S)
|
||||
var owner string
|
||||
assert.Nil(r.t, attributevalue.Unmarshal(input.ExpressionAttributeValues[":ownerval"], &owner))
|
||||
assert.Equal(r.t, "test-owner", owner)
|
||||
assert.Equal(r.t, "k,l", *input.ProjectionExpression)
|
||||
assert.True(r.t, *input.ConsistentRead)
|
||||
fn(&dynamodb.ScanOutput{
|
||||
Items: []map[string]*dynamodb.AttributeValue{
|
||||
return &dynamodb.ScanOutput{
|
||||
Items: []map[string]dynamodbtypes.AttributeValue{
|
||||
{
|
||||
"k": &dynamodb.AttributeValue{S: aws.String("bar.test-zone.example.org#CNAME#")},
|
||||
"l": &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{
|
||||
endpoint.ResourceLabelKey: {S: aws.String("ingress/default/my-ingress")},
|
||||
"k": &dynamodbtypes.AttributeValueMemberS{Value: "bar.test-zone.example.org#CNAME#"},
|
||||
"l": &dynamodbtypes.AttributeValueMemberM{Value: map[string]dynamodbtypes.AttributeValue{
|
||||
endpoint.ResourceLabelKey: &dynamodbtypes.AttributeValueMemberS{Value: "ingress/default/my-ingress"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
"k": &dynamodb.AttributeValue{S: aws.String("baz.test-zone.example.org#A#set-1")},
|
||||
"l": &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{
|
||||
endpoint.ResourceLabelKey: {S: aws.String("ingress/default/my-ingress")},
|
||||
"k": &dynamodbtypes.AttributeValueMemberS{Value: "baz.test-zone.example.org#A#set-1"},
|
||||
"l": &dynamodbtypes.AttributeValueMemberM{Value: map[string]dynamodbtypes.AttributeValue{
|
||||
endpoint.ResourceLabelKey: &dynamodbtypes.AttributeValueMemberS{Value: "ingress/default/my-ingress"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
"k": &dynamodb.AttributeValue{S: aws.String("baz.test-zone.example.org#A#set-2")},
|
||||
"l": &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{
|
||||
endpoint.ResourceLabelKey: {S: aws.String("ingress/default/other-ingress")},
|
||||
"k": &dynamodbtypes.AttributeValueMemberS{Value: "baz.test-zone.example.org#A#set-2"},
|
||||
"l": &dynamodbtypes.AttributeValueMemberM{Value: map[string]dynamodbtypes.AttributeValue{
|
||||
endpoint.ResourceLabelKey: &dynamodbtypes.AttributeValueMemberS{Value: "ingress/default/other-ingress"},
|
||||
}},
|
||||
},
|
||||
{
|
||||
"k": &dynamodb.AttributeValue{S: aws.String("quux.test-zone.example.org#A#set-2")},
|
||||
"l": &dynamodb.AttributeValue{M: map[string]*dynamodb.AttributeValue{
|
||||
endpoint.ResourceLabelKey: {S: aws.String("ingress/default/quux-ingress")},
|
||||
"k": &dynamodbtypes.AttributeValueMemberS{Value: "quux.test-zone.example.org#A#set-2"},
|
||||
"l": &dynamodbtypes.AttributeValueMemberM{Value: map[string]dynamodbtypes.AttributeValue{
|
||||
endpoint.ResourceLabelKey: &dynamodbtypes.AttributeValueMemberS{Value: "ingress/default/quux-ingress"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, true)
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *DynamoDBStub) BatchExecuteStatementWithContext(context aws.Context, input *dynamodb.BatchExecuteStatementInput, option ...request.Option) (*dynamodb.BatchExecuteStatementOutput, error) {
|
||||
func (r *DynamoDBStub) BatchExecuteStatement(context context.Context, input *dynamodb.BatchExecuteStatementInput, option ...func(*dynamodb.Options)) (*dynamodb.BatchExecuteStatementOutput, error) {
|
||||
assert.NotNil(r.t, context)
|
||||
hasDelete := strings.HasPrefix(strings.ToLower(aws.StringValue(input.Statements[0].Statement)), "delete")
|
||||
hasDelete := strings.HasPrefix(strings.ToLower(*input.Statements[0].Statement), "delete")
|
||||
assert.Equal(r.t, hasDelete, r.changesApplied, "delete after provider changes, everything else before")
|
||||
assert.LessOrEqual(r.t, len(input.Statements), 25)
|
||||
responses := make([]*dynamodb.BatchStatementResponse, 0, len(input.Statements))
|
||||
responses := make([]dynamodbtypes.BatchStatementResponse, 0, len(input.Statements))
|
||||
|
||||
for _, statement := range input.Statements {
|
||||
assert.Equal(r.t, hasDelete, strings.HasPrefix(strings.ToLower(aws.StringValue(statement.Statement)), "delete"))
|
||||
switch aws.StringValue(statement.Statement) {
|
||||
assert.Equal(r.t, hasDelete, strings.HasPrefix(strings.ToLower(*statement.Statement), "delete"))
|
||||
switch *statement.Statement {
|
||||
case "DELETE FROM \"test-table\" WHERE \"k\"=? AND \"o\"=?":
|
||||
assert.True(r.t, r.changesApplied, "unexpected delete before provider changes")
|
||||
|
||||
key := aws.StringValue(statement.Parameters[0].S)
|
||||
var key string
|
||||
assert.Nil(r.t, attributevalue.Unmarshal(statement.Parameters[0], &key))
|
||||
assert.True(r.t, r.stubConfig.ExpectDelete.Has(key), "unexpected delete for key %q", key)
|
||||
r.stubConfig.ExpectDelete.Delete(key)
|
||||
|
||||
assert.Equal(r.t, "test-owner", aws.StringValue(statement.Parameters[1].S))
|
||||
var testOwner string
|
||||
assert.Nil(r.t, attributevalue.Unmarshal(statement.Parameters[1], &testOwner))
|
||||
assert.Equal(r.t, "test-owner", testOwner)
|
||||
|
||||
responses = append(responses, &dynamodb.BatchStatementResponse{})
|
||||
responses = append(responses, dynamodbtypes.BatchStatementResponse{})
|
||||
|
||||
case "INSERT INTO \"test-table\" VALUE {'k':?, 'o':?, 'l':?}":
|
||||
assert.False(r.t, r.changesApplied, "unexpected insert after provider changes")
|
||||
|
||||
key := aws.StringValue(statement.Parameters[0].S)
|
||||
var key string
|
||||
assert.Nil(r.t, attributevalue.Unmarshal(statement.Parameters[0], &key))
|
||||
if code, exists := r.stubConfig.ExpectInsertError[key]; exists {
|
||||
delete(r.stubConfig.ExpectInsertError, key)
|
||||
responses = append(responses, &dynamodb.BatchStatementResponse{
|
||||
Error: &dynamodb.BatchStatementError{
|
||||
Code: aws.String(code),
|
||||
responses = append(responses, dynamodbtypes.BatchStatementResponse{
|
||||
Error: &dynamodbtypes.BatchStatementError{
|
||||
Code: code,
|
||||
Message: aws.String("testing error"),
|
||||
},
|
||||
})
|
||||
@ -1218,10 +1224,15 @@ func (r *DynamoDBStub) BatchExecuteStatementWithContext(context aws.Context, inp
|
||||
assert.True(r.t, found, "unexpected insert for key %q", key)
|
||||
delete(r.stubConfig.ExpectInsert, key)
|
||||
|
||||
assert.Equal(r.t, "test-owner", aws.StringValue(statement.Parameters[1].S))
|
||||
var testOwner string
|
||||
assert.Nil(r.t, attributevalue.Unmarshal(statement.Parameters[1], &testOwner))
|
||||
assert.Equal(r.t, "test-owner", testOwner)
|
||||
|
||||
for label, attribute := range statement.Parameters[2].M {
|
||||
value := aws.StringValue(attribute.S)
|
||||
var labels map[string]string
|
||||
err := attributevalue.Unmarshal(statement.Parameters[2], &labels)
|
||||
assert.Nil(r.t, err)
|
||||
|
||||
for label, value := range labels {
|
||||
expectedValue, found := expectedLabels[label]
|
||||
assert.True(r.t, found, "insert for key %q has unexpected label %q", key, label)
|
||||
delete(expectedLabels, label)
|
||||
@ -1232,17 +1243,18 @@ func (r *DynamoDBStub) BatchExecuteStatementWithContext(context aws.Context, inp
|
||||
r.t.Errorf("insert for key %q did not get expected label %q", key, label)
|
||||
}
|
||||
|
||||
responses = append(responses, &dynamodb.BatchStatementResponse{})
|
||||
responses = append(responses, dynamodbtypes.BatchStatementResponse{})
|
||||
|
||||
case "UPDATE \"test-table\" SET \"l\"=? WHERE \"k\"=?":
|
||||
assert.False(r.t, r.changesApplied, "unexpected update after provider changes")
|
||||
|
||||
key := aws.StringValue(statement.Parameters[1].S)
|
||||
var key string
|
||||
assert.Nil(r.t, attributevalue.Unmarshal(statement.Parameters[1], &key))
|
||||
if code, exists := r.stubConfig.ExpectUpdateError[key]; exists {
|
||||
delete(r.stubConfig.ExpectInsertError, key)
|
||||
responses = append(responses, &dynamodb.BatchStatementResponse{
|
||||
Error: &dynamodb.BatchStatementError{
|
||||
Code: aws.String(code),
|
||||
responses = append(responses, dynamodbtypes.BatchStatementResponse{
|
||||
Error: &dynamodbtypes.BatchStatementError{
|
||||
Code: code,
|
||||
Message: aws.String("testing error"),
|
||||
},
|
||||
})
|
||||
@ -1253,8 +1265,10 @@ func (r *DynamoDBStub) BatchExecuteStatementWithContext(context aws.Context, inp
|
||||
assert.True(r.t, found, "unexpected update for key %q", key)
|
||||
delete(r.stubConfig.ExpectUpdate, key)
|
||||
|
||||
for label, attribute := range statement.Parameters[0].M {
|
||||
value := aws.StringValue(attribute.S)
|
||||
var labels map[string]string
|
||||
assert.Nil(r.t, attributevalue.Unmarshal(statement.Parameters[0], &labels))
|
||||
|
||||
for label, value := range labels {
|
||||
expectedValue, found := expectedLabels[label]
|
||||
assert.True(r.t, found, "update for key %q has unexpected label %q", key, label)
|
||||
delete(expectedLabels, label)
|
||||
@ -1265,10 +1279,10 @@ func (r *DynamoDBStub) BatchExecuteStatementWithContext(context aws.Context, inp
|
||||
r.t.Errorf("update for key %q did not get expected label %q", key, label)
|
||||
}
|
||||
|
||||
responses = append(responses, &dynamodb.BatchStatementResponse{})
|
||||
responses = append(responses, dynamodbtypes.BatchStatementResponse{})
|
||||
|
||||
default:
|
||||
r.t.Errorf("unexpected statement: %s", aws.StringValue(statement.Statement))
|
||||
r.t.Errorf("unexpected statement: %s", *statement.Statement)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -102,6 +102,11 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
continue
|
||||
}
|
||||
|
||||
if node.Spec.Unschedulable {
|
||||
log.Debugf("Skipping node %s because it is unschedulable", node.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("creating endpoint for node %s", node.Name)
|
||||
|
||||
ttl := getTTLFromAnnotations(node.Annotations, fmt.Sprintf("node/%s", node.Name))
|
||||
|
||||
@ -101,6 +101,7 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
nodeAddresses []v1.NodeAddress
|
||||
labels map[string]string
|
||||
annotations map[string]string
|
||||
unschedulable bool // default to false
|
||||
expected []*endpoint.Endpoint
|
||||
expectError bool
|
||||
}{
|
||||
@ -321,6 +322,13 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
{RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"1.2.3.4"}, RecordTTL: endpoint.TTL(10)},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "unschedulable node return nothing",
|
||||
nodeName: "node1",
|
||||
nodeAddresses: []v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}},
|
||||
unschedulable: true,
|
||||
expected: []*endpoint.Endpoint{},
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.title, func(t *testing.T) {
|
||||
@ -342,6 +350,9 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
Labels: tc.labels,
|
||||
Annotations: tc.annotations,
|
||||
},
|
||||
Spec: v1.NodeSpec{
|
||||
Unschedulable: tc.unschedulable,
|
||||
},
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: tc.nodeAddresses,
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user