mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
Merge branch 'master' into deps/dependabot
This commit is contained in:
commit
c9cf1e9e1f
2
.github/workflows/lint-test-chart.yaml
vendored
2
.github/workflows/lint-test-chart.yaml
vendored
@ -45,7 +45,7 @@ jobs:
|
||||
run: |
|
||||
changed=$(ct list-changed)
|
||||
if [[ -n "$changed" ]]; then
|
||||
echo "::set-output name=changed::true"
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
|
2
.github/workflows/release-chart.yaml
vendored
2
.github/workflows/release-chart.yaml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
chart_version="$(grep -Po "(?<=^version: ).+" charts/external-dns/Chart.yaml)"
|
||||
echo "::set-output name=version::${chart_version}"
|
||||
echo "version=${chart_version}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get changelog entry
|
||||
id: changelog_reader
|
||||
|
@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### All Changes
|
||||
|
||||
- Disallowed privilege escalation in container security context and set the seccomp profile type to `RuntimeDefault`. ([#3689](https://github.com/kubernetes-sigs/external-dns/pull/3689)) [@nrvnrvn](https://github.com/nrvnrvn)
|
||||
|
||||
## [v1.13.0] - 2023-03-30
|
||||
|
||||
### All Changes
|
||||
|
1
charts/external-dns/ci/ci-values.yaml
Normal file
1
charts/external-dns/ci/ci-values.yaml
Normal file
@ -0,0 +1 @@
|
||||
provider: inmemory
|
@ -43,8 +43,11 @@ shareProcessNamespace: false
|
||||
|
||||
podSecurityContext:
|
||||
fsGroup: 65534
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65534
|
||||
readOnlyRootFilesystem: true
|
||||
|
96
docs/tutorials/traefik-proxy.md
Normal file
96
docs/tutorials/traefik-proxy.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Configuring ExternalDNS to use the Traefik Proxy Source
|
||||
|
||||
This tutorial describes how to configure ExternalDNS to use the Traefik Proxy source.
|
||||
It is meant to supplement the other provider-specific setup tutorials.
|
||||
|
||||
## Manifest (for clusters without RBAC enabled)
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
containers:
|
||||
- name: external-dns
|
||||
# update this to the desired external-dns version
|
||||
image: registry.k8s.io/external-dns/external-dns:v0.13.3
|
||||
args:
|
||||
- --source=traefik-proxy
|
||||
- --provider=aws
|
||||
- --registry=txt
|
||||
- --txt-owner-id=my-identifier
|
||||
```
|
||||
|
||||
## Manifest (for clusters with RBAC enabled)
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: external-dns
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: external-dns
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["services","endpoints","pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["list","watch"]
|
||||
- apiGroups: ["traefik.containo.us","traefik.io"]
|
||||
resources: ["ingressroutes", "ingressroutetcps", "ingressrouteudps"]
|
||||
verbs: ["get","watch","list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: external-dns-viewer
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: external-dns
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: external-dns
|
||||
namespace: default
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: external-dns
|
||||
spec:
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: external-dns
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: external-dns
|
||||
spec:
|
||||
serviceAccountName: external-dns
|
||||
containers:
|
||||
- name: external-dns
|
||||
# update this to the desired external-dns version
|
||||
image: registry.k8s.io/external-dns/external-dns:v0.13.3
|
||||
args:
|
||||
- --source=traefik-proxy
|
||||
- --provider=aws
|
||||
- --registry=txt
|
||||
- --txt-owner-id=my-identifier
|
||||
```
|
@ -222,22 +222,43 @@ func (e *Endpoint) WithSetIdentifier(setIdentifier string) *Endpoint {
|
||||
// warrant its own field on the Endpoint object itself. It differs from Labels in the fact that it's
|
||||
// not persisted in the Registry but only kept in memory during a single record synchronization.
|
||||
func (e *Endpoint) WithProviderSpecific(key, value string) *Endpoint {
|
||||
if e.ProviderSpecific == nil {
|
||||
e.ProviderSpecific = ProviderSpecific{}
|
||||
}
|
||||
|
||||
e.ProviderSpecific = append(e.ProviderSpecific, ProviderSpecificProperty{Name: key, Value: value})
|
||||
e.SetProviderSpecificProperty(key, value)
|
||||
return e
|
||||
}
|
||||
|
||||
// GetProviderSpecificProperty returns a ProviderSpecificProperty if the property exists.
|
||||
func (e *Endpoint) GetProviderSpecificProperty(key string) (ProviderSpecificProperty, bool) {
|
||||
// GetProviderSpecificProperty returns the value of a ProviderSpecificProperty if the property exists.
|
||||
func (e *Endpoint) GetProviderSpecificProperty(key string) (string, bool) {
|
||||
for _, providerSpecific := range e.ProviderSpecific {
|
||||
if providerSpecific.Name == key {
|
||||
return providerSpecific, true
|
||||
return providerSpecific.Value, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// SetProviderSpecificProperty sets the value of a ProviderSpecificProperty.
|
||||
func (e *Endpoint) SetProviderSpecificProperty(key string, value string) {
|
||||
for i, providerSpecific := range e.ProviderSpecific {
|
||||
if providerSpecific.Name == key {
|
||||
e.ProviderSpecific[i] = ProviderSpecificProperty{
|
||||
Name: key,
|
||||
Value: value,
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
e.ProviderSpecific = append(e.ProviderSpecific, ProviderSpecificProperty{Name: key, Value: value})
|
||||
}
|
||||
|
||||
// DeleteProviderSpecificProperty deletes any ProviderSpecificProperty of the specified name.
|
||||
func (e *Endpoint) DeleteProviderSpecificProperty(key string) {
|
||||
for i, providerSpecific := range e.ProviderSpecific {
|
||||
if providerSpecific.Name == key {
|
||||
e.ProviderSpecific = append(e.ProviderSpecific[:i], e.ProviderSpecific[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
return ProviderSpecificProperty{}, false
|
||||
}
|
||||
|
||||
func (e *Endpoint) String() string {
|
||||
|
@ -19,11 +19,12 @@ package testutils
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
)
|
||||
|
||||
func ExampleSameEndpoints() {
|
||||
func TestExampleSameEndpoints(t *testing.T) {
|
||||
eps := []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
|
@ -414,7 +414,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)
|
||||
|
||||
// Flags related to processing source
|
||||
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress, f5-virtualserver)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "gateway-httproute", "gateway-grpcroute", "gateway-tlsroute", "gateway-tcproute", "gateway-udproute", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress", "f5-virtualserver")
|
||||
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, gateway-httproute, gateway-grpcroute, gateway-tlsroute, gateway-tcproute, gateway-udproute, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress, f5-virtualserver, traefik-proxy)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "gateway-httproute", "gateway-grpcroute", "gateway-tlsroute", "gateway-tcproute", "gateway-udproute", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress", "f5-virtualserver", "traefik-proxy")
|
||||
app.Flag("openshift-router-name", "if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record.").StringVar(&cfg.OCPRouterName)
|
||||
app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
|
||||
app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
|
||||
|
@ -47,10 +47,12 @@ const (
|
||||
// As we are using the standard AWS client, this should already be compliant.
|
||||
// Hence, ifever AWS decides to raise this limit, we will automatically reduce the pressure on rate limits
|
||||
route53PageSize = "300"
|
||||
// provider specific key that designates whether an AWS ALIAS record has the EvaluateTargetHealth
|
||||
// field set to true.
|
||||
providerSpecificAlias = "alias"
|
||||
providerSpecificTargetHostedZone = "aws/target-hosted-zone"
|
||||
// providerSpecificAlias specifies whether a CNAME endpoint maps to an AWS ALIAS record.
|
||||
providerSpecificAlias = "alias"
|
||||
providerSpecificTargetHostedZone = "aws/target-hosted-zone"
|
||||
// providerSpecificEvaluateTargetHealth specifies whether an AWS ALIAS record
|
||||
// has the EvaluateTargetHealth field set to true. Present iff the endpoint
|
||||
// has a `providerSpecificAlias` value of `true`.
|
||||
providerSpecificEvaluateTargetHealth = "aws/evaluate-target-health"
|
||||
providerSpecificWeight = "aws/weight"
|
||||
providerSpecificRegion = "aws/region"
|
||||
@ -283,13 +285,6 @@ 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 {
|
||||
@ -390,7 +385,11 @@ func (p *AWSProvider) records(ctx context.Context, zones map[string]*route53.Hos
|
||||
targets[idx] = aws.StringValue(rr.Value)
|
||||
}
|
||||
|
||||
newEndpoints = append(newEndpoints, endpoint.NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), aws.StringValue(r.Type), ttl, targets...))
|
||||
ep := endpoint.NewEndpointWithTTL(wildcardUnescape(aws.StringValue(r.Name)), aws.StringValue(r.Type), ttl, targets...)
|
||||
if aws.StringValue(r.Type) == endpoint.RecordTypeCNAME {
|
||||
ep = ep.WithProviderSpecific(providerSpecificAlias, "false")
|
||||
}
|
||||
newEndpoints = append(newEndpoints, ep)
|
||||
}
|
||||
|
||||
if r.AliasTarget != nil {
|
||||
@ -466,8 +465,12 @@ func (p *AWSProvider) requiresDeleteCreate(old *endpoint.Endpoint, new *endpoint
|
||||
}
|
||||
|
||||
// an ALIAS record change to/from a CNAME
|
||||
if old.RecordType == endpoint.RecordTypeCNAME && useAlias(old, p.preferCNAME) != useAlias(new, p.preferCNAME) {
|
||||
return true
|
||||
if old.RecordType == endpoint.RecordTypeCNAME {
|
||||
oldAlias, _ := old.GetProviderSpecificProperty(providerSpecificAlias)
|
||||
newAlias, _ := new.GetProviderSpecificProperty(providerSpecificAlias)
|
||||
if oldAlias != newAlias {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// a set identifier change
|
||||
@ -667,23 +670,29 @@ func (p *AWSProvider) newChanges(action string, endpoints []*endpoint.Endpoint)
|
||||
func (p *AWSProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint {
|
||||
for _, ep := range endpoints {
|
||||
alias := false
|
||||
if aliasString, ok := ep.GetProviderSpecificProperty(providerSpecificAlias); ok {
|
||||
alias = aliasString.Value == "true"
|
||||
} else if useAlias(ep, p.preferCNAME) {
|
||||
alias = true
|
||||
log.Debugf("Modifying endpoint: %v, setting %s=true", ep, providerSpecificAlias)
|
||||
ep.ProviderSpecific = append(ep.ProviderSpecific, endpoint.ProviderSpecificProperty{
|
||||
Name: providerSpecificAlias,
|
||||
Value: "true",
|
||||
})
|
||||
if ep.RecordType != endpoint.RecordTypeCNAME {
|
||||
ep.DeleteProviderSpecificProperty(providerSpecificAlias)
|
||||
} else if aliasString, ok := ep.GetProviderSpecificProperty(providerSpecificAlias); ok {
|
||||
alias = aliasString == "true"
|
||||
if !alias && aliasString != "false" {
|
||||
ep.SetProviderSpecificProperty(providerSpecificAlias, "false")
|
||||
}
|
||||
} else {
|
||||
alias = useAlias(ep, p.preferCNAME)
|
||||
log.Debugf("Modifying endpoint: %v, setting %s=%v", ep, providerSpecificAlias, alias)
|
||||
ep.SetProviderSpecificProperty(providerSpecificAlias, strconv.FormatBool(alias))
|
||||
}
|
||||
|
||||
if _, ok := ep.GetProviderSpecificProperty(providerSpecificEvaluateTargetHealth); alias && !ok {
|
||||
log.Debugf("Modifying endpoint: %v, setting %s=%t", ep, providerSpecificEvaluateTargetHealth, p.evaluateTargetHealth)
|
||||
ep.ProviderSpecific = append(ep.ProviderSpecific, endpoint.ProviderSpecificProperty{
|
||||
Name: providerSpecificEvaluateTargetHealth,
|
||||
Value: fmt.Sprintf("%t", p.evaluateTargetHealth),
|
||||
})
|
||||
if alias {
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificEvaluateTargetHealth); ok {
|
||||
if prop != "true" && prop != "false" {
|
||||
ep.SetProviderSpecificProperty(providerSpecificEvaluateTargetHealth, "false")
|
||||
}
|
||||
} else {
|
||||
ep.SetProviderSpecificProperty(providerSpecificEvaluateTargetHealth, strconv.FormatBool(p.evaluateTargetHealth))
|
||||
}
|
||||
} else {
|
||||
ep.DeleteProviderSpecificProperty(providerSpecificEvaluateTargetHealth)
|
||||
}
|
||||
}
|
||||
return endpoints
|
||||
@ -706,7 +715,7 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
|
||||
if targetHostedZone := isAWSAlias(ep); targetHostedZone != "" {
|
||||
evalTargetHealth := p.evaluateTargetHealth
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificEvaluateTargetHealth); ok {
|
||||
evalTargetHealth = prop.Value == "true"
|
||||
evalTargetHealth = prop == "true"
|
||||
}
|
||||
// If the endpoint has a Dualstack label, append a change for AAAA record as well.
|
||||
if val, ok := ep.Labels[endpoint.DualstackLabelKey]; ok {
|
||||
@ -737,18 +746,18 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
|
||||
if setIdentifier != "" {
|
||||
change.ResourceRecordSet.SetIdentifier = aws.String(setIdentifier)
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificWeight); ok {
|
||||
weight, err := strconv.ParseInt(prop.Value, 10, 64)
|
||||
weight, err := strconv.ParseInt(prop, 10, 64)
|
||||
if err != nil {
|
||||
log.Errorf("Failed parsing value of %s: %s: %v; using weight of 0", providerSpecificWeight, prop.Value, err)
|
||||
log.Errorf("Failed parsing value of %s: %s: %v; using weight of 0", providerSpecificWeight, prop, err)
|
||||
weight = 0
|
||||
}
|
||||
change.ResourceRecordSet.Weight = aws.Int64(weight)
|
||||
}
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificRegion); ok {
|
||||
change.ResourceRecordSet.Region = aws.String(prop.Value)
|
||||
change.ResourceRecordSet.Region = aws.String(prop)
|
||||
}
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificFailover); ok {
|
||||
change.ResourceRecordSet.Failover = aws.String(prop.Value)
|
||||
change.ResourceRecordSet.Failover = aws.String(prop)
|
||||
}
|
||||
if _, ok := ep.GetProviderSpecificProperty(providerSpecificMultiValueAnswer); ok {
|
||||
change.ResourceRecordSet.MultiValueAnswer = aws.Bool(true)
|
||||
@ -757,15 +766,15 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
|
||||
geolocation := &route53.GeoLocation{}
|
||||
useGeolocation := false
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGeolocationContinentCode); ok {
|
||||
geolocation.ContinentCode = aws.String(prop.Value)
|
||||
geolocation.ContinentCode = aws.String(prop)
|
||||
useGeolocation = true
|
||||
} else {
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGeolocationCountryCode); ok {
|
||||
geolocation.CountryCode = aws.String(prop.Value)
|
||||
geolocation.CountryCode = aws.String(prop)
|
||||
useGeolocation = true
|
||||
}
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificGeolocationSubdivisionCode); ok {
|
||||
geolocation.SubdivisionCode = aws.String(prop.Value)
|
||||
geolocation.SubdivisionCode = aws.String(prop)
|
||||
useGeolocation = true
|
||||
}
|
||||
}
|
||||
@ -775,7 +784,7 @@ func (p *AWSProvider) newChange(action string, ep *endpoint.Endpoint) (*Route53C
|
||||
}
|
||||
|
||||
if prop, ok := ep.GetProviderSpecificProperty(providerSpecificHealthCheckID); ok {
|
||||
change.ResourceRecordSet.HealthCheckId = aws.String(prop.Value)
|
||||
change.ResourceRecordSet.HealthCheckId = aws.String(prop)
|
||||
}
|
||||
|
||||
if ownedRecord, ok := ep.Labels[endpoint.OwnedRecordLabelKey]; ok {
|
||||
@ -989,13 +998,13 @@ func useAlias(ep *endpoint.Endpoint, preferCNAME bool) bool {
|
||||
// isAWSAlias determines if a given endpoint is supposed to create an AWS Alias record
|
||||
// and (if so) returns the target hosted zone ID
|
||||
func isAWSAlias(ep *endpoint.Endpoint) string {
|
||||
prop, exists := ep.GetProviderSpecificProperty(providerSpecificAlias)
|
||||
if exists && prop.Value == "true" && ep.RecordType == endpoint.RecordTypeCNAME && len(ep.Targets) > 0 {
|
||||
isAlias, exists := ep.GetProviderSpecificProperty(providerSpecificAlias)
|
||||
if exists && isAlias == "true" && ep.RecordType == endpoint.RecordTypeCNAME && len(ep.Targets) > 0 {
|
||||
// alias records can only point to canonical hosted zones (e.g. to ELBs) or other records in the same zone
|
||||
|
||||
if hostedZoneID, ok := ep.GetProviderSpecificProperty(providerSpecificTargetHostedZone); ok {
|
||||
// existing Endpoint where we got the target hosted zone from the Route53 data
|
||||
return hostedZoneID.Value
|
||||
return hostedZoneID
|
||||
}
|
||||
|
||||
// check if the target is in a canonical hosted zone
|
||||
|
@ -494,7 +494,7 @@ func TestAWSRecords(t *testing.T) {
|
||||
records, err := provider.Records(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
validateEndpoints(t, provider, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4"),
|
||||
endpoint.NewEndpointWithTTL("list-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
endpoint.NewEndpointWithTTL("*.wildcard-test.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8"),
|
||||
@ -511,7 +511,7 @@ func TestAWSRecords(t *testing.T) {
|
||||
endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationContinentCode, "EU"),
|
||||
endpoint.NewEndpointWithTTL("geolocation-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificGeolocationCountryCode, "DE"),
|
||||
endpoint.NewEndpointWithTTL("geolocation-subdivision-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "1.2.3.4").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificGeolocationSubdivisionCode, "NY"),
|
||||
endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.example.com").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificWeight, "10").WithProviderSpecific(providerSpecificHealthCheckID, "foo-bar-healthcheck-id"),
|
||||
endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "foo.example.com").WithSetIdentifier("test-set-1").WithProviderSpecific(providerSpecificWeight, "10").WithProviderSpecific(providerSpecificHealthCheckID, "foo-bar-healthcheck-id").WithProviderSpecific(providerSpecificAlias, "false"),
|
||||
endpoint.NewEndpointWithTTL("healthcheck-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "4.3.2.1").WithSetIdentifier("test-set-2").WithProviderSpecific(providerSpecificWeight, "20").WithProviderSpecific(providerSpecificHealthCheckID, "abc-def-healthcheck-id"),
|
||||
endpoint.NewEndpointWithTTL("mail.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeMX, endpoint.TTL(recordTTL), "10 mailhost1.example.com", "20 mailhost2.example.com"),
|
||||
})
|
||||
@ -529,11 +529,11 @@ func TestAWSAdjustEndpoints(t *testing.T) {
|
||||
endpoint.NewEndpoint("cname-test-elb-no-eth.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "false"), // eth = evaluate target health
|
||||
}
|
||||
|
||||
provider.AdjustEndpoints(records)
|
||||
records = provider.AdjustEndpoints(records)
|
||||
|
||||
validateEndpoints(t, records, []*endpoint.Endpoint{
|
||||
validateEndpoints(t, provider, records, []*endpoint.Endpoint{
|
||||
endpoint.NewEndpoint("a-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeA, "8.8.8.8"),
|
||||
endpoint.NewEndpoint("cname-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.example.com"),
|
||||
endpoint.NewEndpoint("cname-test.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.example.com").WithProviderSpecific(providerSpecificAlias, "false"),
|
||||
endpoint.NewEndpoint("cname-test-alias.zone-1.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "alias-target.zone-2.ext-dns-test-2.teapot.zalan.do").WithProviderSpecific(providerSpecificAlias, "true").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "true"),
|
||||
endpoint.NewEndpoint("cname-test-elb.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true").WithProviderSpecific(providerSpecificEvaluateTargetHealth, "true"),
|
||||
endpoint.NewEndpoint("cname-test-elb-no-alias.zone-2.ext-dns-test-2.teapot.zalan.do", endpoint.RecordTypeCNAME, "foo.eu-central-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "false"),
|
||||
@ -1426,7 +1426,7 @@ func TestAWSsubmitChanges(t *testing.T) {
|
||||
records, err := provider.Records(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, records, endpoints)
|
||||
validateEndpoints(t, provider, records, endpoints)
|
||||
}
|
||||
|
||||
func TestAWSsubmitChangesError(t *testing.T) {
|
||||
@ -1607,8 +1607,11 @@ func TestAWSBatchChangeSetExceedingNameChange(t *testing.T) {
|
||||
require.Equal(t, 0, len(batchCs))
|
||||
}
|
||||
|
||||
func validateEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) {
|
||||
func validateEndpoints(t *testing.T, provider *AWSProvider, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) {
|
||||
assert.True(t, testutils.SameEndpoints(endpoints, expected), "actual and expected endpoints don't match. %+v:%+v", endpoints, expected)
|
||||
|
||||
normalized := provider.AdjustEndpoints(endpoints)
|
||||
assert.True(t, testutils.SameEndpoints(normalized, expected), "actual and normalized endpoints don't match. %+v:%+v", endpoints, normalized)
|
||||
}
|
||||
|
||||
func validateAWSZones(t *testing.T, zones map[string]*route53.HostedZone, expected map[string]*route53.HostedZone) {
|
||||
@ -1840,51 +1843,6 @@ 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,
|
||||
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
|
||||
}
|
||||
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"),
|
||||
@ -1908,7 +1866,7 @@ func setAWSRecords(t *testing.T, provider *AWSProvider, records []*route53.Resou
|
||||
endpoints, err := provider.Records(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
validateEndpoints(t, endpoints, []*endpoint.Endpoint{})
|
||||
validateEndpoints(t, provider, endpoints, []*endpoint.Endpoint{})
|
||||
|
||||
var changes Route53Changes
|
||||
for _, record := range records {
|
||||
@ -2071,13 +2029,13 @@ func TestRequiresDeleteCreate(t *testing.T) {
|
||||
provider, _ := newAWSProvider(t, endpoint.NewDomainFilter([]string{"foo.bar."}), provider.NewZoneIDFilter([]string{}), provider.NewZoneTypeFilter(""), defaultEvaluateTargetHealth, false, nil)
|
||||
|
||||
oldRecordType := endpoint.NewEndpointWithTTL("recordType", endpoint.RecordTypeA, endpoint.TTL(recordTTL), "8.8.8.8")
|
||||
newRecordType := endpoint.NewEndpointWithTTL("recordType", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar")
|
||||
newRecordType := endpoint.NewEndpointWithTTL("recordType", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar").WithProviderSpecific(providerSpecificAlias, "false")
|
||||
|
||||
assert.False(t, provider.requiresDeleteCreate(oldRecordType, oldRecordType), "actual and expected endpoints don't match. %+v:%+v", oldRecordType, oldRecordType)
|
||||
assert.True(t, provider.requiresDeleteCreate(oldRecordType, newRecordType), "actual and expected endpoints don't match. %+v:%+v", oldRecordType, newRecordType)
|
||||
|
||||
oldCNAMEAlias := endpoint.NewEndpointWithTTL("CNAMEAlias", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar")
|
||||
newCNAMEAlias := endpoint.NewEndpointWithTTL("CNAMEAlias", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.us-east-1.elb.amazonaws.com")
|
||||
oldCNAMEAlias := endpoint.NewEndpointWithTTL("CNAMEAlias", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar").WithProviderSpecific(providerSpecificAlias, "false")
|
||||
newCNAMEAlias := endpoint.NewEndpointWithTTL("CNAMEAlias", endpoint.RecordTypeCNAME, endpoint.TTL(recordTTL), "bar.us-east-1.elb.amazonaws.com").WithProviderSpecific(providerSpecificAlias, "true")
|
||||
|
||||
assert.False(t, provider.requiresDeleteCreate(oldCNAMEAlias, oldCNAMEAlias), "actual and expected endpoints don't match. %+v:%+v", oldCNAMEAlias, oldCNAMEAlias.DNSName)
|
||||
assert.True(t, provider.requiresDeleteCreate(oldCNAMEAlias, newCNAMEAlias), "actual and expected endpoints don't match. %+v:%+v", oldCNAMEAlias, newCNAMEAlias)
|
||||
|
@ -30,6 +30,13 @@ 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 canonicalizes a set of candidate endpoints.
|
||||
// It is called with a set of candidate endpoints obtained from the various sources.
|
||||
// It returns a set modified as required by the provider. The provider is responsible for
|
||||
// adding, removing, and modifying the ProviderSpecific properties to match
|
||||
// the endpoints that the provider returns in `Records` so that the change plan will not have
|
||||
// unnecessary (potentially failing) changes. It may also modify other fields, add, or remove
|
||||
// Endpoints. It is permitted to modify the supplied endpoints.
|
||||
AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoint.Endpoint
|
||||
GetDomainFilter() endpoint.DomainFilterInterface
|
||||
}
|
||||
|
@ -278,9 +278,9 @@ func endpointToScalewayRecords(zoneName string, ep *endpoint.Endpoint) []*domain
|
||||
}
|
||||
priority := scalewayDefaultPriority
|
||||
if prop, ok := ep.GetProviderSpecificProperty(scalewayPriorityKey); ok {
|
||||
prio, err := strconv.ParseUint(prop.Value, 10, 32)
|
||||
prio, err := strconv.ParseUint(prop, 10, 32)
|
||||
if err != nil {
|
||||
log.Errorf("Failed parsing value of %s: %s: %v; using priority of %d", scalewayPriorityKey, prop.Value, err, scalewayDefaultPriority)
|
||||
log.Errorf("Failed parsing value of %s: %s: %v; using priority of %d", scalewayPriorityKey, prop, err, scalewayDefaultPriority)
|
||||
} else {
|
||||
priority = uint32(prio)
|
||||
}
|
||||
|
@ -64,20 +64,35 @@ type proxySpecHTTPListener struct {
|
||||
}
|
||||
|
||||
type proxyVirtualHost struct {
|
||||
Domains []string `json:"domains,omitempty"`
|
||||
Metadata proxyVirtualHostMetadata `json:"metadata,omitempty"`
|
||||
Domains []string `json:"domains,omitempty"`
|
||||
Metadata proxyVirtualHostMetadata `json:"metadata,omitempty"`
|
||||
MetadataStatic proxyVirtualHostMetadataStatic `json:"metadataStatic,omitempty"`
|
||||
}
|
||||
|
||||
type proxyVirtualHostMetadata struct {
|
||||
Source []proxyVirtualHostMetadataSource `json:"sources,omitempty"`
|
||||
}
|
||||
|
||||
type proxyVirtualHostMetadataStatic struct {
|
||||
Source []proxyVirtualHostMetadataStaticSource `json:"sources,omitempty"`
|
||||
}
|
||||
|
||||
type proxyVirtualHostMetadataSource struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
type proxyVirtualHostMetadataStaticSource struct {
|
||||
ResourceKind string `json:"resourceKind,omitempty"`
|
||||
ResourceRef proxyVirtualHostMetadataSourceResourceRef `json:"resourceRef,omitempty"`
|
||||
}
|
||||
|
||||
type proxyVirtualHostMetadataSourceResourceRef struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
type glooSource struct {
|
||||
dynamicKubeClient dynamic.Interface
|
||||
kubeClient kubernetes.Interface
|
||||
@ -165,6 +180,18 @@ func (gs *glooSource) annotationsFromProxySource(ctx context.Context, virtualHos
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, src := range virtualHost.MetadataStatic.Source {
|
||||
kind := sourceKind(src.ResourceKind)
|
||||
if kind != nil {
|
||||
source, err := gs.dynamicKubeClient.Resource(*kind).Namespace(src.ResourceRef.Namespace).Get(ctx, src.ResourceRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range source.GetAnnotations() {
|
||||
annotations[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return annotations, nil
|
||||
}
|
||||
|
||||
|
@ -211,6 +211,97 @@ var externalProxySource = metav1.PartialObjectMetadata{
|
||||
},
|
||||
}
|
||||
|
||||
// Proxy with metadata static test
|
||||
var proxyMetadataStatic = proxy{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: proxyGVR.GroupVersion().String(),
|
||||
Kind: "Proxy",
|
||||
},
|
||||
Metadata: metav1.ObjectMeta{
|
||||
Name: "internal-static",
|
||||
Namespace: defaultGlooNamespace,
|
||||
},
|
||||
Spec: proxySpec{
|
||||
Listeners: []proxySpecListener{
|
||||
{
|
||||
HTTPListener: proxySpecHTTPListener{
|
||||
VirtualHosts: []proxyVirtualHost{
|
||||
{
|
||||
Domains: []string{"f.test", "g.test"},
|
||||
MetadataStatic: proxyVirtualHostMetadataStatic{
|
||||
Source: []proxyVirtualHostMetadataStaticSource{
|
||||
{
|
||||
ResourceKind: "*v1.Unknown",
|
||||
ResourceRef: proxyVirtualHostMetadataSourceResourceRef{
|
||||
Name: "my-unknown-svc",
|
||||
Namespace: "unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Domains: []string{"h.test"},
|
||||
MetadataStatic: proxyVirtualHostMetadataStatic{
|
||||
Source: []proxyVirtualHostMetadataStaticSource{
|
||||
{
|
||||
ResourceKind: "*v1.VirtualService",
|
||||
ResourceRef: proxyVirtualHostMetadataSourceResourceRef{
|
||||
Name: "my-internal-static-svc",
|
||||
Namespace: "internal-static",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var proxyMetadataStaticSvc = corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: proxyMetadataStatic.Metadata.Name,
|
||||
Namespace: proxyMetadataStatic.Metadata.Namespace,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeLoadBalancer,
|
||||
},
|
||||
Status: corev1.ServiceStatus{
|
||||
LoadBalancer: corev1.LoadBalancerStatus{
|
||||
Ingress: []corev1.LoadBalancerIngress{
|
||||
{
|
||||
IP: "203.0.115.1",
|
||||
},
|
||||
{
|
||||
IP: "203.0.115.2",
|
||||
},
|
||||
{
|
||||
IP: "203.0.115.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var proxyMetadataStaticSource = metav1.PartialObjectMetadata{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: virtualServiceGVR.GroupVersion().String(),
|
||||
Kind: "VirtualService",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: proxyMetadataStatic.Spec.Listeners[0].HTTPListener.VirtualHosts[1].MetadataStatic.Source[0].ResourceRef.Name,
|
||||
Namespace: proxyMetadataStatic.Spec.Listeners[0].HTTPListener.VirtualHosts[1].MetadataStatic.Source[0].ResourceRef.Namespace,
|
||||
Annotations: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/ttl": "420",
|
||||
"external-dns.alpha.kubernetes.io/aws-geolocation-country-code": "ES",
|
||||
"external-dns.alpha.kubernetes.io/set-identifier": "identifier",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestGlooSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -226,9 +317,11 @@ func TestGlooSource(t *testing.T) {
|
||||
|
||||
internalProxyUnstructured := unstructured.Unstructured{}
|
||||
externalProxyUnstructured := unstructured.Unstructured{}
|
||||
proxyMetadataStaticUnstructured := unstructured.Unstructured{}
|
||||
|
||||
internalProxySourceUnstructured := unstructured.Unstructured{}
|
||||
externalProxySourceUnstructured := unstructured.Unstructured{}
|
||||
proxyMetadataStaticSourceUnstructured := unstructured.Unstructured{}
|
||||
|
||||
internalProxyAsJSON, err := json.Marshal(internalProxy)
|
||||
assert.NoError(t, err)
|
||||
@ -236,39 +329,53 @@ func TestGlooSource(t *testing.T) {
|
||||
externalProxyAsJSON, err := json.Marshal(externalProxy)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxyMetadataStaticAsJSON, err := json.Marshal(proxyMetadataStatic)
|
||||
assert.NoError(t, err)
|
||||
|
||||
internalProxySvcAsJSON, err := json.Marshal(internalProxySource)
|
||||
assert.NoError(t, err)
|
||||
|
||||
externalProxySvcAsJSON, err := json.Marshal(externalProxySource)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxyMetadataStaticSvcAsJSON, err := json.Marshal(proxyMetadataStaticSource)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, internalProxyUnstructured.UnmarshalJSON(internalProxyAsJSON))
|
||||
assert.NoError(t, externalProxyUnstructured.UnmarshalJSON(externalProxyAsJSON))
|
||||
assert.NoError(t, proxyMetadataStaticUnstructured.UnmarshalJSON(proxyMetadataStaticAsJSON))
|
||||
|
||||
assert.NoError(t, internalProxySourceUnstructured.UnmarshalJSON(internalProxySvcAsJSON))
|
||||
assert.NoError(t, externalProxySourceUnstructured.UnmarshalJSON(externalProxySvcAsJSON))
|
||||
assert.NoError(t, proxyMetadataStaticSourceUnstructured.UnmarshalJSON(proxyMetadataStaticSvcAsJSON))
|
||||
|
||||
// Create proxy resources
|
||||
_, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &internalProxyUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &externalProxyUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &proxyMetadataStaticUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create proxy source
|
||||
_, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(internalProxySource.Namespace).Create(context.Background(), &internalProxySourceUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(externalProxySource.Namespace).Create(context.Background(), &externalProxySourceUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(proxyMetadataStaticSource.Namespace).Create(context.Background(), &proxyMetadataStaticSourceUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create proxy service resources
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(internalProxySvc.GetNamespace()).Create(context.Background(), &internalProxySvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(externalProxySvc.GetNamespace()).Create(context.Background(), &externalProxySvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(proxyMetadataStaticSvc.GetNamespace()).Create(context.Background(), &proxyMetadataStaticSvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
endpoints, err := source.Endpoints(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, endpoints, 5)
|
||||
assert.Len(t, endpoints, 8)
|
||||
assert.ElementsMatch(t, endpoints, []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a.test",
|
||||
@ -322,5 +429,35 @@ func TestGlooSource(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "f.test",
|
||||
Targets: []string{proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 0,
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{},
|
||||
},
|
||||
{
|
||||
DNSName: "g.test",
|
||||
Targets: []string{proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 0,
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{},
|
||||
},
|
||||
{
|
||||
DNSName: "h.test",
|
||||
Targets: []string{proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
SetIdentifier: "identifier",
|
||||
RecordTTL: 420,
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{
|
||||
endpoint.ProviderSpecificProperty{
|
||||
Name: "aws/geolocation-country-code",
|
||||
Value: "ES",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -23,12 +23,12 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
|
||||
istioclient "istio.io/client-go/pkg/clientset/versioned"
|
||||
istioinformers "istio.io/client-go/pkg/informers/externalversions"
|
||||
networkingv1alpha3informer "istio.io/client-go/pkg/informers/externalversions/networking/v1alpha3"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
@ -203,7 +203,10 @@ func (sc *virtualServiceSource) getGateway(ctx context.Context, gatewayStr strin
|
||||
}
|
||||
|
||||
gateway, err := sc.istioClient.NetworkingV1alpha3().Gateways(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
log.Warnf("VirtualService (%s/%s) references non-existent gateway: %s ", virtualService.Namespace, virtualService.Name, gatewayStr)
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
log.Errorf("Failed retrieving gateway %s referenced by VirtualService %s/%s: %v", gatewayStr, virtualService.Namespace, virtualService.Name, err)
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,19 +18,23 @@ package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"istio.io/api/meta/v1alpha1"
|
||||
istionetworking "istio.io/api/networking/v1alpha3"
|
||||
networkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
|
||||
istiofake "istio.io/client-go/pkg/clientset/versioned/fake"
|
||||
fakenetworking3 "istio.io/client-go/pkg/clientset/versioned/typed/networking/v1alpha3/fake"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
k8sclienttesting "k8s.io/client-go/testing"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
)
|
||||
@ -1579,7 +1583,9 @@ func newTestVirtualServiceSource(loadBalancerList []fakeIngressGatewayService, g
|
||||
|
||||
for _, gw := range gwList {
|
||||
gwObj := gw.Config()
|
||||
_, err := fakeIstioClient.NetworkingV1alpha3().Gateways(gw.namespace).Create(context.Background(), gwObj, metav1.CreateOptions{})
|
||||
// use create instead of add
|
||||
// https://github.com/kubernetes/client-go/blob/92512ee2b8cf6696e9909245624175b7f0c971d9/testing/fixture.go#LL336C3-L336C52
|
||||
_, err := fakeIstioClient.NetworkingV1alpha3().Gateways(gw.namespace).Create(context.Background(), &gwObj, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1634,3 +1640,118 @@ func (c fakeVirtualServiceConfig) Config() *networkingv1alpha3.VirtualService {
|
||||
Spec: vs,
|
||||
}
|
||||
}
|
||||
|
||||
func TestVirtualServiceSourceGetGateway(t *testing.T) {
|
||||
type fields struct {
|
||||
virtualServiceSource *virtualServiceSource
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
gatewayStr string
|
||||
virtualService *networkingv1alpha3.VirtualService
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want *networkingv1alpha3.Gateway
|
||||
expectedErrStr string
|
||||
}{
|
||||
{name: "EmptyGateway", fields: fields{
|
||||
virtualServiceSource: func() *virtualServiceSource { vs, _ := newTestVirtualServiceSource(nil, nil); return vs }(),
|
||||
}, args: args{
|
||||
ctx: context.TODO(),
|
||||
gatewayStr: "",
|
||||
virtualService: nil,
|
||||
}, want: nil, expectedErrStr: ""},
|
||||
{name: "MeshGateway", fields: fields{
|
||||
virtualServiceSource: func() *virtualServiceSource { vs, _ := newTestVirtualServiceSource(nil, nil); return vs }(),
|
||||
}, args: args{
|
||||
ctx: context.TODO(),
|
||||
gatewayStr: IstioMeshGateway,
|
||||
virtualService: nil,
|
||||
}, want: nil, expectedErrStr: ""},
|
||||
{name: "MissingGateway", fields: fields{
|
||||
virtualServiceSource: func() *virtualServiceSource { vs, _ := newTestVirtualServiceSource(nil, nil); return vs }(),
|
||||
}, args: args{
|
||||
ctx: context.TODO(),
|
||||
gatewayStr: "doesnt/exist",
|
||||
virtualService: &networkingv1alpha3.VirtualService{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "exist", Namespace: "doesnt"},
|
||||
Spec: istionetworking.VirtualService{},
|
||||
Status: v1alpha1.IstioStatus{},
|
||||
},
|
||||
}, want: nil, expectedErrStr: ""},
|
||||
{name: "InvalidGatewayStr", fields: fields{
|
||||
virtualServiceSource: func() *virtualServiceSource { vs, _ := newTestVirtualServiceSource(nil, nil); return vs }(),
|
||||
}, args: args{
|
||||
ctx: context.TODO(),
|
||||
gatewayStr: "1/2/3/",
|
||||
virtualService: &networkingv1alpha3.VirtualService{},
|
||||
}, want: nil, expectedErrStr: "invalid gateway name (name or namespace/name) found '1/2/3/'"},
|
||||
{name: "ExistingGateway", fields: fields{
|
||||
virtualServiceSource: func() *virtualServiceSource {
|
||||
vs, _ := newTestVirtualServiceSource(nil, []fakeGatewayConfig{{
|
||||
namespace: "bar",
|
||||
name: "foo",
|
||||
}})
|
||||
return vs
|
||||
}(),
|
||||
}, args: args{
|
||||
ctx: context.TODO(),
|
||||
gatewayStr: "bar/foo",
|
||||
virtualService: &networkingv1alpha3.VirtualService{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
|
||||
Spec: istionetworking.VirtualService{},
|
||||
Status: v1alpha1.IstioStatus{},
|
||||
},
|
||||
}, want: &networkingv1alpha3.Gateway{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "bar"},
|
||||
Spec: istionetworking.Gateway{},
|
||||
Status: v1alpha1.IstioStatus{},
|
||||
}, expectedErrStr: ""},
|
||||
{name: "ErrorGettingGateway", fields: fields{
|
||||
virtualServiceSource: func() *virtualServiceSource {
|
||||
istioFake := istiofake.NewSimpleClientset()
|
||||
istioFake.NetworkingV1alpha3().(*fakenetworking3.FakeNetworkingV1alpha3).PrependReactor("get", "gateways", func(action k8sclienttesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
return true, &networkingv1alpha3.Gateway{}, fmt.Errorf("error getting gateway")
|
||||
})
|
||||
vs, _ := NewIstioVirtualServiceSource(
|
||||
context.TODO(),
|
||||
fake.NewSimpleClientset(),
|
||||
istioFake,
|
||||
"",
|
||||
"",
|
||||
"{{.Name}}",
|
||||
false,
|
||||
false,
|
||||
)
|
||||
return vs.(*virtualServiceSource)
|
||||
}(),
|
||||
}, args: args{
|
||||
ctx: context.TODO(),
|
||||
gatewayStr: "foo/bar",
|
||||
virtualService: &networkingv1alpha3.VirtualService{
|
||||
TypeMeta: metav1.TypeMeta{},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "gateway", Namespace: "error"},
|
||||
Spec: istionetworking.VirtualService{},
|
||||
Status: v1alpha1.IstioStatus{},
|
||||
},
|
||||
}, want: nil, expectedErrStr: "error getting gateway"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.fields.virtualServiceSource.getGateway(tt.args.ctx, tt.args.gatewayStr, tt.args.virtualService)
|
||||
if tt.expectedErrStr != "" {
|
||||
assert.EqualError(t, err, tt.expectedErrStr, fmt.Sprintf("getGateway(%v, %v, %v)", tt.args.ctx, tt.args.gatewayStr, tt.args.virtualService))
|
||||
return
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "getGateway(%v, %v, %v)", tt.args.ctx, tt.args.gatewayStr, tt.args.virtualService)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -292,6 +292,16 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
|
||||
return nil, err
|
||||
}
|
||||
return NewGlooSource(dynamicClient, kubernetesClient, cfg.GlooNamespace)
|
||||
case "traefik-proxy":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewTraefikSource(ctx, dynamicClient, kubernetesClient, cfg.Namespace, cfg.AnnotationFilter)
|
||||
case "openshift-route":
|
||||
ocpClient, err := p.OpenShiftClient()
|
||||
if err != nil {
|
||||
|
@ -130,11 +130,41 @@ func (suite *ByNamesTestSuite) TestAllInitialized() {
|
||||
Version: "v1",
|
||||
Resource: "virtualservers",
|
||||
}: "VirtualServersList",
|
||||
{
|
||||
Group: "traefik.containo.us",
|
||||
Version: "v1alpha1",
|
||||
Resource: "ingressroutes",
|
||||
}: "IngressRouteList",
|
||||
{
|
||||
Group: "traefik.containo.us",
|
||||
Version: "v1alpha1",
|
||||
Resource: "ingressroutetcps",
|
||||
}: "IngressRouteTCPList",
|
||||
{
|
||||
Group: "traefik.containo.us",
|
||||
Version: "v1alpha1",
|
||||
Resource: "ingressrouteudps",
|
||||
}: "IngressRouteUDPList",
|
||||
{
|
||||
Group: "traefik.io",
|
||||
Version: "v1alpha1",
|
||||
Resource: "ingressroutes",
|
||||
}: "IngressRouteList",
|
||||
{
|
||||
Group: "traefik.io",
|
||||
Version: "v1alpha1",
|
||||
Resource: "ingressroutetcps",
|
||||
}: "IngressRouteTCPList",
|
||||
{
|
||||
Group: "traefik.io",
|
||||
Version: "v1alpha1",
|
||||
Resource: "ingressrouteudps",
|
||||
}: "IngressRouteUDPList",
|
||||
}), nil)
|
||||
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-httpproxy", "kong-tcpingress", "f5-virtualserver", "fake"}, minimalConfig)
|
||||
sources, err := ByNames(context.TODO(), mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-httpproxy", "kong-tcpingress", "f5-virtualserver", "traefik-proxy", "fake"}, minimalConfig)
|
||||
suite.NoError(err, "should not generate errors")
|
||||
suite.Len(sources, 7, "should generate all seven sources")
|
||||
suite.Len(sources, 8, "should generate all eight sources")
|
||||
}
|
||||
|
||||
func (suite *ByNamesTestSuite) TestOnlyFake() {
|
||||
@ -171,9 +201,6 @@ func (suite *ByNamesTestSuite) TestKubeClientFails() {
|
||||
|
||||
_, err = ByNames(context.TODO(), mockClientGenerator, []string{"kong-tcpingress"}, minimalConfig)
|
||||
suite.Error(err, "should return an error if kubernetes client cannot be created")
|
||||
|
||||
_, err = ByNames(context.TODO(), mockClientGenerator, []string{"f5-virtualserver"}, minimalConfig)
|
||||
suite.Error(err, "should return an error if kubernetes client cannot be created")
|
||||
}
|
||||
|
||||
func (suite *ByNamesTestSuite) TestIstioClientFails() {
|
||||
|
1125
source/traefik_proxy.go
Normal file
1125
source/traefik_proxy.go
Normal file
File diff suppressed because it is too large
Load Diff
1330
source/traefik_proxy_test.go
Normal file
1330
source/traefik_proxy_test.go
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user