mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2026-05-05 06:36:11 +02:00
Merge remote-tracking branch 'origing/master' into dansimone/support-prefer-ingress-annotations
# Conflicts: # docs/faq.md
This commit is contained in:
commit
fa95e86fb1
13
README.md
13
README.md
@ -45,6 +45,7 @@ ExternalDNS' current release is `v0.7`. This version allows you to keep selected
|
||||
* [NS1](https://ns1.com/)
|
||||
* [TransIP](https://www.transip.eu/domain-name/)
|
||||
* [VinylDNS](https://www.vinyldns.io)
|
||||
* [Vultr](https://www.vultr.com)
|
||||
* [OVH](https://www.ovh.com)
|
||||
* [Scaleway](https://www.scaleway.com)
|
||||
|
||||
@ -164,8 +165,8 @@ from source.
|
||||
Next, run an application and expose it via a Kubernetes Service:
|
||||
|
||||
```console
|
||||
$ kubectl run nginx --image=nginx --replicas=1 --port=80
|
||||
$ kubectl expose deployment nginx --port=80 --target-port=80 --type=LoadBalancer
|
||||
$ kubectl run nginx --image=nginx --port=80
|
||||
$ kubectl expose pod nginx --port=80 --target-port=80 --type=LoadBalancer
|
||||
```
|
||||
|
||||
Annotate the Service with your desired external DNS name. Make sure to change `example.org` to your domain.
|
||||
@ -182,6 +183,14 @@ $ kubectl annotate service nginx "external-dns.alpha.kubernetes.io/ttl=10"
|
||||
|
||||
For more details on configuring TTL, see [here](docs/ttl.md).
|
||||
|
||||
Use the internal-hostname annotation to create DNS records with ClusterIP as the target.
|
||||
|
||||
```console
|
||||
$ kubectl annotate service nginx "external-dns.alpha.kubernetes.io/internal-hostname=nginx.internal.example.org."
|
||||
```
|
||||
|
||||
If the service is not of type Loadbalancer you need the --publish-internal-services flag.
|
||||
|
||||
Locally run a single sync loop of ExternalDNS.
|
||||
|
||||
```console
|
||||
|
||||
@ -139,6 +139,8 @@ func (c *Controller) RunOnce(ctx context.Context) error {
|
||||
}
|
||||
sourceEndpointsTotal.Set(float64(len(endpoints)))
|
||||
|
||||
endpoints = c.Registry.AdjustEndpoints(endpoints)
|
||||
|
||||
plan := &plan.Plan{
|
||||
Policies: []plan.Policy{c.Policy},
|
||||
Current: records,
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
## Summary
|
||||
|
||||
[ExternalDNS](https://github.com/kubernetes-sigs/external-dns) is a project that synchronizes Kubernetes’ Services, Ingresses and other Kubernetes resources to DNS backends for several DNS providers.
|
||||
[ExternalDNS](https://github.com/kubernetes-sigs/external-dns) is a project that synchronizes Kubernetes' Services, Ingresses and other Kubernetes resources to DNS backends for several DNS providers.
|
||||
|
||||
The projects was started as a Kubernetes Incubator project in February 2017 and being the Kubernetes incubation initiative officially over, the maintainers want to propose the project to be moved to the kubernetes GitHub organization or to kubernetes-sigs, under the sponsorship of sig-network.
|
||||
|
||||
@ -33,7 +33,7 @@ When the project was proposed (see the [original discussion](https://github.com/
|
||||
|
||||
* Route53-kubernetes - [https://github.com/wearemolecule/route53-kubernetes](https://github.com/wearemolecule/route53-kubernetes)
|
||||
|
||||
ExternalDNS’ goal from the beginning was to provide an officially supported solution to those problems.
|
||||
ExternalDNS' goal from the beginning was to provide an officially supported solution to those problems.
|
||||
|
||||
After two years of development, the project is still in the kubernetes-sigs.
|
||||
|
||||
@ -74,7 +74,7 @@ Moving the ExternalDNS project outside of Kubernetes projects would cause:
|
||||
|
||||
* Problems (re-)establishing user trust which could eventually lead to fragmentation and duplication.
|
||||
|
||||
* It would be hard to establish in which organization the project should be moved to. The most natural would be Zalando’s organization, being the company that put most of the work on the project. While it is possible to assume Zalando’s commitment to open-source, that would be a strategic mistake for the project community and for the Kubernetes ecosystem due to the obvious lack of neutrality.
|
||||
* It would be hard to establish in which organization the project should be moved to. The most natural would be Zalando's organization, being the company that put most of the work on the project. While it is possible to assume Zalando's commitment to open-source, that would be a strategic mistake for the project community and for the Kubernetes ecosystem due to the obvious lack of neutrality.
|
||||
|
||||
* Lack of resources to test, lack of issue management via automation.
|
||||
|
||||
@ -91,7 +91,7 @@ We have evidence that many companies are using ExternalDNS in production, but it
|
||||
|
||||
The project was quoted by a number of tutorials on the web, including the [official tutorials from AWS](https://aws.amazon.com/blogs/opensource/unified-service-discovery-ecs-kubernetes/).
|
||||
|
||||
ExternalDNS can’t be consider to be "done": while the core functionality has been implemented, there is lack of integration testing and structural changes that are needed.
|
||||
ExternalDNS can't be consider to be "done": while the core functionality has been implemented, there is lack of integration testing and structural changes that are needed.
|
||||
|
||||
Those are identified in the project roadmap, which is roughly made of the following items:
|
||||
|
||||
@ -129,7 +129,7 @@ The high number of providers contributed to the project pose a maintainability c
|
||||
|
||||
The project uses the free quota of TravisCI to run tests for the project.
|
||||
|
||||
The release pipeline for the project is currently fully owned by Zalando. It runs on the internal system of the company (closed source) which external maintainers/users can’t access and that pushes images to the publicly accessible docker registry available at the URL `registry.opensource.zalan.do`.
|
||||
The release pipeline for the project is currently fully owned by Zalando. It runs on the internal system of the company (closed source) which external maintainers/users can't access and that pushes images to the publicly accessible docker registry available at the URL `registry.opensource.zalan.do`.
|
||||
|
||||
The docker registry service is provided as best effort with no sort of SLA and the maintainers team openly suggests the users to build and maintain their own docker image based on the provided Dockerfiles.
|
||||
|
||||
@ -149,8 +149,8 @@ The following are risks that were identified:
|
||||
|
||||
We think that the following actions will constitute appropriate mitigations:
|
||||
|
||||
* Decoupling the providers via an API will allow us to resolve the problem of the providers. Being the project already more than 2 years old and given that there are 18 providers implemented, we possess enough informations to define an API that we can be stable in a short timeframe. Once this is stable, the problem of testing the providers can be deferred to be a provider’s responsibility. This will also reduce the scope of External DNS core code, which means that there will be no need for a further increase of the maintaining team.
|
||||
* Decoupling the providers via an API will allow us to resolve the problem of the providers. Being the project already more than 2 years old and given that there are 18 providers implemented, we possess enough information to define an API that we can be stable in a short timeframe. Once this is stable, the problem of testing the providers can be deferred to be a provider's responsibility. This will also reduce the scope of External DNS core code, which means that there will be no need for a further increase of the maintaining team.
|
||||
|
||||
* We added integration testing for the main cloud providers to the roadmap for the 1.0 release to make sure that we cover the mostly used ones. We believe that this item should be tackled independently from the decoupling of providers as it would be capable of generating value independently from the result of the decoupling efforts.
|
||||
|
||||
* With the move to the Kubernetes incubation, we hope that we will be able to access the testing resources of the Kubernetes project. In this way, we hope to decouple the project from the dependency on Zalando’s internal CI tool. This will help open up the possibility to increase the visibility on the project from external contributors, which currently would be blocked by the lack of access to the software used for the whole release pipeline.
|
||||
* With the move to the Kubernetes incubation, we hope that we will be able to access the testing resources of the Kubernetes project. In this way, we hope to decouple the project from the dependency on Zalando's internal CI tool. This will help open up the possibility to increase the visibility on the project from external contributors, which currently would be blocked by the lack of access to the software used for the whole release pipeline.
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl)
|
||||
|
||||
Compile and run locally against a remote k8s cluster.
|
||||
```
|
||||
```shell
|
||||
git clone https://github.com/kubernetes-sigs/external-dns.git && cd external-dns
|
||||
make build
|
||||
# login to remote k8s cluster
|
||||
@ -16,14 +16,14 @@ make build
|
||||
```
|
||||
|
||||
Run linting, unit tests, and coverage report.
|
||||
```
|
||||
```shell
|
||||
make lint
|
||||
make test
|
||||
make cover-html
|
||||
```
|
||||
|
||||
Build container image.
|
||||
```
|
||||
```shell
|
||||
make build.docker
|
||||
```
|
||||
|
||||
@ -31,7 +31,7 @@ make build.docker
|
||||
|
||||
ExternalDNS's sources of DNS records live in package [source](../../source). They implement the `Source` interface that has a single method `Endpoints` which returns the represented source's objects converted to `Endpoints`. Endpoints are just a tuple of DNS name and target where target can be an IP or another hostname.
|
||||
|
||||
For example, the `ServiceSource` returns all Services converted to `Endpoints` where the hostname is the value of the `external-dns.alpha.kubernetes.io/hostname` annotation and the target is the IP of the load balancer.
|
||||
For example, the `ServiceSource` returns all Services converted to `Endpoints` where the hostname is the value of the `external-dns.alpha.kubernetes.io/hostname` annotation and the target is the IP of the load balancer or where the hostname is the value of the `external-dns.alpha.kubernetes.io/internal-hostname` annotation and the target is the IP of the service CLusterIP.
|
||||
|
||||
This list of endpoints is passed to the [Plan](../../plan) which determines the difference between the current DNS records and the desired list of `Endpoints`.
|
||||
|
||||
@ -49,10 +49,10 @@ A typical way to start on, e.g. a CoreDNS provider, would be to add a `coredns.g
|
||||
|
||||
Note, how your provider doesn't need to know anything about where the DNS records come from, nor does it have to figure out the difference between the current and the desired state, it merely executes the actions calculated by the plan.
|
||||
|
||||
# Running Github Actions locally
|
||||
# Running GitHub Actions locally
|
||||
|
||||
You can also extend the CI workflow which is currently implemented as Github Action within the [workflow](https://github.com/kubernetes-sigs/external-dns/tree/HEAD/.github/workflows) folder.
|
||||
In order to test your changes before committing you can leverage [act](https://github.com/nektos/act) to run the Github Action locally.
|
||||
You can also extend the CI workflow which is currently implemented as GitHub Action within the [workflow](https://github.com/kubernetes-sigs/external-dns/tree/HEAD/.github/workflows) folder.
|
||||
In order to test your changes before committing you can leverage [act](https://github.com/nektos/act) to run the GitHub Action locally.
|
||||
|
||||
Follow the installation instructions in the nektos/act [README.md](https://github.com/nektos/act/blob/master/README.md).
|
||||
Afterwards just run `act` within the root folder of the project.
|
||||
|
||||
@ -57,6 +57,8 @@ Services exposed via `type=LoadBalancer`, `type=ExternalName` and for the hostna
|
||||
|
||||
There are three sources of information for ExternalDNS to decide on DNS name. ExternalDNS will pick one in order as listed below:
|
||||
|
||||
1. For ingress objects ExternalDNS will create a DNS record based on the host specified for the ingress object. For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the loadbalancer IP, it also will look for the annotation `external-dns.alpha.kubernetes.io/internal-hostname` on the service and use the service IP.
|
||||
|
||||
1. For ingress objects ExternalDNS will create a DNS record based on the hosts specified for the ingress object, as well as the `external-dns.alpha.kubernetes.io/hostname` annotation. For services ExternalDNS will look for the annotation `external-dns.alpha.kubernetes.io/hostname` on the service and use the corresponding value.
|
||||
- For ingresses, you can optionally force ExternalDNS to create records based on _either_ the hosts specified or the `external-dns.alpha.kubernetes.io/hostname` annotation. This behavior is controlled by
|
||||
setting the `external-dns.alpha.kubernetes.io/ingress-hostname-source` annotation on that ingress to either `defined-hosts-only` or `annotation-only`.
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
## Background
|
||||
|
||||
[Project proposal](https://groups.google.com/forum/#!searchin/kubernetes-dev/external$20dns%7Csort:relevance/kubernetes-dev/2wGQUB0fUuE/9OXz01i2BgAJ)
|
||||
[Project proposal](https://groups.google.com/forum/#!searching/kubernetes-dev/external$20dns%7Csort:relevance/kubernetes-dev/2wGQUB0fUuE/9OXz01i2BgAJ)
|
||||
|
||||
[Initial discussion](https://docs.google.com/document/d/1ML_q3OppUtQKXan6Q42xIq2jelSoIivuXI8zExbc6ec/edit#heading=h.1pgkuagjhm4p)
|
||||
|
||||
|
||||
@ -410,7 +410,7 @@ For any given DNS name, only **one** of the following routing policies can be us
|
||||
## Associating DNS records with healthchecks
|
||||
|
||||
You can configure Route53 to associate DNS records with healthchecks for automated DNS failover using
|
||||
`external-dns.alpha.kubernetes.io/health-check-id: <health-check-id>` annotation.
|
||||
`external-dns.alpha.kubernetes.io/aws-health-check-id: <health-check-id>` annotation.
|
||||
|
||||
Note: ExternalDNS does not support creating healthchecks, and assumes that `<health-check-id>` already exists.
|
||||
|
||||
|
||||
@ -155,7 +155,7 @@ $ kubectl get services echo
|
||||
$ kubectl get endpoints echo
|
||||
```
|
||||
|
||||
Make sure everything looks correct, i.e the service is defined and recieves a
|
||||
Make sure everything looks correct, i.e the service is defined and receives a
|
||||
public IP, and that the endpoint also has a pod IP.
|
||||
|
||||
Once that's done, wait about 30s-1m (interval for external-dns to kick in), then do:
|
||||
|
||||
@ -263,7 +263,7 @@ $ kubectl create -f external-dns.yaml
|
||||
```
|
||||
- Depending on where you run your service from, it can take a few minutes for your cloud provider to create an external IP for the service.
|
||||
- Please verify on the [UltraDNS UI](https://portal.ultradns.neustar) that the records have been created under the zone "example.com".
|
||||
- Finally, you will need to clean up the deployment and service. Please verify on the UI afterwards that the records have been deleted from the zone ‘example.com’:
|
||||
- Finally, you will need to clean up the deployment and service. Please verify on the UI afterwards that the records have been deleted from the zone "example.com":
|
||||
```console
|
||||
$ kubectl delete -f apple-banana-echo.yaml
|
||||
$ kubectl delete -f expose-apple-banana-app.yaml
|
||||
|
||||
@ -3,7 +3,7 @@ kind: Kustomization
|
||||
|
||||
images:
|
||||
- name: k8s.gcr.io/external-dns/external-dns
|
||||
newTag: v0.7.4
|
||||
newTag: v0.7.5
|
||||
|
||||
resources:
|
||||
- ./external-dns-deployment.yaml
|
||||
|
||||
2
main.go
2
main.go
@ -309,7 +309,7 @@ func main() {
|
||||
case "noop":
|
||||
r, err = registry.NewNoopRegistry(p)
|
||||
case "txt":
|
||||
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval)
|
||||
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement)
|
||||
case "aws-sd":
|
||||
r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID)
|
||||
default:
|
||||
|
||||
@ -123,6 +123,7 @@ type Config struct {
|
||||
MetricsAddress string
|
||||
LogLevel string
|
||||
TXTCacheInterval time.Duration
|
||||
TXTWildcardReplacement string
|
||||
ExoscaleEndpoint string
|
||||
ExoscaleAPIKey string `secure:"yes"`
|
||||
ExoscaleAPISecret string `secure:"yes"`
|
||||
@ -218,6 +219,7 @@ var defaultConfig = &Config{
|
||||
TXTPrefix: "",
|
||||
TXTSuffix: "",
|
||||
TXTCacheInterval: 0,
|
||||
TXTWildcardReplacement: "",
|
||||
Interval: time.Minute,
|
||||
Once: false,
|
||||
DryRun: false,
|
||||
@ -367,7 +369,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("infoblox-max-results", "Add _max_results as query parameter to the URL on all API requests. The default is 0 which means _max_results is not set and the default of the server is used.").Default(strconv.Itoa(defaultConfig.InfobloxMaxResults)).IntVar(&cfg.InfobloxMaxResults)
|
||||
app.Flag("dyn-customer-name", "When using the Dyn provider, specify the Customer Name").Default("").StringVar(&cfg.DynCustomerName)
|
||||
app.Flag("dyn-username", "When using the Dyn provider, specify the Username").Default("").StringVar(&cfg.DynUsername)
|
||||
app.Flag("dyn-password", "When using the Dyn provider, specify the pasword").Default("").StringVar(&cfg.DynPassword)
|
||||
app.Flag("dyn-password", "When using the Dyn provider, specify the password").Default("").StringVar(&cfg.DynPassword)
|
||||
app.Flag("dyn-min-ttl", "Minimal TTL (in seconds) for records. This value will be used if the provided TTL for a service/ingress is lower than this.").IntVar(&cfg.DynMinTTLSeconds)
|
||||
app.Flag("oci-config-file", "When using the OCI provider, specify the OCI configuration file (required when --provider=oci").Default(defaultConfig.OCIConfigFile).StringVar(&cfg.OCIConfigFile)
|
||||
app.Flag("rcodezero-txt-encrypt", "When using the Rcodezero provider with txt registry option, set if TXT rrs are encrypted (default: false)").Default(strconv.FormatBool(defaultConfig.RcodezeroTXTEncrypt)).BoolVar(&cfg.RcodezeroTXTEncrypt)
|
||||
@ -414,6 +416,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("txt-owner-id", "When using the TXT registry, a name that identifies this instance of ExternalDNS (default: default)").Default(defaultConfig.TXTOwnerID).StringVar(&cfg.TXTOwnerID)
|
||||
app.Flag("txt-prefix", "When using the TXT registry, a custom string that's prefixed to each ownership DNS record (optional). Mutual exclusive with txt-suffix!").Default(defaultConfig.TXTPrefix).StringVar(&cfg.TXTPrefix)
|
||||
app.Flag("txt-suffix", "When using the TXT registry, a custom string that's suffixed to the host portion of each ownership DNS record (optional). Mutual exclusive with txt-prefix!").Default(defaultConfig.TXTSuffix).StringVar(&cfg.TXTSuffix)
|
||||
app.Flag("txt-wildcard-replacement", "When using the TXT registry, a custom string that's used instead of an asterisk for TXT records corresponding to wildcard DNS records (optional)").Default(defaultConfig.TXTWildcardReplacement).StringVar(&cfg.TXTWildcardReplacement)
|
||||
|
||||
// Flags related to the main control loop
|
||||
app.Flag("txt-cache-interval", "The interval between cache synchronizations in duration format (default: disabled)").Default(defaultConfig.TXTCacheInterval.String()).DurationVar(&cfg.TXTCacheInterval)
|
||||
|
||||
@ -194,12 +194,6 @@ func (p *Plan) shouldUpdateProviderSpecific(desired, current *endpoint.Endpoint)
|
||||
}
|
||||
if current.ProviderSpecific != nil {
|
||||
for _, c := range current.ProviderSpecific {
|
||||
// don't consider target health when detecting changes
|
||||
// see: https://github.com/kubernetes-sigs/external-dns/issues/869#issuecomment-458576954
|
||||
if c.Name == "aws/evaluate-target-health" {
|
||||
continue
|
||||
}
|
||||
|
||||
if d, ok := desiredProperties[c.Name]; ok {
|
||||
if p.PropertyComparator != nil {
|
||||
if !p.PropertyComparator(c.Name, c.Value, d.Value) {
|
||||
|
||||
@ -686,3 +686,132 @@ func TestNormalizeDNSName(t *testing.T) {
|
||||
assert.Equal(t, r.expect, gotName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldUpdateProviderSpecific(tt *testing.T) {
|
||||
comparator := func(name, previous, current string) bool {
|
||||
return previous == current
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
current *endpoint.Endpoint
|
||||
desired *endpoint.Endpoint
|
||||
propertyComparator func(name, previous, current string) bool
|
||||
shouldUpdate bool
|
||||
}{
|
||||
{
|
||||
name: "skip AWS target health",
|
||||
current: &endpoint.Endpoint{
|
||||
DNSName: "foo.com",
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "aws/evaluate-target-health", Value: "true"},
|
||||
},
|
||||
},
|
||||
desired: &endpoint.Endpoint{
|
||||
DNSName: "bar.com",
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "aws/evaluate-target-health", Value: "true"},
|
||||
},
|
||||
},
|
||||
propertyComparator: comparator,
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "custom property unchanged",
|
||||
current: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
desired: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
propertyComparator: comparator,
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "custom property value changed",
|
||||
current: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
desired: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "false"},
|
||||
},
|
||||
},
|
||||
propertyComparator: comparator,
|
||||
shouldUpdate: true,
|
||||
},
|
||||
{
|
||||
name: "custom property key changed",
|
||||
current: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
desired: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "new/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
propertyComparator: comparator,
|
||||
shouldUpdate: true,
|
||||
},
|
||||
{
|
||||
name: "desired has same key and value as current but not comparator is set",
|
||||
current: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
desired: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
shouldUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "desired has same key and different value as current but not comparator is set",
|
||||
current: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
desired: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "false"},
|
||||
},
|
||||
},
|
||||
shouldUpdate: true,
|
||||
},
|
||||
{
|
||||
name: "desired has different key from current but not comparator is set",
|
||||
current: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "custom/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
desired: &endpoint.Endpoint{
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "new/property", Value: "true"},
|
||||
},
|
||||
},
|
||||
shouldUpdate: true,
|
||||
},
|
||||
} {
|
||||
tt.Run(test.name, func(t *testing.T) {
|
||||
plan := &Plan{
|
||||
Current: []*endpoint.Endpoint{test.current},
|
||||
Desired: []*endpoint.Endpoint{test.desired},
|
||||
PropertyComparator: test.propertyComparator,
|
||||
}
|
||||
b := plan.shouldUpdateProviderSpecific(test.desired, test.current)
|
||||
assert.Equal(t, test.shouldUpdate, b)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,6 +205,13 @@ func NewAWSProvider(awsConfig AWSConfig) (*AWSProvider, error) {
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func (p *AWSProvider) PropertyValuesEqual(name string, previous string, current string) bool {
|
||||
if name == "aws/evaluate-target-health" {
|
||||
return true
|
||||
}
|
||||
return p.BaseProvider.PropertyValuesEqual(name, previous, current)
|
||||
}
|
||||
|
||||
// Zones returns the list of hosted zones.
|
||||
func (p *AWSProvider) Zones(ctx context.Context) (map[string]*route53.HostedZone, error) {
|
||||
if p.zonesCache.zones != nil && time.Since(p.zonesCache.age) < p.zonesCache.duration {
|
||||
@ -382,11 +389,6 @@ func (p *AWSProvider) CreateRecords(ctx context.Context, endpoints []*endpoint.E
|
||||
return p.doRecords(ctx, route53.ChangeActionCreate, endpoints)
|
||||
}
|
||||
|
||||
// UpdateRecords updates a given set of old records to a new set of records in a given hosted zone.
|
||||
func (p *AWSProvider) UpdateRecords(ctx context.Context, endpoints, _ []*endpoint.Endpoint) error {
|
||||
return p.doRecords(ctx, route53.ChangeActionUpsert, endpoints)
|
||||
}
|
||||
|
||||
// DeleteRecords deletes a given set of DNS records in a given zone.
|
||||
func (p *AWSProvider) DeleteRecords(ctx context.Context, endpoints []*endpoint.Endpoint) error {
|
||||
return p.doRecords(ctx, route53.ChangeActionDelete, endpoints)
|
||||
@ -405,6 +407,47 @@ func (p *AWSProvider) doRecords(ctx context.Context, action string, endpoints []
|
||||
return p.submitChanges(ctx, p.newChanges(action, endpoints, records, zones), zones)
|
||||
}
|
||||
|
||||
// UpdateRecords updates a given set of old records to a new set of records in a given hosted zone.
|
||||
func (p *AWSProvider) UpdateRecords(ctx context.Context, updates, current []*endpoint.Endpoint) error {
|
||||
zones, err := p.Zones(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to list zones, aborting UpdateRecords")
|
||||
}
|
||||
|
||||
records, err := p.records(ctx, zones)
|
||||
if err != nil {
|
||||
log.Errorf("failed to list records while preparing UpdateRecords: %s", err)
|
||||
}
|
||||
|
||||
return p.submitChanges(ctx, p.createUpdateChanges(updates, current, records, zones), zones)
|
||||
}
|
||||
|
||||
func (p *AWSProvider) createUpdateChanges(newEndpoints, oldEndpoints []*endpoint.Endpoint, recordsCache []*endpoint.Endpoint, zones map[string]*route53.HostedZone) []*route53.Change {
|
||||
var deletes []*endpoint.Endpoint
|
||||
var creates []*endpoint.Endpoint
|
||||
var updates []*endpoint.Endpoint
|
||||
|
||||
for i, new := range newEndpoints {
|
||||
old := oldEndpoints[i]
|
||||
if new.RecordType != old.RecordType ||
|
||||
// Handle the case where an AWS ALIAS record is changing to/from a CNAME.
|
||||
(old.RecordType == endpoint.RecordTypeCNAME && useAlias(old, p.preferCNAME) != useAlias(new, p.preferCNAME)) {
|
||||
// The record type changed, so UPSERT will fail. Instead perform a DELETE followed by a CREATE.
|
||||
deletes = append(deletes, old)
|
||||
creates = append(creates, new)
|
||||
} else {
|
||||
// Safe to perform an UPSERT.
|
||||
updates = append(updates, new)
|
||||
}
|
||||
}
|
||||
|
||||
combined := make([]*route53.Change, 0, len(deletes)+len(creates)+len(updates))
|
||||
combined = append(combined, p.newChanges(route53.ChangeActionCreate, creates, recordsCache, zones)...)
|
||||
combined = append(combined, p.newChanges(route53.ChangeActionUpsert, updates, recordsCache, zones)...)
|
||||
combined = append(combined, p.newChanges(route53.ChangeActionDelete, deletes, recordsCache, zones)...)
|
||||
return combined
|
||||
}
|
||||
|
||||
// ApplyChanges applies a given set of changes in a given zone.
|
||||
func (p *AWSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
zones, err := p.Zones(ctx)
|
||||
@ -421,11 +464,12 @@ func (p *AWSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) e
|
||||
}
|
||||
}
|
||||
|
||||
combinedChanges := make([]*route53.Change, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete))
|
||||
updateChanges := p.createUpdateChanges(changes.UpdateNew, changes.UpdateOld, records, zones)
|
||||
|
||||
combinedChanges := make([]*route53.Change, 0, len(changes.Delete)+len(changes.Create)+len(updateChanges))
|
||||
combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionCreate, changes.Create, records, zones)...)
|
||||
combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionUpsert, changes.UpdateNew, records, zones)...)
|
||||
combinedChanges = append(combinedChanges, p.newChanges(route53.ChangeActionDelete, changes.Delete, records, zones)...)
|
||||
combinedChanges = append(combinedChanges, updateChanges...)
|
||||
|
||||
return p.submitChanges(ctx, combinedChanges, zones)
|
||||
}
|
||||
|
||||
@ -384,6 +384,7 @@ func TestAWSUpdateRecords(t *testing.T) {
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"ext-dns-test-2.teapot.zalan.do."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.1.1.1"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8", "8.8.4.4"),
|
||||
})
|
||||
@ -391,12 +392,14 @@ func TestAWSUpdateRecords(t *testing.T) {
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.1.1.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
}
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"),
|
||||
}
|
||||
@ -409,6 +412,7 @@ func TestAWSUpdateRecords(t *testing.T) {
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1"),
|
||||
endpoint.NewEndpointWithTTL("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-multiple.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4", "4.3.2.1"),
|
||||
})
|
||||
@ -456,6 +460,8 @@ func TestAWSApplyChanges(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.1.1.1"),
|
||||
endpoint.NewEndpointWithTTL("update-test-alias-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.eu-central-1.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
@ -475,6 +481,8 @@ func TestAWSApplyChanges(t *testing.T) {
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.1.1.1"),
|
||||
endpoint.NewEndpoint("update-test-alias-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
@ -482,6 +490,8 @@ func TestAWSApplyChanges(t *testing.T) {
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-alias-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "my-internal-host.example.com"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"),
|
||||
@ -520,6 +530,8 @@ func TestAWSApplyChanges(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("create-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1"),
|
||||
endpoint.NewEndpointWithTTL("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-alias-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "my-internal-host.example.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("create-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.elb.amazonaws.com"),
|
||||
@ -536,6 +548,7 @@ func TestAWSApplyChangesDryRun(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("delete-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.4.4"),
|
||||
endpoint.NewEndpointWithTTL("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.1.1.1"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("delete-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "qux.elb.amazonaws.com"),
|
||||
endpoint.NewEndpointWithTTL("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.elb.amazonaws.com"),
|
||||
@ -557,6 +570,7 @@ func TestAWSApplyChangesDryRun(t *testing.T) {
|
||||
currentRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.4.4"),
|
||||
endpoint.NewEndpoint("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.1.1.1"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "bar.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8", "8.8.4.4"),
|
||||
@ -564,6 +578,7 @@ func TestAWSApplyChangesDryRun(t *testing.T) {
|
||||
updatedRecords := []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("update-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4"),
|
||||
endpoint.NewEndpoint("update-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "4.3.2.1"),
|
||||
endpoint.NewEndpoint("update-test-a-to-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-cname-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "baz.elb.amazonaws.com"),
|
||||
endpoint.NewEndpoint("update-test-multiple.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "1.2.3.4", "4.3.2.1"),
|
||||
@ -1100,6 +1115,50 @@ func TestAWSSuitableZones(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAWSHealthTargetAnnotation(tt *testing.T) {
|
||||
comparator := func(name, previous, current string) bool {
|
||||
return previous == current
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
current *endpoint.Endpoint
|
||||
desired *endpoint.Endpoint
|
||||
propertyComparator func(name, previous, current string) bool
|
||||
shouldUpdate bool
|
||||
}{
|
||||
{
|
||||
name: "skip AWS target health",
|
||||
current: &endpoint.Endpoint{
|
||||
RecordType: "A",
|
||||
DNSName: "foo.com",
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "aws/evaluate-target-health", Value: "true"},
|
||||
},
|
||||
},
|
||||
desired: &endpoint.Endpoint{
|
||||
DNSName: "foo.com",
|
||||
RecordType: "A",
|
||||
ProviderSpecific: []endpoint.ProviderSpecificProperty{
|
||||
{Name: "aws/evaluate-target-health", Value: "false"},
|
||||
},
|
||||
},
|
||||
propertyComparator: comparator,
|
||||
shouldUpdate: false,
|
||||
},
|
||||
} {
|
||||
tt.Run(test.name, func(t *testing.T) {
|
||||
provider := &AWSProvider{}
|
||||
plan := &plan.Plan{
|
||||
Current: []*endpoint.Endpoint{test.current},
|
||||
Desired: []*endpoint.Endpoint{test.desired},
|
||||
PropertyComparator: provider.PropertyValuesEqual,
|
||||
}
|
||||
plan = plan.Calculate()
|
||||
assert.Equal(t, test.shouldUpdate, len(plan.Changes.UpdateNew) == 1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createAWSZone(t *testing.T, provider *AWSProvider, zone *route53.HostedZone) {
|
||||
params := &route53.CreateHostedZoneInput{
|
||||
CallerReference: aws.String("external-dns.alpha.kubernetes.io/test-zone"),
|
||||
|
||||
@ -157,7 +157,7 @@ func (p *CloudFlareProvider) Zones(ctx context.Context) ([]cloudflare.Zone, erro
|
||||
p.PaginationOptions.Page = 1
|
||||
|
||||
// if there is a zoneIDfilter configured
|
||||
// && if the filter isnt just a blank string (used in tests)
|
||||
// && if the filter isn't just a blank string (used in tests)
|
||||
if len(p.zoneIDFilter.ZoneIDs) > 0 && p.zoneIDFilter.ZoneIDs[0] != "" {
|
||||
log.Debugln("zoneIDFilter configured. only looking up zone IDs defined")
|
||||
for _, zoneID := range p.zoneIDFilter.ZoneIDs {
|
||||
@ -331,6 +331,18 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdjustEndpoints modifies the endpoints as needed by the specific provider
|
||||
func (p *CloudFlareProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
adjustedEndpoints := []*endpoint.Endpoint{}
|
||||
for _, e := range endpoints {
|
||||
if shouldBeProxied(e, p.proxiedByDefault) {
|
||||
e.RecordTTL = 0
|
||||
}
|
||||
adjustedEndpoints = append(adjustedEndpoints, e)
|
||||
}
|
||||
return adjustedEndpoints
|
||||
}
|
||||
|
||||
// changesByZone separates a multi-zone change into a single change per zone.
|
||||
func (p *CloudFlareProvider) changesByZone(zones []cloudflare.Zone, changeSet []*cloudFlareChange) map[string][]*cloudFlareChange {
|
||||
changes := make(map[string][]*cloudFlareChange)
|
||||
|
||||
@ -1133,3 +1133,60 @@ func TestCloudflareComplexUpdate(t *testing.T) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) {
|
||||
client := NewMockCloudFlareClientWithRecords(map[string][]cloudflare.DNSRecord{
|
||||
"001": []cloudflare.DNSRecord{
|
||||
{
|
||||
ID: "1234567890",
|
||||
ZoneID: "001",
|
||||
Name: "foobar.bar.com",
|
||||
Type: endpoint.RecordTypeA,
|
||||
TTL: 1,
|
||||
Content: "1.2.3.4",
|
||||
Proxied: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
provider := &CloudFlareProvider{
|
||||
Client: client,
|
||||
}
|
||||
|
||||
records, err := provider.Records(context.Background())
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("should not fail, %s", err)
|
||||
}
|
||||
|
||||
endpoints := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "foobar.bar.com",
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 300,
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
{
|
||||
Name: "external-dns.alpha.kubernetes.io/cloudflare-proxied",
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
provider.AdjustEndpoints(endpoints)
|
||||
|
||||
plan := &plan.Plan{
|
||||
Current: records,
|
||||
Desired: endpoints,
|
||||
DomainFilter: endpoint.NewDomainFilter([]string{"bar.com"}),
|
||||
}
|
||||
|
||||
planned := plan.Calculate()
|
||||
|
||||
assert.Equal(t, 0, len(planned.Changes.Create), "no new changes should be here")
|
||||
assert.Equal(t, 0, len(planned.Changes.UpdateNew), "no new changes should be here")
|
||||
assert.Equal(t, 0, len(planned.Changes.UpdateOld), "no new changes should be here")
|
||||
assert.Equal(t, 0, len(planned.Changes.Delete), "no new changes should be here")
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
)
|
||||
@ -83,7 +84,10 @@ func NewDigitalOceanProvider(ctx context.Context, domainFilter endpoint.DomainFi
|
||||
oauthClient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{
|
||||
AccessToken: token,
|
||||
}))
|
||||
client := godo.NewClient(oauthClient)
|
||||
client, err := godo.New(oauthClient, godo.SetUserAgent("ExternalDNS/"+externaldns.Version))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &DigitalOceanProvider{
|
||||
Client: client.Domains,
|
||||
|
||||
@ -195,7 +195,7 @@ func fixMissingTTL(ttl endpoint.TTL, minTTLSeconds int) string {
|
||||
return strconv.Itoa(i)
|
||||
}
|
||||
|
||||
// merge produces a singe list of records that can be used as a replacement.
|
||||
// merge produces a single list of records that can be used as a replacement.
|
||||
// Dyn allows to replace all records with a single call
|
||||
// Invariant: the result contains only elements from the updateNew parameter
|
||||
func merge(updateOld, updateNew []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
@ -625,7 +625,7 @@ func (d *dynProviderState) Records(ctx context.Context) ([]*endpoint.Endpoint, e
|
||||
// this method does C + 2*Z requests: C=total number of changes, Z = number of
|
||||
// affected zones (1 login + 1 commit)
|
||||
func (d *dynProviderState) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
log.Debugf("Processing chages: %+v", changes)
|
||||
log.Debugf("Processing changes: %+v", changes)
|
||||
|
||||
if d.DryRun {
|
||||
log.Infof("Will NOT delete these records: %+v", changes.Delete)
|
||||
|
||||
@ -203,7 +203,7 @@ func (p *OCIProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||
|
||||
// ApplyChanges applies a given set of changes to a given zone.
|
||||
func (p *OCIProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
|
||||
log.Debugf("Processing chages: %+v", changes)
|
||||
log.Debugf("Processing changes: %+v", changes)
|
||||
|
||||
ops := []dns.RecordOperation{}
|
||||
ops = append(ops, p.newFilteredRecordOperations(changes.Create, dns.RecordOperationOperationAdd)...)
|
||||
|
||||
@ -140,8 +140,8 @@ func TestOvhRecords(t *testing.T) {
|
||||
endpoints, err := provider.Records(context.TODO())
|
||||
assert.NoError(err)
|
||||
// Little fix for multi targets endpoint
|
||||
for _, endoint := range endpoints {
|
||||
sort.Strings(endoint.Targets)
|
||||
for _, endpoint := range endpoints {
|
||||
sort.Strings(endpoint.Targets)
|
||||
}
|
||||
assert.ElementsMatch(endpoints, []*endpoint.Endpoint{
|
||||
{DNSName: "example.org", RecordType: "A", RecordTTL: 10, Labels: endpoint.NewLabels(), Targets: []string{"203.0.113.42"}},
|
||||
|
||||
@ -30,11 +30,16 @@ type Provider interface {
|
||||
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||
ApplyChanges(ctx context.Context, changes *plan.Changes) error
|
||||
PropertyValuesEqual(name string, previous string, current string) bool
|
||||
AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint
|
||||
}
|
||||
|
||||
type BaseProvider struct {
|
||||
}
|
||||
|
||||
func (b BaseProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func (b BaseProvider) PropertyValuesEqual(name, previous, current string) bool {
|
||||
return previous == current
|
||||
}
|
||||
|
||||
@ -263,7 +263,7 @@ func (p *TransIPProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, er
|
||||
}
|
||||
|
||||
// endpointNameForRecord returns "www.example.org" for DNSEntry with Name "www" and
|
||||
// Doman with Name "example.org"
|
||||
// Domain with Name "example.org"
|
||||
func (p *TransIPProvider) endpointNameForRecord(r transip.DNSEntry, d transip.Domain) string {
|
||||
// root name is identified by "@" and should be translated to domain name for
|
||||
// the endpoint entry.
|
||||
|
||||
@ -231,7 +231,7 @@ func TestUltraDNSProvider_ApplyChangesCNAME(t *testing.T) {
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
// This will work if you would set the environment variables such as "ULTRADNS_INTEGRATION" and zone should be avaialble "kubernetes-ultradns-provider-test.com"
|
||||
// This will work if you would set the environment variables such as "ULTRADNS_INTEGRATION" and zone should be available "kubernetes-ultradns-provider-test.com"
|
||||
func TestUltraDNSProvider_ApplyChanges_Integration(t *testing.T) {
|
||||
|
||||
_, ok := os.LookupEnv("ULTRADNS_INTEGRATION")
|
||||
@ -311,7 +311,7 @@ func TestUltraDNSProvider_ApplyChanges_Integration(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
// This will work if you would set the environment variables such as "ULTRADNS_INTEGRATION" and zone should be avaialble "kubernetes-ultradns-provider-test.com" for multiple target
|
||||
// This will work if you would set the environment variables such as "ULTRADNS_INTEGRATION" and zone should be available "kubernetes-ultradns-provider-test.com" for multiple target
|
||||
func TestUltraDNSProvider_ApplyChanges_MultipleTarget_integeration(t *testing.T) {
|
||||
_, ok := os.LookupEnv("ULTRADNS_INTEGRATION")
|
||||
if !ok {
|
||||
@ -652,7 +652,7 @@ func TestUltraDNSProvider_PoolConversionCase(t *testing.T) {
|
||||
resp, _ := provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/ttl.kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{})
|
||||
assert.Equal(t, resp.Status, "200 OK")
|
||||
|
||||
//Coverting to RD Pool
|
||||
//Converting to RD Pool
|
||||
_ = os.Setenv("ULTRADNS_POOL_TYPE", "rdpool")
|
||||
provider, _ = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false)
|
||||
changes = &plan.Changes{}
|
||||
@ -662,7 +662,7 @@ func TestUltraDNSProvider_PoolConversionCase(t *testing.T) {
|
||||
resp, _ = provider.client.Do("GET", "zones/kubernetes-ultradns-provider-test.com./rrsets/A/ttl.kubernetes-ultradns-provider-test.com.", nil, udnssdk.RRSetListDTO{})
|
||||
assert.Equal(t, resp.Status, "200 OK")
|
||||
|
||||
//Coverting back to SB Pool
|
||||
//Converting back to SB Pool
|
||||
_ = os.Setenv("ULTRADNS_POOL_TYPE", "sbpool")
|
||||
provider, _ = NewUltraDNSProvider(endpoint.NewDomainFilter([]string{"kubernetes-ultradns-provider-test.com"}), false)
|
||||
changes = &plan.Changes{}
|
||||
|
||||
@ -91,3 +91,8 @@ func (sdr *AWSSDRegistry) updateLabels(endpoints []*endpoint.Endpoint) {
|
||||
func (sdr *AWSSDRegistry) PropertyValuesEqual(name string, previous string, current string) bool {
|
||||
return sdr.provider.PropertyValuesEqual(name, previous, current)
|
||||
}
|
||||
|
||||
// AdjustEndpoints modifies the endpoints as needed by the specific provider
|
||||
func (sdr *AWSSDRegistry) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
return sdr.provider.AdjustEndpoints(endpoints)
|
||||
}
|
||||
|
||||
@ -50,3 +50,8 @@ func (im *NoopRegistry) ApplyChanges(ctx context.Context, changes *plan.Changes)
|
||||
func (im *NoopRegistry) PropertyValuesEqual(attribute string, previous string, current string) bool {
|
||||
return im.provider.PropertyValuesEqual(attribute, previous, current)
|
||||
}
|
||||
|
||||
// AdjustEndpoints modifies the endpoints as needed by the specific provider
|
||||
func (im *NoopRegistry) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
return im.provider.AdjustEndpoints(endpoints)
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ type Registry interface {
|
||||
Records(ctx context.Context) ([]*endpoint.Endpoint, error)
|
||||
ApplyChanges(ctx context.Context, changes *plan.Changes) error
|
||||
PropertyValuesEqual(attribute string, previous string, current string) bool
|
||||
AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint
|
||||
}
|
||||
|
||||
//TODO(ideahitme): consider moving this to Plan
|
||||
|
||||
@ -40,10 +40,15 @@ type TXTRegistry struct {
|
||||
recordsCache []*endpoint.Endpoint
|
||||
recordsCacheRefreshTime time.Time
|
||||
cacheInterval time.Duration
|
||||
|
||||
// optional string to use to replace the asterisk in wildcard entries - without using this,
|
||||
// registry TXT records corresponding to wildcard records will be invalid (and rejected by most providers), due to
|
||||
// having a '*' appear (not as the first character) - see https://tools.ietf.org/html/rfc1034#section-4.3.3
|
||||
wildcardReplacement string
|
||||
}
|
||||
|
||||
// NewTXTRegistry returns new TXTRegistry object
|
||||
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration) (*TXTRegistry, error) {
|
||||
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration, txtWildcardReplacement string) (*TXTRegistry, error) {
|
||||
if ownerID == "" {
|
||||
return nil, errors.New("owner id cannot be empty")
|
||||
}
|
||||
@ -52,13 +57,14 @@ func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID st
|
||||
return nil, errors.New("txt-prefix and txt-suffix are mutual exclusive")
|
||||
}
|
||||
|
||||
mapper := newaffixNameMapper(txtPrefix, txtSuffix)
|
||||
mapper := newaffixNameMapper(txtPrefix, txtSuffix, txtWildcardReplacement)
|
||||
|
||||
return &TXTRegistry{
|
||||
provider: provider,
|
||||
ownerID: ownerID,
|
||||
mapper: mapper,
|
||||
cacheInterval: cacheInterval,
|
||||
provider: provider,
|
||||
ownerID: ownerID,
|
||||
mapper: mapper,
|
||||
cacheInterval: cacheInterval,
|
||||
wildcardReplacement: txtWildcardReplacement,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -107,7 +113,13 @@ func (im *TXTRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, error
|
||||
if ep.Labels == nil {
|
||||
ep.Labels = endpoint.NewLabels()
|
||||
}
|
||||
key := fmt.Sprintf("%s::%s", ep.DNSName, ep.SetIdentifier)
|
||||
dnsNameSplit := strings.Split(ep.DNSName, ".")
|
||||
// If specified, replace a leading asterisk in the generated txt record name with some other string
|
||||
if im.wildcardReplacement != "" && dnsNameSplit[0] == "*" {
|
||||
dnsNameSplit[0] = im.wildcardReplacement
|
||||
}
|
||||
dnsName := strings.Join(dnsNameSplit, ".")
|
||||
key := fmt.Sprintf("%s::%s", dnsName, ep.SetIdentifier)
|
||||
if labels, ok := labelMap[key]; ok {
|
||||
for k, v := range labels {
|
||||
ep.Labels[k] = v
|
||||
@ -196,6 +208,11 @@ func (im *TXTRegistry) PropertyValuesEqual(name string, previous string, current
|
||||
return im.provider.PropertyValuesEqual(name, previous, current)
|
||||
}
|
||||
|
||||
// AdjustEndpoints modifies the endpoints as needed by the specific provider
|
||||
func (im *TXTRegistry) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
return im.provider.AdjustEndpoints(endpoints)
|
||||
}
|
||||
|
||||
/**
|
||||
TXT registry specific private methods
|
||||
*/
|
||||
@ -211,14 +228,15 @@ type nameMapper interface {
|
||||
}
|
||||
|
||||
type affixNameMapper struct {
|
||||
prefix string
|
||||
suffix string
|
||||
prefix string
|
||||
suffix string
|
||||
wildcardReplacement string
|
||||
}
|
||||
|
||||
var _ nameMapper = affixNameMapper{}
|
||||
|
||||
func newaffixNameMapper(prefix string, suffix string) affixNameMapper {
|
||||
return affixNameMapper{prefix: strings.ToLower(prefix), suffix: strings.ToLower(suffix)}
|
||||
func newaffixNameMapper(prefix string, suffix string, wildcardReplacement string) affixNameMapper {
|
||||
return affixNameMapper{prefix: strings.ToLower(prefix), suffix: strings.ToLower(suffix), wildcardReplacement: strings.ToLower(wildcardReplacement)}
|
||||
}
|
||||
|
||||
func (pr affixNameMapper) toEndpointName(txtDNSName string) string {
|
||||
@ -238,6 +256,12 @@ func (pr affixNameMapper) toEndpointName(txtDNSName string) string {
|
||||
|
||||
func (pr affixNameMapper) toTXTName(endpointDNSName string) string {
|
||||
DNSName := strings.SplitN(endpointDNSName, ".", 2)
|
||||
|
||||
// If specified, replace a leading asterisk in the generated txt record name with some other string
|
||||
if pr.wildcardReplacement != "" && DNSName[0] == "*" {
|
||||
DNSName[0] = pr.wildcardReplacement
|
||||
}
|
||||
|
||||
if len(DNSName) < 2 {
|
||||
return pr.prefix + DNSName[0] + pr.suffix
|
||||
}
|
||||
|
||||
@ -44,20 +44,20 @@ func TestTXTRegistry(t *testing.T) {
|
||||
|
||||
func testTXTRegistryNew(t *testing.T) {
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
_, err := NewTXTRegistry(p, "txt", "", "", time.Hour)
|
||||
_, err := NewTXTRegistry(p, "txt", "", "", time.Hour, "")
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "", "txt", "", time.Hour)
|
||||
_, err = NewTXTRegistry(p, "", "txt", "", time.Hour, "")
|
||||
require.Error(t, err)
|
||||
|
||||
r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour)
|
||||
r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, p, r.provider)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour)
|
||||
r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour)
|
||||
_, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour, "")
|
||||
require.Error(t, err)
|
||||
|
||||
_, ok := r.mapper.(affixNameMapper)
|
||||
@ -65,7 +65,7 @@ func testTXTRegistryNew(t *testing.T) {
|
||||
assert.Equal(t, "owner", r.ownerID)
|
||||
assert.Equal(t, p, r.provider)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "", "owner", time.Hour)
|
||||
r, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok = r.mapper.(affixNameMapper)
|
||||
@ -97,6 +97,8 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-1"),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "lb2.loadbalancer.com", endpoint.RecordTypeCNAME, "").WithSetIdentifier("test-set-2"),
|
||||
newEndpointWithOwner("multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
newEndpointWithOwner("*.wildcard.test-zone.example.org", "foo.loadbalancer.com", endpoint.RecordTypeCNAME, ""),
|
||||
newEndpointWithOwner("txt.wc.wildcard.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
expectedRecords := []*endpoint.Endpoint{
|
||||
@ -169,15 +171,23 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
endpoint.OwnerLabelKey: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "*.wildcard.test-zone.example.org",
|
||||
Targets: endpoint.Targets{"foo.loadbalancer.com"},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
Labels: map[string]string{
|
||||
endpoint.OwnerLabelKey: "owner",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
// Ensure prefix is case-insensitive
|
||||
r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour)
|
||||
r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "")
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
||||
@ -276,13 +286,13 @@ func testTXTRegistryRecordsSuffixed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
// Ensure prefix is case-insensitive
|
||||
r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour)
|
||||
r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour, "")
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
||||
@ -357,7 +367,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "")
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -394,7 +404,7 @@ func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("txt.multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "")
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -488,13 +498,14 @@ func testTXTRegistryApplyChangesWithSuffix(t *testing.T) {
|
||||
newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "wildcard")
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", "", "", "ingress/default/my-ingress"),
|
||||
newEndpointWithOwnerResource("multiple.test-zone.example.org", "lb3.loadbalancer.com", "", "", "ingress/default/my-ingress").WithSetIdentifier("test-set-3"),
|
||||
newEndpointWithOwnerResource("example", "new-loadbalancer-1.lb.com", "", "", "ingress/default/my-ingress"),
|
||||
newEndpointWithOwnerResource("*.wildcard.test-zone.example.org", "new-loadbalancer-1.lb.com", "", "", "ingress/default/my-ingress"),
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
|
||||
@ -517,6 +528,8 @@ func testTXTRegistryApplyChangesWithSuffix(t *testing.T) {
|
||||
newEndpointWithOwner("multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-3"),
|
||||
newEndpointWithOwnerResource("example", "new-loadbalancer-1.lb.com", "", "owner", "ingress/default/my-ingress"),
|
||||
newEndpointWithOwner("example-txt", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, ""),
|
||||
newEndpointWithOwnerResource("*.wildcard.test-zone.example.org", "new-loadbalancer-1.lb.com", "", "owner", "ingress/default/my-ingress"),
|
||||
newEndpointWithOwner("wildcard-txt.wildcard.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner,external-dns/resource=ingress/default/my-ingress\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
Delete: []*endpoint.Endpoint{
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "foobar.loadbalancer.com", endpoint.RecordTypeCNAME, "owner"),
|
||||
@ -578,7 +591,7 @@ func testTXTRegistryApplyChangesNoPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "")
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
|
||||
@ -1090,31 +1090,31 @@ func testIngressEndpoints(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "ignore tls section",
|
||||
targetNamespace: "",
|
||||
ignoreIngressTLSSpec: true,
|
||||
title: "ignore tls section",
|
||||
targetNamespace: "",
|
||||
ignoreIngressTLSSpec: true,
|
||||
ingressItems: []fakeIngress{
|
||||
{
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"example.org"}},
|
||||
ips: []string{"1.2.3.4"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []*endpoint.Endpoint{},
|
||||
},
|
||||
{
|
||||
title: "reading tls section",
|
||||
targetNamespace: "",
|
||||
ignoreIngressTLSSpec: false,
|
||||
title: "reading tls section",
|
||||
targetNamespace: "",
|
||||
ignoreIngressTLSSpec: false,
|
||||
ingressItems: []fakeIngress{
|
||||
{
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
name: "fake1",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"example.org"}},
|
||||
ips: []string{"1.2.3.4"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
|
||||
@ -217,7 +217,7 @@ func NewRouteGroupSource(timeout time.Duration, token, tokenPath, apiServerURL,
|
||||
}
|
||||
|
||||
apiServer := u.String()
|
||||
// strip port if well known port, because of TLS certifcate match
|
||||
// strip port if well known port, because of TLS certificate match
|
||||
if u.Scheme == "https" && u.Port() == "443" {
|
||||
apiServer = "https://" + u.Hostname()
|
||||
}
|
||||
|
||||
@ -362,7 +362,7 @@ func (sc *serviceSource) endpointsFromTemplate(svc *v1.Service) ([]*endpoint.End
|
||||
providerSpecific, setIdentifier := getProviderSpecificAnnotations(svc.Annotations)
|
||||
hostnameList := strings.Split(strings.Replace(buf.String(), " ", "", -1), ",")
|
||||
for _, hostname := range hostnameList {
|
||||
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, providerSpecific, setIdentifier)...)
|
||||
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, providerSpecific, setIdentifier, false)...)
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
@ -374,9 +374,17 @@ func (sc *serviceSource) endpoints(svc *v1.Service) []*endpoint.Endpoint {
|
||||
// Skip endpoints if we do not want entries from annotations
|
||||
if !sc.ignoreHostnameAnnotation {
|
||||
providerSpecific, setIdentifier := getProviderSpecificAnnotations(svc.Annotations)
|
||||
hostnameList := getHostnamesFromAnnotations(svc.Annotations)
|
||||
var hostnameList []string
|
||||
var internalHostnameList []string
|
||||
|
||||
hostnameList = getHostnamesFromAnnotations(svc.Annotations)
|
||||
for _, hostname := range hostnameList {
|
||||
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, providerSpecific, setIdentifier)...)
|
||||
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, providerSpecific, setIdentifier, false)...)
|
||||
}
|
||||
|
||||
internalHostnameList = getInternalHostnamesFromAnnotations(svc.Annotations)
|
||||
for _, hostname := range internalHostnameList {
|
||||
endpoints = append(endpoints, sc.generateEndpoints(svc, hostname, providerSpecific, setIdentifier, true)...)
|
||||
}
|
||||
}
|
||||
return endpoints
|
||||
@ -432,7 +440,7 @@ func (sc *serviceSource) setResourceLabel(service *v1.Service, endpoints []*endp
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, providerSpecific endpoint.ProviderSpecific, setIdentifier string) []*endpoint.Endpoint {
|
||||
func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, providerSpecific endpoint.ProviderSpecific, setIdentifier string, useClusterIP bool) []*endpoint.Endpoint {
|
||||
hostname = strings.TrimSuffix(hostname, ".")
|
||||
ttl, err := getTTLFromAnnotations(svc.Annotations)
|
||||
if err != nil {
|
||||
@ -460,7 +468,11 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, pro
|
||||
|
||||
switch svc.Spec.Type {
|
||||
case v1.ServiceTypeLoadBalancer:
|
||||
targets = append(targets, extractLoadBalancerTargets(svc)...)
|
||||
if useClusterIP {
|
||||
targets = append(targets, extractServiceIps(svc)...)
|
||||
} else {
|
||||
targets = append(targets, extractLoadBalancerTargets(svc)...)
|
||||
}
|
||||
case v1.ServiceTypeClusterIP:
|
||||
if sc.publishInternal {
|
||||
targets = append(targets, extractServiceIps(svc)...)
|
||||
|
||||
@ -315,7 +315,7 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
false,
|
||||
},
|
||||
{
|
||||
"FQDN template with multiple hostnames return an endpoint with target IP when ignoreing annotations",
|
||||
"FQDN template with multiple hostnames return an endpoint with target IP when ignoring annotations",
|
||||
"",
|
||||
"",
|
||||
"testing",
|
||||
@ -1130,6 +1130,56 @@ func testServiceSourceEndpoints(t *testing.T) {
|
||||
[]*endpoint.Endpoint{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"internal-host annotated services return an endpoint with Cluster IP",
|
||||
"",
|
||||
"",
|
||||
"testing",
|
||||
"foo",
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
internalHostnameAnnotationKey: "foo.internal.example.org.",
|
||||
},
|
||||
"1.1.1.1",
|
||||
[]string{},
|
||||
[]string{"1.2.3.4"},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.internal.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"internal-host annotated and host annotated services return an endpoint with Cluster IP and an endpoint with lb IP",
|
||||
"",
|
||||
"",
|
||||
"testing",
|
||||
"foo",
|
||||
v1.ServiceTypeLoadBalancer,
|
||||
"",
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
hostnameAnnotationKey: "foo.example.org.",
|
||||
internalHostnameAnnotationKey: "foo.internal.example.org.",
|
||||
},
|
||||
"1.1.1.1",
|
||||
[]string{},
|
||||
[]string{"1.2.3.4"},
|
||||
[]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "foo.internal.example.org", Targets: endpoint.Targets{"1.1.1.1"}},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.title, func(t *testing.T) {
|
||||
// Create a Kubernetes testing client
|
||||
|
||||
@ -51,6 +51,8 @@ const (
|
||||
// The value of the controller annotation so that we feel responsible
|
||||
// The value of the controller annotation so that we feel responsible
|
||||
controllerAnnotationValue = "dns-controller"
|
||||
// The annotation used for defining the desired hostname
|
||||
internalHostnameAnnotationKey = "external-dns.alpha.kubernetes.io/internal-hostname"
|
||||
)
|
||||
|
||||
// Provider-specific annotations
|
||||
@ -116,6 +118,14 @@ func getAccessFromAnnotations(annotations map[string]string) string {
|
||||
return annotations[accessAnnotationKey]
|
||||
}
|
||||
|
||||
func getInternalHostnamesFromAnnotations(annotations map[string]string) []string {
|
||||
internalHostnameAnnotation, exists := annotations[internalHostnameAnnotationKey]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return strings.Split(strings.Replace(internalHostnameAnnotation, " ", "", -1), ",")
|
||||
}
|
||||
|
||||
func getAliasFromAnnotations(annotations map[string]string) bool {
|
||||
aliasAnnotation, exists := annotations[aliasAnnotationKey]
|
||||
return exists && aliasAnnotation == "true"
|
||||
|
||||
@ -75,7 +75,7 @@ func TestGetTTLFromAnnotations(t *testing.T) {
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
title: "TTL annotation value is set correcly using duration (fractional)",
|
||||
title: "TTL annotation value is set correctly using duration (fractional)",
|
||||
annotations: map[string]string{ttlAnnotationKey: "20.5s"},
|
||||
expectedTTL: endpoint.TTL(20),
|
||||
expectedErr: nil,
|
||||
|
||||
@ -157,5 +157,5 @@ func TestByNames(t *testing.T) {
|
||||
}
|
||||
|
||||
var minimalConfig = &Config{
|
||||
ContourLoadBalancerService: "heptio-contour/contour",
|
||||
ContourLoadBalancerService: "heptio-contour/contour",
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user