Merge remote-tracking branch 'origing/master' into dansimone/support-prefer-ingress-annotations

# Conflicts:
#	docs/faq.md
This commit is contained in:
dan.simone@oracle.com 2021-01-04 15:39:34 -08:00
commit fa95e86fb1
37 changed files with 534 additions and 94 deletions

View File

@ -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

View File

@ -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,

View File

@ -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 Zalandos organization, being the company that put most of the work on the project. While it is possible to assume Zalandos 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 cant 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 cant 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 providers 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 Zalandos 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.

View File

@ -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.

View File

@ -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`.

View File

@ -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)

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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) {

View File

@ -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)
})
}
}

View File

@ -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)
}

View File

@ -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"),

View File

@ -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)

View File

@ -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")
}

View File

@ -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,

View File

@ -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)

View File

@ -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)...)

View File

@ -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"}},

View File

@ -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
}

View File

@ -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.

View File

@ -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{}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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{

View File

@ -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",

View File

@ -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()
}

View File

@ -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)...)

View File

@ -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

View File

@ -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"

View File

@ -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,

View File

@ -157,5 +157,5 @@ func TestByNames(t *testing.T) {
}
var minimalConfig = &Config{
ContourLoadBalancerService: "heptio-contour/contour",
ContourLoadBalancerService: "heptio-contour/contour",
}