mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 09:36:58 +02:00
Merge remote-tracking branch 'kubernetes-sigs/master' into godaddy
This commit is contained in:
commit
f7f90733ce
@ -17,6 +17,7 @@
|
||||
- Update contributing section in README (#1760) @seanmalloy
|
||||
- Option to cache AWS zones list @bpineau
|
||||
- Refactor, enhance and test Akamai provider and documentation (#1846) @edglynes
|
||||
- Fix: only use absolute CNAMEs in Scaleway provider (#1859) @Sh4d1
|
||||
|
||||
## v0.7.3 - 2020-08-05
|
||||
|
||||
|
@ -34,17 +34,19 @@ This is crucial as ExternalDNS reads those endpoints records when creating DNS-R
|
||||
In the subsequent parameter we will make use of this. If you don't want to work with ingress-resources in your later use, you can leave the parameter out.
|
||||
|
||||
Verify the correct propagation of the loadbalancer's ip by listing the ingresses.
|
||||
|
||||
```
|
||||
$ kubectl get ingress
|
||||
```
|
||||
|
||||
The address column should contain the ip for each ingress. ExternalDNS will pick up exactly this piece of information.
|
||||
|
||||
```
|
||||
NAME HOSTS ADDRESS PORTS AGE
|
||||
nginx1 sample1.aks.com 52.167.195.110 80 6d22h
|
||||
nginx2 sample2.aks.com 52.167.195.110 80 6d21h
|
||||
```
|
||||
|
||||
|
||||
If you do not want to deploy the ingress controller with Helm, ensure to pass the following cmdline-flags to it through the mechanism of your choice:
|
||||
|
||||
```
|
||||
@ -144,6 +146,8 @@ This is per default done through the file `~/.kube/config`.
|
||||
For general background information on this see [kubernetes-docs](https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/).
|
||||
Azure-CLI features functionality for automatically maintaining this file for AKS-Clusters. See [Azure-Docs](https://docs.microsoft.com/de-de/cli/azure/aks?view=azure-cli-latest#az-aks-get-credentials).
|
||||
|
||||
Follow the steps for [azure-dns provider](./azure.md#creating-configuration-file) to create a configuration file.
|
||||
|
||||
Then apply one of the following manifests depending on whether you use RBAC or not.
|
||||
|
||||
The credentials of the service principal are provided to ExternalDNS as environment-variables.
|
||||
@ -175,13 +179,14 @@ spec:
|
||||
- --provider=azure-private-dns
|
||||
- --azure-resource-group=externaldns
|
||||
- --azure-subscription-id=<use the id of your subscription>
|
||||
env:
|
||||
- name: AZURE_TENANT_ID
|
||||
value: "<use the tenantId discovered during creation of service principal>"
|
||||
- name: AZURE_CLIENT_ID
|
||||
value: "<use the aadClientId discovered during creation of service principal>"
|
||||
- name: AZURE_CLIENT_SECRET
|
||||
value: "<use the aadClientSecret discovered during creation of service principal>"
|
||||
volumeMounts:
|
||||
- name: azure-config-file
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: azure-config-file
|
||||
secret:
|
||||
secretName: azure-config-file
|
||||
```
|
||||
|
||||
### Manifest (for clusters with RBAC enabled, cluster access)
|
||||
@ -245,13 +250,14 @@ spec:
|
||||
- --provider=azure-private-dns
|
||||
- --azure-resource-group=externaldns
|
||||
- --azure-subscription-id=<use the id of your subscription>
|
||||
env:
|
||||
- name: AZURE_TENANT_ID
|
||||
value: "<use the tenantId discovered during creation of service principal>"
|
||||
- name: AZURE_CLIENT_ID
|
||||
value: "<use the aadClientId discovered during creation of service principal>"
|
||||
- name: AZURE_CLIENT_SECRET
|
||||
value: "<use the aadClientSecret discovered during creation of service principal>"
|
||||
volumeMounts:
|
||||
- name: azure-config-file
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: azure-config-file
|
||||
secret:
|
||||
secretName: azure-config-file
|
||||
```
|
||||
|
||||
### Manifest (for clusters with RBAC enabled, namespace access)
|
||||
@ -315,13 +321,14 @@ spec:
|
||||
- --provider=azure-private-dns
|
||||
- --azure-resource-group=externaldns
|
||||
- --azure-subscription-id=<use the id of your subscription>
|
||||
env:
|
||||
- name: AZURE_TENANT_ID
|
||||
value: "<use the tenantId discovered during creation of service principal>"
|
||||
- name: AZURE_CLIENT_ID
|
||||
value: "<use the aadClientId discovered during creation of service principal>"
|
||||
- name: AZURE_CLIENT_SECRET
|
||||
value: "<use the aadClientSecret discovered during creation of service principal>"
|
||||
volumeMounts:
|
||||
- name: azure-config-file
|
||||
mountPath: /etc/kubernetes
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: azure-config-file
|
||||
secret:
|
||||
secretName: azure-config-file
|
||||
```
|
||||
|
||||
Create the deployment for ExternalDNS:
|
||||
|
@ -78,7 +78,7 @@ rules:
|
||||
See also current RBAC yaml files:
|
||||
- [kube-ingress-aws-controller](https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/manifests/ingress-controller/01-rbac.yaml)
|
||||
- [skipper](https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/manifests/skipper/rbac.yaml)
|
||||
- [external-dns](https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/manifests/external-dns/rbac.yaml)
|
||||
- [external-dns](https://github.com/zalando-incubator/kubernetes-on-aws/blob/dev/cluster/manifests/external-dns/01-rbac.yaml)
|
||||
|
||||
[3]: https://opensource.zalando.com/skipper/kubernetes/routegroups/#routegroups
|
||||
[4]: https://opensource.zalando.com/skipper
|
||||
|
14
go.mod
14
go.mod
@ -8,7 +8,6 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go v45.1.0+incompatible
|
||||
github.com/Azure/go-autorest/autorest v0.11.10
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.5
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.3
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
|
||||
@ -19,6 +18,7 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.31.4
|
||||
github.com/cloudflare/cloudflare-go v0.10.1
|
||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
||||
github.com/datawire/ambassador v1.6.0
|
||||
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba
|
||||
github.com/digitalocean/godo v1.36.0
|
||||
github.com/dnsimple/dnsimple-go v0.60.0
|
||||
@ -46,7 +46,8 @@ require (
|
||||
github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/smartystreets/gunit v1.3.4 // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/terra-farm/udnssdk v1.3.5 // indirect
|
||||
github.com/transip/gotransip v5.8.2+incompatible
|
||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
||||
@ -54,17 +55,20 @@ require (
|
||||
github.com/vultr/govultr v0.4.2
|
||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875
|
||||
go.uber.org/ratelimit v0.1.0
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
||||
golang.org/x/tools v0.0.0-20200708003708-134513de8882 // indirect
|
||||
google.golang.org/api v0.15.0
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
honnef.co/go/tools v0.0.1-2020.1.4 // indirect
|
||||
istio.io/api v0.0.0-20200529165953-72dad51d4ffc
|
||||
istio.io/client-go v0.0.0-20200529172309-31c16ea3f751
|
||||
k8s.io/api v0.18.8
|
||||
k8s.io/apimachinery v0.18.8
|
||||
k8s.io/client-go v0.18.8
|
||||
k8s.io/kubernetes v1.13.0
|
||||
)
|
||||
|
||||
replace (
|
||||
|
2
main.go
2
main.go
@ -193,7 +193,7 @@ func main() {
|
||||
case "azure-dns", "azure":
|
||||
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
||||
case "azure-private-dns":
|
||||
p, err = azure.NewAzurePrivateDNSProvider(domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureSubscriptionID, cfg.DryRun)
|
||||
p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
||||
case "vinyldns":
|
||||
p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||
case "vultr":
|
||||
|
@ -326,7 +326,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 sources
|
||||
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, crd, empty, skipper-routegroup,openshift-route)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route")
|
||||
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host")
|
||||
|
||||
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)
|
||||
|
@ -19,17 +19,12 @@ package azure
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
@ -41,18 +36,6 @@ const (
|
||||
azureRecordTTL = 300
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Cloud string `json:"cloud" yaml:"cloud"`
|
||||
TenantID string `json:"tenantId" yaml:"tenantId"`
|
||||
SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"`
|
||||
ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"`
|
||||
Location string `json:"location" yaml:"location"`
|
||||
ClientID string `json:"aadClientId" yaml:"aadClientId"`
|
||||
ClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"`
|
||||
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension" yaml:"useManagedIdentityExtension"`
|
||||
UserAssignedIdentityID string `json:"userAssignedIdentityID" yaml:"userAssignedIdentityID"`
|
||||
}
|
||||
|
||||
// ZonesClient is an interface of dns.ZoneClient that can be stubbed for testing.
|
||||
type ZonesClient interface {
|
||||
ListByResourceGroupComplete(ctx context.Context, resourceGroupName string, top *int32) (result dns.ZoneListResultIterator, err error)
|
||||
@ -82,46 +65,22 @@ type AzureProvider struct {
|
||||
//
|
||||
// Returns the provider or an error if a provider could not be created.
|
||||
func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zoneNameFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup string, userAssignedIdentityClientID string, dryRun bool) (*AzureProvider, error) {
|
||||
contents, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||
}
|
||||
cfg := config{}
|
||||
err = yaml.Unmarshal(contents, &cfg)
|
||||
cfg, err := getConfig(configFile, resourceGroup, userAssignedIdentityClientID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||
}
|
||||
|
||||
// If a resource group was given, override what was present in the config file
|
||||
if resourceGroup != "" {
|
||||
cfg.ResourceGroup = resourceGroup
|
||||
}
|
||||
// If userAssignedIdentityClientID is provided explicitly, override existing one in config file
|
||||
if userAssignedIdentityClientID != "" {
|
||||
cfg.UserAssignedIdentityID = userAssignedIdentityClientID
|
||||
}
|
||||
|
||||
var environment azure.Environment
|
||||
if cfg.Cloud == "" {
|
||||
environment = azure.PublicCloud
|
||||
} else {
|
||||
environment, err = azure.EnvironmentFromName(cfg.Cloud)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid cloud value '%s': %v", cfg.Cloud, err)
|
||||
}
|
||||
}
|
||||
|
||||
token, err := getAccessToken(cfg, environment)
|
||||
token, err := getAccessToken(*cfg, cfg.Environment)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get token: %v", err)
|
||||
}
|
||||
|
||||
zonesClient := dns.NewZonesClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||
zonesClient := dns.NewZonesClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||
zonesClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||
recordSetsClient := dns.NewRecordSetsClientWithBaseURI(environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||
recordSetsClient := dns.NewRecordSetsClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||
recordSetsClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||
|
||||
provider := &AzureProvider{
|
||||
return &AzureProvider{
|
||||
domainFilter: domainFilter,
|
||||
zoneNameFilter: zoneNameFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
@ -130,61 +89,7 @@ func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zon
|
||||
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
||||
zonesClient: zonesClient,
|
||||
recordSetsClient: recordSetsClient,
|
||||
}
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
// getAccessToken retrieves Azure API access token.
|
||||
func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePrincipalToken, error) {
|
||||
// Try to retrieve token with service principal credentials.
|
||||
// Try to use service principal first, some AKS clusters are in an intermediate state that `UseManagedIdentityExtension` is `true`
|
||||
// and service principal exists. In this case, we still want to use service principal to authenticate.
|
||||
if len(cfg.ClientID) > 0 &&
|
||||
len(cfg.ClientSecret) > 0 &&
|
||||
// due to some historical reason, for pure MSI cluster,
|
||||
// they will use "msi" as placeholder in azure.json.
|
||||
// In this case, we shouldn't try to use SPN to authenticate.
|
||||
!strings.EqualFold(cfg.ClientID, "msi") &&
|
||||
!strings.EqualFold(cfg.ClientSecret, "msi") {
|
||||
log.Info("Using client_id+client_secret to retrieve access token for Azure API.")
|
||||
oauthConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, cfg.TenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve OAuth config: %v", err)
|
||||
}
|
||||
|
||||
token, err := adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, environment.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create service principal token: %v", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Try to retrieve token with MSI.
|
||||
if cfg.UseManagedIdentityExtension {
|
||||
log.Info("Using managed identity extension to retrieve access token for Azure API.")
|
||||
msiEndpoint, err := adal.GetMSIVMEndpoint()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the managed service identity endpoint: %v", err)
|
||||
}
|
||||
|
||||
if cfg.UserAssignedIdentityID != "" {
|
||||
log.Infof("Resolving to user assigned identity, client id is %s.", cfg.UserAssignedIdentityID)
|
||||
token, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, environment.ServiceManagementEndpoint, cfg.UserAssignedIdentityID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the managed service identity token: %v", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
log.Info("Resolving to system assigned identity.")
|
||||
token, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, environment.ServiceManagementEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the managed service identity token: %v", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no credentials provided for Azure API")
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Records gets the current records.
|
||||
@ -352,20 +257,20 @@ func (p *AzureProvider) mapChanges(zones []dns.Zone, changes *plan.Changes) (azu
|
||||
func (p *AzureProvider) deleteRecords(ctx context.Context, deleted azureChangeMap) {
|
||||
// Delete records first
|
||||
for zone, endpoints := range deleted {
|
||||
for _, endpoint := range endpoints {
|
||||
name := p.recordSetNameForZone(zone, endpoint)
|
||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
||||
log.Debugf("Skipping deletion of record %s because it was filtered out by the specified --domain-filter", endpoint.DNSName)
|
||||
for _, ep := range endpoints {
|
||||
name := p.recordSetNameForZone(zone, ep)
|
||||
if !p.domainFilter.Match(ep.DNSName) {
|
||||
log.Debugf("Skipping deletion of record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||
continue
|
||||
}
|
||||
if p.dryRun {
|
||||
log.Infof("Would delete %s record named '%s' for Azure DNS zone '%s'.", endpoint.RecordType, name, zone)
|
||||
log.Infof("Would delete %s record named '%s' for Azure DNS zone '%s'.", ep.RecordType, name, zone)
|
||||
} else {
|
||||
log.Infof("Deleting %s record named '%s' for Azure DNS zone '%s'.", endpoint.RecordType, name, zone)
|
||||
if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, name, dns.RecordType(endpoint.RecordType), ""); err != nil {
|
||||
log.Infof("Deleting %s record named '%s' for Azure DNS zone '%s'.", ep.RecordType, name, zone)
|
||||
if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, name, dns.RecordType(ep.RecordType), ""); err != nil {
|
||||
log.Errorf(
|
||||
"Failed to delete %s record named '%s' for Azure DNS zone '%s': %v",
|
||||
endpoint.RecordType,
|
||||
ep.RecordType,
|
||||
name,
|
||||
zone,
|
||||
err,
|
||||
@ -378,18 +283,18 @@ func (p *AzureProvider) deleteRecords(ctx context.Context, deleted azureChangeMa
|
||||
|
||||
func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMap) {
|
||||
for zone, endpoints := range updated {
|
||||
for _, endpoint := range endpoints {
|
||||
name := p.recordSetNameForZone(zone, endpoint)
|
||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
||||
log.Debugf("Skipping update of record %s because it was filtered out by the specified --domain-filter", endpoint.DNSName)
|
||||
for _, ep := range endpoints {
|
||||
name := p.recordSetNameForZone(zone, ep)
|
||||
if !p.domainFilter.Match(ep.DNSName) {
|
||||
log.Debugf("Skipping update of record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||
continue
|
||||
}
|
||||
if p.dryRun {
|
||||
log.Infof(
|
||||
"Would update %s record named '%s' to '%s' for Azure DNS zone '%s'.",
|
||||
endpoint.RecordType,
|
||||
ep.RecordType,
|
||||
name,
|
||||
endpoint.Targets,
|
||||
ep.Targets,
|
||||
zone,
|
||||
)
|
||||
continue
|
||||
@ -397,20 +302,20 @@ func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMa
|
||||
|
||||
log.Infof(
|
||||
"Updating %s record named '%s' to '%s' for Azure DNS zone '%s'.",
|
||||
endpoint.RecordType,
|
||||
ep.RecordType,
|
||||
name,
|
||||
endpoint.Targets,
|
||||
ep.Targets,
|
||||
zone,
|
||||
)
|
||||
|
||||
recordSet, err := p.newRecordSet(endpoint)
|
||||
recordSet, err := p.newRecordSet(ep)
|
||||
if err == nil {
|
||||
_, err = p.recordSetsClient.CreateOrUpdate(
|
||||
ctx,
|
||||
p.resourceGroup,
|
||||
zone,
|
||||
name,
|
||||
dns.RecordType(endpoint.RecordType),
|
||||
dns.RecordType(ep.RecordType),
|
||||
recordSet,
|
||||
"",
|
||||
"",
|
||||
@ -419,9 +324,9 @@ func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMa
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
"Failed to update %s record named '%s' to '%s' for DNS zone '%s': %v",
|
||||
endpoint.RecordType,
|
||||
ep.RecordType,
|
||||
name,
|
||||
endpoint.Targets,
|
||||
ep.Targets,
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
|
@ -21,9 +21,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/privatedns/mgmt/privatedns"
|
||||
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@ -47,44 +46,43 @@ type PrivateRecordSetsClient interface {
|
||||
// AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service
|
||||
type AzurePrivateDNSProvider struct {
|
||||
provider.BaseProvider
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
dryRun bool
|
||||
subscriptionID string
|
||||
resourceGroup string
|
||||
zonesClient PrivateZonesClient
|
||||
recordSetsClient PrivateRecordSetsClient
|
||||
domainFilter endpoint.DomainFilter
|
||||
zoneIDFilter provider.ZoneIDFilter
|
||||
dryRun bool
|
||||
resourceGroup string
|
||||
userAssignedIdentityClientID string
|
||||
zonesClient PrivateZonesClient
|
||||
recordSetsClient PrivateRecordSetsClient
|
||||
}
|
||||
|
||||
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
||||
//
|
||||
// Returns the provider or an error if a provider could not be created.
|
||||
func NewAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup string, subscriptionID string, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
||||
authorizer, err := auth.NewAuthorizerFromEnvironment()
|
||||
func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup, userAssignedIdentityClientID string, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
||||
cfg, err := getConfig(configFile, resourceGroup, userAssignedIdentityClientID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||
}
|
||||
|
||||
settings, err := auth.GetSettingsFromEnvironment()
|
||||
token, err := getAccessToken(*cfg, cfg.Environment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to get token: %v", err)
|
||||
}
|
||||
|
||||
zonesClient := privatedns.NewPrivateZonesClientWithBaseURI(settings.Environment.ResourceManagerEndpoint, subscriptionID)
|
||||
zonesClient.Authorizer = authorizer
|
||||
recordSetsClient := privatedns.NewRecordSetsClientWithBaseURI(settings.Environment.ResourceManagerEndpoint, subscriptionID)
|
||||
recordSetsClient.Authorizer = authorizer
|
||||
zonesClient := privatedns.NewPrivateZonesClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||
zonesClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||
recordSetsClient := privatedns.NewRecordSetsClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||
recordSetsClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||
|
||||
provider := &AzurePrivateDNSProvider{
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
dryRun: dryRun,
|
||||
subscriptionID: subscriptionID,
|
||||
resourceGroup: resourceGroup,
|
||||
zonesClient: zonesClient,
|
||||
recordSetsClient: recordSetsClient,
|
||||
}
|
||||
return provider, nil
|
||||
return &AzurePrivateDNSProvider{
|
||||
domainFilter: domainFilter,
|
||||
zoneIDFilter: zoneIDFilter,
|
||||
dryRun: dryRun,
|
||||
resourceGroup: cfg.ResourceGroup,
|
||||
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
||||
zonesClient: zonesClient,
|
||||
recordSetsClient: recordSetsClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Records gets the current records.
|
||||
@ -256,16 +254,16 @@ func (p *AzurePrivateDNSProvider) deleteRecords(ctx context.Context, deleted azu
|
||||
log.Debugf("Records to be deleted: %d", len(deleted))
|
||||
// Delete records first
|
||||
for zone, endpoints := range deleted {
|
||||
for _, endpoint := range endpoints {
|
||||
name := p.recordSetNameForZone(zone, endpoint)
|
||||
for _, ep := range endpoints {
|
||||
name := p.recordSetNameForZone(zone, ep)
|
||||
if p.dryRun {
|
||||
log.Infof("Would delete %s record named '%s' for Azure Private DNS zone '%s'.", endpoint.RecordType, name, zone)
|
||||
log.Infof("Would delete %s record named '%s' for Azure Private DNS zone '%s'.", ep.RecordType, name, zone)
|
||||
} else {
|
||||
log.Infof("Deleting %s record named '%s' for Azure Private DNS zone '%s'.", endpoint.RecordType, name, zone)
|
||||
if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, privatedns.RecordType(endpoint.RecordType), name, ""); err != nil {
|
||||
log.Infof("Deleting %s record named '%s' for Azure Private DNS zone '%s'.", ep.RecordType, name, zone)
|
||||
if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, privatedns.RecordType(ep.RecordType), name, ""); err != nil {
|
||||
log.Errorf(
|
||||
"Failed to delete %s record named '%s' for Azure Private DNS zone '%s': %v",
|
||||
endpoint.RecordType,
|
||||
ep.RecordType,
|
||||
name,
|
||||
zone,
|
||||
err,
|
||||
@ -279,14 +277,14 @@ func (p *AzurePrivateDNSProvider) deleteRecords(ctx context.Context, deleted azu
|
||||
func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azurePrivateDNSChangeMap) {
|
||||
log.Debugf("Records to be updated: %d", len(updated))
|
||||
for zone, endpoints := range updated {
|
||||
for _, endpoint := range endpoints {
|
||||
name := p.recordSetNameForZone(zone, endpoint)
|
||||
for _, ep := range endpoints {
|
||||
name := p.recordSetNameForZone(zone, ep)
|
||||
if p.dryRun {
|
||||
log.Infof(
|
||||
"Would update %s record named '%s' to '%s' for Azure Private DNS zone '%s'.",
|
||||
endpoint.RecordType,
|
||||
ep.RecordType,
|
||||
name,
|
||||
endpoint.Targets,
|
||||
ep.Targets,
|
||||
zone,
|
||||
)
|
||||
continue
|
||||
@ -294,19 +292,19 @@ func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azu
|
||||
|
||||
log.Infof(
|
||||
"Updating %s record named '%s' to '%s' for Azure Private DNS zone '%s'.",
|
||||
endpoint.RecordType,
|
||||
ep.RecordType,
|
||||
name,
|
||||
endpoint.Targets,
|
||||
ep.Targets,
|
||||
zone,
|
||||
)
|
||||
|
||||
recordSet, err := p.newRecordSet(endpoint)
|
||||
recordSet, err := p.newRecordSet(ep)
|
||||
if err == nil {
|
||||
_, err = p.recordSetsClient.CreateOrUpdate(
|
||||
ctx,
|
||||
p.resourceGroup,
|
||||
zone,
|
||||
privatedns.RecordType(endpoint.RecordType),
|
||||
privatedns.RecordType(ep.RecordType),
|
||||
name,
|
||||
recordSet,
|
||||
"",
|
||||
@ -316,9 +314,9 @@ func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azu
|
||||
if err != nil {
|
||||
log.Errorf(
|
||||
"Failed to update %s record named '%s' to '%s' for Azure Private DNS zone '%s': %v",
|
||||
endpoint.RecordType,
|
||||
ep.RecordType,
|
||||
name,
|
||||
endpoint.Targets,
|
||||
ep.Targets,
|
||||
zone,
|
||||
err,
|
||||
)
|
||||
|
@ -18,16 +18,11 @@ package azure
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/privatedns"
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/plan"
|
||||
"sigs.k8s.io/external-dns/provider"
|
||||
@ -255,36 +250,6 @@ func newAzurePrivateDNSProvider(domainFilter endpoint.DomainFilter, zoneIDFilter
|
||||
}
|
||||
}
|
||||
|
||||
func validateAzurePrivateDNSClientsResourceManager(t *testing.T, environmentName string, expectedResourceManagerEndpoint string) {
|
||||
err := os.Setenv(auth.EnvironmentName, environmentName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
azurePrivateDNSProvider, err := NewAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), "k8s", "sub", true)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
zonesClientBaseURI := azurePrivateDNSProvider.zonesClient.(privatedns.PrivateZonesClient).BaseURI
|
||||
recordSetsClientBaseURI := azurePrivateDNSProvider.recordSetsClient.(privatedns.RecordSetsClient).BaseURI
|
||||
|
||||
assert.Equal(t, zonesClientBaseURI, expectedResourceManagerEndpoint, "expected and actual resource manager endpoints don't match. expected: %s, got: %s", expectedResourceManagerEndpoint, zonesClientBaseURI)
|
||||
assert.Equal(t, recordSetsClientBaseURI, expectedResourceManagerEndpoint, "expected and actual resource manager endpoints don't match. expected: %s, got: %s", expectedResourceManagerEndpoint, recordSetsClientBaseURI)
|
||||
}
|
||||
|
||||
func TestNewAzurePrivateDNSProvider(t *testing.T) {
|
||||
// make sure to reset the environment variables at the end again
|
||||
originalEnv := os.Getenv(auth.EnvironmentName)
|
||||
defer os.Setenv(auth.EnvironmentName, originalEnv)
|
||||
|
||||
validateAzurePrivateDNSClientsResourceManager(t, "", azure.PublicCloud.ResourceManagerEndpoint)
|
||||
validateAzurePrivateDNSClientsResourceManager(t, "AZURECHINACLOUD", azure.ChinaCloud.ResourceManagerEndpoint)
|
||||
validateAzurePrivateDNSClientsResourceManager(t, "AZUREGERMANCLOUD", azure.GermanCloud.ResourceManagerEndpoint)
|
||||
validateAzurePrivateDNSClientsResourceManager(t, "AZUREUSGOVERNMENTCLOUD", azure.USGovernmentCloud.ResourceManagerEndpoint)
|
||||
}
|
||||
|
||||
func TestAzurePrivateDNSRecord(t *testing.T) {
|
||||
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
|
||||
&[]privatedns.PrivateZone{
|
||||
|
129
provider/azure/config.go
Normal file
129
provider/azure/config.go
Normal file
@ -0,0 +1,129 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// config represents common config items for Azure DNS and Azure Private DNS
|
||||
type config struct {
|
||||
Cloud string `json:"cloud" yaml:"cloud"`
|
||||
Environment azure.Environment `json:"-" yaml:"-"`
|
||||
TenantID string `json:"tenantId" yaml:"tenantId"`
|
||||
SubscriptionID string `json:"subscriptionId" yaml:"subscriptionId"`
|
||||
ResourceGroup string `json:"resourceGroup" yaml:"resourceGroup"`
|
||||
Location string `json:"location" yaml:"location"`
|
||||
ClientID string `json:"aadClientId" yaml:"aadClientId"`
|
||||
ClientSecret string `json:"aadClientSecret" yaml:"aadClientSecret"`
|
||||
UseManagedIdentityExtension bool `json:"useManagedIdentityExtension" yaml:"useManagedIdentityExtension"`
|
||||
UserAssignedIdentityID string `json:"userAssignedIdentityID" yaml:"userAssignedIdentityID"`
|
||||
}
|
||||
|
||||
func getConfig(configFile, resourceGroup, userAssignedIdentityClientID string) (*config, error) {
|
||||
contents, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||
}
|
||||
cfg := &config{}
|
||||
err = yaml.Unmarshal(contents, &cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
||||
}
|
||||
|
||||
// If a resource group was given, override what was present in the config file
|
||||
if resourceGroup != "" {
|
||||
cfg.ResourceGroup = resourceGroup
|
||||
}
|
||||
// If userAssignedIdentityClientID is provided explicitly, override existing one in config file
|
||||
if userAssignedIdentityClientID != "" {
|
||||
cfg.UserAssignedIdentityID = userAssignedIdentityClientID
|
||||
}
|
||||
|
||||
var environment azure.Environment
|
||||
if cfg.Cloud == "" {
|
||||
environment = azure.PublicCloud
|
||||
} else {
|
||||
environment, err = azure.EnvironmentFromName(cfg.Cloud)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid cloud value '%s': %v", cfg.Cloud, err)
|
||||
}
|
||||
}
|
||||
cfg.Environment = environment
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// getAccessToken retrieves Azure API access token.
|
||||
func getAccessToken(cfg config, environment azure.Environment) (*adal.ServicePrincipalToken, error) {
|
||||
// Try to retrieve token with service principal credentials.
|
||||
// Try to use service principal first, some AKS clusters are in an intermediate state that `UseManagedIdentityExtension` is `true`
|
||||
// and service principal exists. In this case, we still want to use service principal to authenticate.
|
||||
if len(cfg.ClientID) > 0 &&
|
||||
len(cfg.ClientSecret) > 0 &&
|
||||
// due to some historical reason, for pure MSI cluster,
|
||||
// they will use "msi" as placeholder in azure.json.
|
||||
// In this case, we shouldn't try to use SPN to authenticate.
|
||||
!strings.EqualFold(cfg.ClientID, "msi") &&
|
||||
!strings.EqualFold(cfg.ClientSecret, "msi") {
|
||||
log.Info("Using client_id+client_secret to retrieve access token for Azure API.")
|
||||
oauthConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, cfg.TenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve OAuth config: %v", err)
|
||||
}
|
||||
|
||||
token, err := adal.NewServicePrincipalToken(*oauthConfig, cfg.ClientID, cfg.ClientSecret, environment.ResourceManagerEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create service principal token: %v", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// Try to retrieve token with MSI.
|
||||
if cfg.UseManagedIdentityExtension {
|
||||
log.Info("Using managed identity extension to retrieve access token for Azure API.")
|
||||
msiEndpoint, err := adal.GetMSIVMEndpoint()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the managed service identity endpoint: %v", err)
|
||||
}
|
||||
|
||||
if cfg.UserAssignedIdentityID != "" {
|
||||
log.Infof("Resolving to user assigned identity, client id is %s.", cfg.UserAssignedIdentityID)
|
||||
token, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, environment.ServiceManagementEndpoint, cfg.UserAssignedIdentityID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the managed service identity token: %v", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
log.Info("Resolving to system assigned identity.")
|
||||
token, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, environment.ServiceManagementEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create the managed service identity token: %v", err)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no credentials provided for Azure API")
|
||||
}
|
67
provider/azure/config_test.go
Normal file
67
provider/azure/config_test.go
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package azure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetAzureEnvironmentConfig(t *testing.T) {
|
||||
tmp, err := ioutil.TempFile("", "azureconf")
|
||||
if err != nil {
|
||||
t.Errorf("couldn't write temp file %v", err)
|
||||
}
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
tests := map[string]struct {
|
||||
cloud string
|
||||
err error
|
||||
}{
|
||||
"AzureChinaCloud": {"AzureChinaCloud", nil},
|
||||
"AzureGermanCloud": {"AzureGermanCloud", nil},
|
||||
"AzurePublicCloud": {"", nil},
|
||||
"AzureUSGovernment": {"AzureUSGovernmentCloud", nil},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, _ = tmp.Seek(0, 0)
|
||||
_, _ = tmp.Write([]byte(fmt.Sprintf(`{"cloud": "%s"}`, test.cloud)))
|
||||
got, err := getConfig(tmp.Name(), "", "")
|
||||
if err != nil {
|
||||
t.Errorf("got unexpected err %v", err)
|
||||
}
|
||||
|
||||
if test.cloud == "" {
|
||||
test.cloud = "AzurePublicCloud"
|
||||
}
|
||||
want, err := azure.EnvironmentFromName(test.cloud)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't get azure environment from provided name %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(want, got.Environment) {
|
||||
t.Errorf("got %v, want %v", got.Environment, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -269,8 +269,13 @@ func endpointToScalewayRecords(zoneName string, ep *endpoint.Endpoint) []*domain
|
||||
records := []*domain.Record{}
|
||||
|
||||
for _, target := range ep.Targets {
|
||||
finalTargetName := target
|
||||
if domain.RecordType(ep.RecordType) == domain.RecordTypeCNAME {
|
||||
finalTargetName = provider.EnsureTrailingDot(target)
|
||||
}
|
||||
|
||||
records = append(records, &domain.Record{
|
||||
Data: target,
|
||||
Data: finalTargetName,
|
||||
Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "),
|
||||
Priority: priority,
|
||||
TTL: ttl,
|
||||
@ -285,9 +290,14 @@ func endpointToScalewayRecordsChangeDelete(zoneName string, ep *endpoint.Endpoin
|
||||
records := []*domain.RecordChange{}
|
||||
|
||||
for _, target := range ep.Targets {
|
||||
finalTargetName := target
|
||||
if domain.RecordType(ep.RecordType) == domain.RecordTypeCNAME {
|
||||
finalTargetName = provider.EnsureTrailingDot(target)
|
||||
}
|
||||
|
||||
records = append(records, &domain.RecordChange{
|
||||
Delete: &domain.RecordChangeDelete{
|
||||
Data: target,
|
||||
Data: finalTargetName,
|
||||
Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "),
|
||||
Type: domain.RecordType(ep.RecordType),
|
||||
},
|
||||
|
@ -93,7 +93,7 @@ func (m *mockScalewayDomain) ListDNSZoneRecords(req *domain.ListDNSZoneRecordsRe
|
||||
Type: domain.RecordTypeA,
|
||||
},
|
||||
{
|
||||
Data: "test.example.com",
|
||||
Data: "test.example.com.",
|
||||
Name: "two",
|
||||
TTL: 600,
|
||||
Priority: 30,
|
||||
@ -330,7 +330,7 @@ func TestScalewayProvider_generateApplyRequests(t *testing.T) {
|
||||
Add: &domain.RecordChangeAdd{
|
||||
Records: []*domain.Record{
|
||||
{
|
||||
Data: "example.com",
|
||||
Data: "example.com.",
|
||||
Name: "",
|
||||
TTL: 600,
|
||||
Type: domain.RecordTypeCNAME,
|
||||
|
283
source/ambassador_host.go
Normal file
283
source/ambassador_host.go
Normal file
@ -0,0 +1,283 @@
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ambassador "github.com/datawire/ambassador/pkg/api/getambassador.io/v2"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
)
|
||||
|
||||
// ambHostAnnotation is the annotation in the Host that maps to a Service
|
||||
const ambHostAnnotation = "external-dns.ambassador-service"
|
||||
|
||||
// groupName is the group name for the Ambassador API
|
||||
const groupName = "getambassador.io"
|
||||
|
||||
var schemeGroupVersion = schema.GroupVersion{Group: groupName, Version: "v2"}
|
||||
|
||||
var ambHostGVR = schemeGroupVersion.WithResource("hosts")
|
||||
|
||||
// ambassadorHostSource is an implementation of Source for Ambassador Host objects.
|
||||
// The IngressRoute implementation uses the spec.virtualHost.fqdn value for the hostname.
|
||||
// Use targetAnnotationKey to explicitly set Endpoint.
|
||||
type ambassadorHostSource struct {
|
||||
dynamicKubeClient dynamic.Interface
|
||||
kubeClient kubernetes.Interface
|
||||
namespace string
|
||||
ambassadorHostInformer informers.GenericInformer
|
||||
unstructuredConverter *unstructuredConverter
|
||||
}
|
||||
|
||||
// NewAmbassadorHostSource creates a new ambassadorHostSource with the given config.
|
||||
func NewAmbassadorHostSource(
|
||||
dynamicKubeClient dynamic.Interface,
|
||||
kubeClient kubernetes.Interface,
|
||||
namespace string) (Source, error) {
|
||||
var err error
|
||||
|
||||
// Use shared informer to listen for add/update/delete of Host in the specified namespace.
|
||||
// Set resync period to 0, to prevent processing when nothing has changed.
|
||||
informerFactory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynamicKubeClient, 0, namespace, nil)
|
||||
ambassadorHostInformer := informerFactory.ForResource(ambHostGVR)
|
||||
|
||||
// Add default resource event handlers to properly initialize informer.
|
||||
ambassadorHostInformer.Informer().AddEventHandler(
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
// TODO informer is not explicitly stopped since controller is not passing in its channel.
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
// wait for the local cache to be populated.
|
||||
err = poll(time.Second, 60*time.Second, func() (bool, error) {
|
||||
return ambassadorHostInformer.Informer().HasSynced(), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to sync cache")
|
||||
}
|
||||
|
||||
uc, err := newUnstructuredConverter()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to setup Unstructured Converter")
|
||||
}
|
||||
|
||||
return &ambassadorHostSource{
|
||||
dynamicKubeClient: dynamicKubeClient,
|
||||
kubeClient: kubeClient,
|
||||
namespace: namespace,
|
||||
ambassadorHostInformer: ambassadorHostInformer,
|
||||
unstructuredConverter: uc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Endpoints returns endpoint objects for each host-target combination that should be processed.
|
||||
// Retrieves all Hosts in the source's namespace(s).
|
||||
func (sc *ambassadorHostSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||
hosts, err := sc.ambassadorHostInformer.Lister().ByNamespace(sc.namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoints := []*endpoint.Endpoint{}
|
||||
for _, hostObj := range hosts {
|
||||
unstructuredHost, ok := hostObj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return nil, errors.New("could not convert")
|
||||
}
|
||||
|
||||
host := &ambassador.Host{}
|
||||
err := sc.unstructuredConverter.scheme.Convert(unstructuredHost, host, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fullname := fmt.Sprintf("%s/%s", host.Namespace, host.Name)
|
||||
|
||||
// look for the "exernal-dns.ambassador-service" annotation. If it is not there then just ignore this `Host`
|
||||
service, found := host.Annotations[ambHostAnnotation]
|
||||
if !found {
|
||||
log.Debugf("Host %s ignored: no annotation %q found", fullname, ambHostAnnotation)
|
||||
continue
|
||||
}
|
||||
|
||||
targets, err := sc.targetsFromAmbassadorLoadBalancer(ctx, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hostEndpoints, err := sc.endpointsFromHost(ctx, host, targets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(hostEndpoints) == 0 {
|
||||
log.Debugf("No endpoints could be generated from Host %s", fullname)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Endpoints generated from Host: %s: %v", fullname, hostEndpoints)
|
||||
endpoints = append(endpoints, hostEndpoints...)
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
sort.Sort(ep.Targets)
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
// endpointsFromHost extracts the endpoints from a Host object
|
||||
func (sc *ambassadorHostSource) endpointsFromHost(ctx context.Context, host *ambassador.Host, targets endpoint.Targets) ([]*endpoint.Endpoint, error) {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
providerSpecific := endpoint.ProviderSpecific{}
|
||||
setIdentifier := ""
|
||||
|
||||
annotations := host.Annotations
|
||||
ttl, err := getTTLFromAnnotations(annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if host.Spec != nil {
|
||||
hostname := host.Spec.Hostname
|
||||
if hostname != "" {
|
||||
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...)
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (sc *ambassadorHostSource) targetsFromAmbassadorLoadBalancer(ctx context.Context, service string) (targets endpoint.Targets, err error) {
|
||||
lbNamespace, lbName, err := parseAmbLoadBalancerService(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc, err := sc.kubeClient.CoreV1().Services(lbNamespace).Get(ctx, lbName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, lb := range svc.Status.LoadBalancer.Ingress {
|
||||
if lb.IP != "" {
|
||||
targets = append(targets, lb.IP)
|
||||
}
|
||||
if lb.Hostname != "" {
|
||||
targets = append(targets, lb.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseAmbLoadBalancerService returns a name/namespace tuple from the annotation in
|
||||
// an Ambassador Host CRD
|
||||
//
|
||||
// This is a thing because Ambassador has historically supported cross-namespace
|
||||
// references using a name.namespace syntax, but here we want to also support
|
||||
// namespace/name.
|
||||
//
|
||||
// Returns namespace, name, error.
|
||||
|
||||
func parseAmbLoadBalancerService(service string) (namespace, name string, err error) {
|
||||
// Start by assuming that we have namespace/name.
|
||||
parts := strings.Split(service, "/")
|
||||
|
||||
if len(parts) == 1 {
|
||||
// No "/" at all, so let's try for name.namespace. To be consistent with the
|
||||
// rest of Ambassador, use SplitN to limit this to one split, so that e.g.
|
||||
// svc.foo.bar uses service "svc" in namespace "foo.bar".
|
||||
parts = strings.SplitN(service, ".", 2)
|
||||
|
||||
if len(parts) == 2 {
|
||||
// We got a namespace, great.
|
||||
name := parts[0]
|
||||
namespace := parts[1]
|
||||
|
||||
return namespace, name, nil
|
||||
}
|
||||
|
||||
// If here, we have no separator, so the whole string is the service, and
|
||||
// we can assume the default namespace.
|
||||
name := service
|
||||
namespace := api.NamespaceDefault
|
||||
|
||||
return namespace, name, nil
|
||||
} else if len(parts) == 2 {
|
||||
// This is "namespace/name". Note that the name could be qualified,
|
||||
// which is fine.
|
||||
namespace := parts[0]
|
||||
name := parts[1]
|
||||
|
||||
return namespace, name, nil
|
||||
}
|
||||
|
||||
// If we got here, this string is simply ill-formatted. Return an error.
|
||||
return "", "", errors.New(fmt.Sprintf("invalid external-dns service: %s", service))
|
||||
}
|
||||
|
||||
func (sc *ambassadorHostSource) AddEventHandler(ctx context.Context, handler func()) {
|
||||
}
|
||||
|
||||
// unstructuredConverter handles conversions between unstructured.Unstructured and Ambassador types
|
||||
type unstructuredConverter struct {
|
||||
// scheme holds an initializer for converting Unstructured to a type
|
||||
scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
// newUnstructuredConverter returns a new unstructuredConverter initialized
|
||||
func newUnstructuredConverter() (*unstructuredConverter, error) {
|
||||
uc := &unstructuredConverter{
|
||||
scheme: runtime.NewScheme(),
|
||||
}
|
||||
|
||||
// Setup converter to understand custom CRD types
|
||||
ambassador.AddToScheme(uc.scheme)
|
||||
|
||||
// Add the core types we need
|
||||
if err := scheme.AddToScheme(uc.scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return uc, nil
|
||||
}
|
78
source/ambassador_host_test.go
Normal file
78
source/ambassador_host_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2019 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package source
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type AmbassadorSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func TestAmbassadorSource(t *testing.T) {
|
||||
suite.Run(t, new(AmbassadorSuite))
|
||||
t.Run("Interface", testAmbassadorSourceImplementsSource)
|
||||
}
|
||||
|
||||
// testAmbassadorSourceImplementsSource tests that ambassadorHostSource is a valid Source.
|
||||
func testAmbassadorSourceImplementsSource(t *testing.T) {
|
||||
require.Implements(t, (*Source)(nil), new(ambassadorHostSource))
|
||||
}
|
||||
|
||||
// TestParseAmbLoadBalancerService tests our parsing of Ambassador service info.
|
||||
func TestParseAmbLoadBalancerService(t *testing.T) {
|
||||
vectors := []struct {
|
||||
input string
|
||||
ns string
|
||||
svc string
|
||||
errstr string
|
||||
}{
|
||||
{"svc", "default", "svc", ""},
|
||||
{"ns/svc", "ns", "svc", ""},
|
||||
{"svc.ns", "ns", "svc", ""},
|
||||
{"svc.ns.foo.bar", "ns.foo.bar", "svc", ""},
|
||||
{"ns/svc/foo/bar", "", "", "invalid external-dns service: ns/svc/foo/bar"},
|
||||
{"ns/svc/foo.bar", "", "", "invalid external-dns service: ns/svc/foo.bar"},
|
||||
{"ns.foo/svc/bar", "", "", "invalid external-dns service: ns.foo/svc/bar"},
|
||||
}
|
||||
|
||||
for _, v := range vectors {
|
||||
ns, svc, err := parseAmbLoadBalancerService(v.input)
|
||||
|
||||
errstr := ""
|
||||
|
||||
if err != nil {
|
||||
errstr = err.Error()
|
||||
}
|
||||
|
||||
if v.ns != ns {
|
||||
t.Errorf("%s: got ns \"%s\", wanted \"%s\"", v.input, ns, v.ns)
|
||||
}
|
||||
|
||||
if v.svc != svc {
|
||||
t.Errorf("%s: got svc \"%s\", wanted \"%s\"", v.input, svc, v.svc)
|
||||
}
|
||||
|
||||
if v.errstr != errstr {
|
||||
t.Errorf("%s: got err \"%s\", wanted \"%s\"", v.input, errstr, v.errstr)
|
||||
}
|
||||
}
|
||||
}
|
@ -628,14 +628,17 @@ func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, nodeTargets e
|
||||
|
||||
for _, port := range svc.Spec.Ports {
|
||||
if port.NodePort > 0 {
|
||||
// following the RFC 2782, SRV record must have a following format
|
||||
// _service._proto.name. TTL class SRV priority weight port
|
||||
// see https://en.wikipedia.org/wiki/SRV_record
|
||||
|
||||
// build a target with a priority of 0, weight of 0, and pointing the given port on the given host
|
||||
target := fmt.Sprintf("0 50 %d %s", port.NodePort, hostname)
|
||||
|
||||
// figure out the portname
|
||||
portName := port.Name
|
||||
if portName == "" {
|
||||
portName = fmt.Sprintf("%d", port.NodePort)
|
||||
}
|
||||
// take the service name from the K8s Service object
|
||||
// it is safe to use since it is DNS compatible
|
||||
// see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
|
||||
serviceName := svc.ObjectMeta.Name
|
||||
|
||||
// figure out the protocol
|
||||
protocol := strings.ToLower(string(port.Protocol))
|
||||
@ -643,7 +646,7 @@ func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, nodeTargets e
|
||||
protocol = "tcp"
|
||||
}
|
||||
|
||||
recordName := fmt.Sprintf("_%s._%s.%s", portName, protocol, hostname)
|
||||
recordName := fmt.Sprintf("_%s._%s.%s", serviceName, protocol, hostname)
|
||||
|
||||
var ep *endpoint.Endpoint
|
||||
if ttl.IsConfigured() {
|
||||
|
@ -1642,7 +1642,7 @@ func TestNodePortServices(t *testing.T) {
|
||||
},
|
||||
nil,
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
false,
|
||||
@ -1729,7 +1729,7 @@ func TestNodePortServices(t *testing.T) {
|
||||
map[string]string{},
|
||||
nil,
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "_30192._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
false,
|
||||
@ -1775,7 +1775,7 @@ func TestNodePortServices(t *testing.T) {
|
||||
},
|
||||
nil,
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
false,
|
||||
@ -1819,7 +1819,7 @@ func TestNodePortServices(t *testing.T) {
|
||||
},
|
||||
nil,
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
false,
|
||||
@ -1865,7 +1865,7 @@ func TestNodePortServices(t *testing.T) {
|
||||
},
|
||||
nil,
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
false,
|
||||
@ -1912,7 +1912,7 @@ func TestNodePortServices(t *testing.T) {
|
||||
},
|
||||
nil,
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
false,
|
||||
@ -1959,7 +1959,7 @@ func TestNodePortServices(t *testing.T) {
|
||||
},
|
||||
nil,
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "_30192._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
false,
|
||||
|
@ -83,12 +83,12 @@ type SingletonClientGenerator struct {
|
||||
kubeClient kubernetes.Interface
|
||||
istioClient *istioclient.Clientset
|
||||
cfClient *cfclient.Client
|
||||
contourClient dynamic.Interface
|
||||
dynKubeClient dynamic.Interface
|
||||
openshiftClient openshift.Interface
|
||||
kubeOnce sync.Once
|
||||
istioOnce sync.Once
|
||||
cfOnce sync.Once
|
||||
contourOnce sync.Once
|
||||
dynCliOnce sync.Once
|
||||
openshiftOnce sync.Once
|
||||
}
|
||||
|
||||
@ -134,13 +134,13 @@ func NewCFClient(cfAPIEndpoint string, cfUsername string, cfPassword string) (*c
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// DynamicKubernetesClient generates a contour client if it was not created before
|
||||
// DynamicKubernetesClient generates a dynamic client if it was not created before
|
||||
func (p *SingletonClientGenerator) DynamicKubernetesClient() (dynamic.Interface, error) {
|
||||
var err error
|
||||
p.contourOnce.Do(func() {
|
||||
p.contourClient, err = NewDynamicKubernetesClient(p.KubeConfig, p.APIServerURL, p.RequestTimeout)
|
||||
p.dynCliOnce.Do(func() {
|
||||
p.dynKubeClient, err = NewDynamicKubernetesClient(p.KubeConfig, p.APIServerURL, p.RequestTimeout)
|
||||
})
|
||||
return p.contourClient, err
|
||||
return p.dynKubeClient, err
|
||||
}
|
||||
|
||||
// OpenShiftClient generates an openshift client if it was not created before
|
||||
@ -213,6 +213,16 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
|
||||
return nil, err
|
||||
}
|
||||
return NewCloudFoundrySource(cfClient)
|
||||
case "ambassador-host":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dynamicClient, err := p.DynamicKubernetesClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewAmbassadorHostSource(dynamicClient, kubernetesClient, cfg.Namespace)
|
||||
case "contour-ingressroute":
|
||||
kubernetesClient, err := p.KubeClient()
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user