mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 17:46:57 +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
|
- Update contributing section in README (#1760) @seanmalloy
|
||||||
- Option to cache AWS zones list @bpineau
|
- Option to cache AWS zones list @bpineau
|
||||||
- Refactor, enhance and test Akamai provider and documentation (#1846) @edglynes
|
- 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
|
## v0.7.3 - 2020-08-05
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# Set up ExternalDNS for Azure Private DNS
|
# Set up ExternalDNS for Azure Private DNS
|
||||||
|
|
||||||
This tutorial describes how to set up ExternalDNS for managing records in Azure Private DNS.
|
This tutorial describes how to set up ExternalDNS for managing records in Azure Private DNS.
|
||||||
|
|
||||||
It comprises of the following steps:
|
It comprises of the following steps:
|
||||||
1) Install NGINX Ingress Controller
|
1) Install NGINX Ingress Controller
|
||||||
2) Provision Azure Private DNS
|
2) Provision Azure Private DNS
|
||||||
3) Configure service principal for managing the zone
|
3) Configure service principal for managing the zone
|
||||||
4) Deploy ExternalDNS
|
4) Deploy ExternalDNS
|
||||||
|
|
||||||
Everything will be deployed on Kubernetes.
|
Everything will be deployed on Kubernetes.
|
||||||
Therefore, please see the subsequent prerequisites.
|
Therefore, please see the subsequent prerequisites.
|
||||||
@ -26,25 +26,27 @@ $ helm install stable/nginx-ingress \
|
|||||||
--name nginx-ingress \
|
--name nginx-ingress \
|
||||||
--set controller.publishService.enabled=true
|
--set controller.publishService.enabled=true
|
||||||
```
|
```
|
||||||
|
|
||||||
The parameter `controller.publishService.enabled` needs to be set to `true.`
|
The parameter `controller.publishService.enabled` needs to be set to `true.`
|
||||||
|
|
||||||
It will make the ingress controller update the endpoint records of ingress-resources to contain the external-ip of the loadbalancer serving the ingress-controller.
|
It will make the ingress controller update the endpoint records of ingress-resources to contain the external-ip of the loadbalancer serving the ingress-controller.
|
||||||
This is crucial as ExternalDNS reads those endpoints records when creating DNS-Records from ingress-resources.
|
This is crucial as ExternalDNS reads those endpoints records when creating DNS-Records from ingress-resources.
|
||||||
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.
|
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.
|
Verify the correct propagation of the loadbalancer's ip by listing the ingresses.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ kubectl get ingress
|
$ kubectl get ingress
|
||||||
```
|
```
|
||||||
|
|
||||||
The address column should contain the ip for each ingress. ExternalDNS will pick up exactly this piece of information.
|
The address column should contain the ip for each ingress. ExternalDNS will pick up exactly this piece of information.
|
||||||
|
|
||||||
```
|
```
|
||||||
NAME HOSTS ADDRESS PORTS AGE
|
NAME HOSTS ADDRESS PORTS AGE
|
||||||
nginx1 sample1.aks.com 52.167.195.110 80 6d22h
|
nginx1 sample1.aks.com 52.167.195.110 80 6d22h
|
||||||
nginx2 sample2.aks.com 52.167.195.110 80 6d21h
|
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:
|
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/).
|
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).
|
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.
|
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.
|
The credentials of the service principal are provided to ExternalDNS as environment-variables.
|
||||||
@ -175,13 +179,14 @@ spec:
|
|||||||
- --provider=azure-private-dns
|
- --provider=azure-private-dns
|
||||||
- --azure-resource-group=externaldns
|
- --azure-resource-group=externaldns
|
||||||
- --azure-subscription-id=<use the id of your subscription>
|
- --azure-subscription-id=<use the id of your subscription>
|
||||||
env:
|
volumeMounts:
|
||||||
- name: AZURE_TENANT_ID
|
- name: azure-config-file
|
||||||
value: "<use the tenantId discovered during creation of service principal>"
|
mountPath: /etc/kubernetes
|
||||||
- name: AZURE_CLIENT_ID
|
readOnly: true
|
||||||
value: "<use the aadClientId discovered during creation of service principal>"
|
volumes:
|
||||||
- name: AZURE_CLIENT_SECRET
|
- name: azure-config-file
|
||||||
value: "<use the aadClientSecret discovered during creation of service principal>"
|
secret:
|
||||||
|
secretName: azure-config-file
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manifest (for clusters with RBAC enabled, cluster access)
|
### Manifest (for clusters with RBAC enabled, cluster access)
|
||||||
@ -200,7 +205,7 @@ rules:
|
|||||||
resources: ["services","endpoints","pods"]
|
resources: ["services","endpoints","pods"]
|
||||||
verbs: ["get","watch","list"]
|
verbs: ["get","watch","list"]
|
||||||
- apiGroups: ["extensions","networking.k8s.io"]
|
- apiGroups: ["extensions","networking.k8s.io"]
|
||||||
resources: ["ingresses"]
|
resources: ["ingresses"]
|
||||||
verbs: ["get","watch","list"]
|
verbs: ["get","watch","list"]
|
||||||
- apiGroups: [""]
|
- apiGroups: [""]
|
||||||
resources: ["nodes"]
|
resources: ["nodes"]
|
||||||
@ -245,13 +250,14 @@ spec:
|
|||||||
- --provider=azure-private-dns
|
- --provider=azure-private-dns
|
||||||
- --azure-resource-group=externaldns
|
- --azure-resource-group=externaldns
|
||||||
- --azure-subscription-id=<use the id of your subscription>
|
- --azure-subscription-id=<use the id of your subscription>
|
||||||
env:
|
volumeMounts:
|
||||||
- name: AZURE_TENANT_ID
|
- name: azure-config-file
|
||||||
value: "<use the tenantId discovered during creation of service principal>"
|
mountPath: /etc/kubernetes
|
||||||
- name: AZURE_CLIENT_ID
|
readOnly: true
|
||||||
value: "<use the aadClientId discovered during creation of service principal>"
|
volumes:
|
||||||
- name: AZURE_CLIENT_SECRET
|
- name: azure-config-file
|
||||||
value: "<use the aadClientSecret discovered during creation of service principal>"
|
secret:
|
||||||
|
secretName: azure-config-file
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manifest (for clusters with RBAC enabled, namespace access)
|
### Manifest (for clusters with RBAC enabled, namespace access)
|
||||||
@ -315,13 +321,14 @@ spec:
|
|||||||
- --provider=azure-private-dns
|
- --provider=azure-private-dns
|
||||||
- --azure-resource-group=externaldns
|
- --azure-resource-group=externaldns
|
||||||
- --azure-subscription-id=<use the id of your subscription>
|
- --azure-subscription-id=<use the id of your subscription>
|
||||||
env:
|
volumeMounts:
|
||||||
- name: AZURE_TENANT_ID
|
- name: azure-config-file
|
||||||
value: "<use the tenantId discovered during creation of service principal>"
|
mountPath: /etc/kubernetes
|
||||||
- name: AZURE_CLIENT_ID
|
readOnly: true
|
||||||
value: "<use the aadClientId discovered during creation of service principal>"
|
volumes:
|
||||||
- name: AZURE_CLIENT_SECRET
|
- name: azure-config-file
|
||||||
value: "<use the aadClientSecret discovered during creation of service principal>"
|
secret:
|
||||||
|
secretName: azure-config-file
|
||||||
```
|
```
|
||||||
|
|
||||||
Create the deployment for ExternalDNS:
|
Create the deployment for ExternalDNS:
|
||||||
|
@ -78,7 +78,7 @@ rules:
|
|||||||
See also current RBAC yaml files:
|
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)
|
- [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)
|
- [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
|
[3]: https://opensource.zalando.com/skipper/kubernetes/routegroups/#routegroups
|
||||||
[4]: https://opensource.zalando.com/skipper
|
[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/azure-sdk-for-go v45.1.0+incompatible
|
||||||
github.com/Azure/go-autorest/autorest v0.11.10
|
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/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/Azure/go-autorest/autorest/to v0.4.0
|
||||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0
|
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.0.0
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
|
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/aws/aws-sdk-go v1.31.4
|
||||||
github.com/cloudflare/cloudflare-go v0.10.1
|
github.com/cloudflare/cloudflare-go v0.10.1
|
||||||
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
|
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/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba
|
||||||
github.com/digitalocean/godo v1.36.0
|
github.com/digitalocean/godo v1.36.0
|
||||||
github.com/dnsimple/dnsimple-go v0.60.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/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0
|
||||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301
|
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.6.0.20200623155123-84df6c4b5301
|
||||||
github.com/sirupsen/logrus v1.6.0
|
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/terra-farm/udnssdk v1.3.5 // indirect
|
||||||
github.com/transip/gotransip v5.8.2+incompatible
|
github.com/transip/gotransip v5.8.2+incompatible
|
||||||
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
|
||||||
@ -54,17 +55,20 @@ require (
|
|||||||
github.com/vultr/govultr v0.4.2
|
github.com/vultr/govultr v0.4.2
|
||||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875
|
go.etcd.io/etcd v0.5.0-alpha.5.0.20200401174654-e694b7bb0875
|
||||||
go.uber.org/ratelimit v0.1.0
|
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/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
|
google.golang.org/api v0.15.0
|
||||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1
|
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/api v0.0.0-20200529165953-72dad51d4ffc
|
||||||
istio.io/client-go v0.0.0-20200529172309-31c16ea3f751
|
istio.io/client-go v0.0.0-20200529172309-31c16ea3f751
|
||||||
k8s.io/api v0.18.8
|
k8s.io/api v0.18.8
|
||||||
k8s.io/apimachinery v0.18.8
|
k8s.io/apimachinery v0.18.8
|
||||||
k8s.io/client-go v0.18.8
|
k8s.io/client-go v0.18.8
|
||||||
|
k8s.io/kubernetes v1.13.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
|
2
main.go
2
main.go
@ -193,7 +193,7 @@ func main() {
|
|||||||
case "azure-dns", "azure":
|
case "azure-dns", "azure":
|
||||||
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
|
||||||
case "azure-private-dns":
|
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":
|
case "vinyldns":
|
||||||
p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
|
||||||
case "vultr":
|
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)
|
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)
|
||||||
|
|
||||||
// Flags related to processing sources
|
// 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("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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
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/azure-sdk-for-go/services/dns/mgmt/2018-05-01/dns"
|
||||||
"github.com/Azure/go-autorest/autorest"
|
"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"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
@ -41,18 +36,6 @@ const (
|
|||||||
azureRecordTTL = 300
|
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.
|
// ZonesClient is an interface of dns.ZoneClient that can be stubbed for testing.
|
||||||
type ZonesClient interface {
|
type ZonesClient interface {
|
||||||
ListByResourceGroupComplete(ctx context.Context, resourceGroupName string, top *int32) (result dns.ZoneListResultIterator, err error)
|
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.
|
// 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) {
|
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)
|
cfg, err := getConfig(configFile, resourceGroup, userAssignedIdentityClientID)
|
||||||
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read Azure config file '%s': %v", configFile, err)
|
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
|
token, err := getAccessToken(*cfg, cfg.Environment)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get token: %v", err)
|
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)
|
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)
|
recordSetsClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||||
|
|
||||||
provider := &AzureProvider{
|
return &AzureProvider{
|
||||||
domainFilter: domainFilter,
|
domainFilter: domainFilter,
|
||||||
zoneNameFilter: zoneNameFilter,
|
zoneNameFilter: zoneNameFilter,
|
||||||
zoneIDFilter: zoneIDFilter,
|
zoneIDFilter: zoneIDFilter,
|
||||||
@ -130,61 +89,7 @@ func NewAzureProvider(configFile string, domainFilter endpoint.DomainFilter, zon
|
|||||||
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
||||||
zonesClient: zonesClient,
|
zonesClient: zonesClient,
|
||||||
recordSetsClient: recordSetsClient,
|
recordSetsClient: recordSetsClient,
|
||||||
}
|
}, nil
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Records gets the current records.
|
// 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) {
|
func (p *AzureProvider) deleteRecords(ctx context.Context, deleted azureChangeMap) {
|
||||||
// Delete records first
|
// Delete records first
|
||||||
for zone, endpoints := range deleted {
|
for zone, endpoints := range deleted {
|
||||||
for _, endpoint := range endpoints {
|
for _, ep := range endpoints {
|
||||||
name := p.recordSetNameForZone(zone, endpoint)
|
name := p.recordSetNameForZone(zone, ep)
|
||||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
if !p.domainFilter.Match(ep.DNSName) {
|
||||||
log.Debugf("Skipping deletion of record %s because it was filtered out by the specified --domain-filter", endpoint.DNSName)
|
log.Debugf("Skipping deletion of record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if p.dryRun {
|
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 {
|
} else {
|
||||||
log.Infof("Deleting %s record named '%s' for Azure DNS zone '%s'.", endpoint.RecordType, name, zone)
|
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(endpoint.RecordType), ""); err != nil {
|
if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, name, dns.RecordType(ep.RecordType), ""); err != nil {
|
||||||
log.Errorf(
|
log.Errorf(
|
||||||
"Failed to delete %s record named '%s' for Azure DNS zone '%s': %v",
|
"Failed to delete %s record named '%s' for Azure DNS zone '%s': %v",
|
||||||
endpoint.RecordType,
|
ep.RecordType,
|
||||||
name,
|
name,
|
||||||
zone,
|
zone,
|
||||||
err,
|
err,
|
||||||
@ -378,18 +283,18 @@ func (p *AzureProvider) deleteRecords(ctx context.Context, deleted azureChangeMa
|
|||||||
|
|
||||||
func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMap) {
|
func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMap) {
|
||||||
for zone, endpoints := range updated {
|
for zone, endpoints := range updated {
|
||||||
for _, endpoint := range endpoints {
|
for _, ep := range endpoints {
|
||||||
name := p.recordSetNameForZone(zone, endpoint)
|
name := p.recordSetNameForZone(zone, ep)
|
||||||
if !p.domainFilter.Match(endpoint.DNSName) {
|
if !p.domainFilter.Match(ep.DNSName) {
|
||||||
log.Debugf("Skipping update of record %s because it was filtered out by the specified --domain-filter", endpoint.DNSName)
|
log.Debugf("Skipping update of record %s because it was filtered out by the specified --domain-filter", ep.DNSName)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if p.dryRun {
|
if p.dryRun {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
"Would update %s record named '%s' to '%s' for Azure DNS zone '%s'.",
|
"Would update %s record named '%s' to '%s' for Azure DNS zone '%s'.",
|
||||||
endpoint.RecordType,
|
ep.RecordType,
|
||||||
name,
|
name,
|
||||||
endpoint.Targets,
|
ep.Targets,
|
||||||
zone,
|
zone,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
@ -397,20 +302,20 @@ func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMa
|
|||||||
|
|
||||||
log.Infof(
|
log.Infof(
|
||||||
"Updating %s record named '%s' to '%s' for Azure DNS zone '%s'.",
|
"Updating %s record named '%s' to '%s' for Azure DNS zone '%s'.",
|
||||||
endpoint.RecordType,
|
ep.RecordType,
|
||||||
name,
|
name,
|
||||||
endpoint.Targets,
|
ep.Targets,
|
||||||
zone,
|
zone,
|
||||||
)
|
)
|
||||||
|
|
||||||
recordSet, err := p.newRecordSet(endpoint)
|
recordSet, err := p.newRecordSet(ep)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, err = p.recordSetsClient.CreateOrUpdate(
|
_, err = p.recordSetsClient.CreateOrUpdate(
|
||||||
ctx,
|
ctx,
|
||||||
p.resourceGroup,
|
p.resourceGroup,
|
||||||
zone,
|
zone,
|
||||||
name,
|
name,
|
||||||
dns.RecordType(endpoint.RecordType),
|
dns.RecordType(ep.RecordType),
|
||||||
recordSet,
|
recordSet,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
@ -419,9 +324,9 @@ func (p *AzureProvider) updateRecords(ctx context.Context, updated azureChangeMa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(
|
log.Errorf(
|
||||||
"Failed to update %s record named '%s' to '%s' for DNS zone '%s': %v",
|
"Failed to update %s record named '%s' to '%s' for DNS zone '%s': %v",
|
||||||
endpoint.RecordType,
|
ep.RecordType,
|
||||||
name,
|
name,
|
||||||
endpoint.Targets,
|
ep.Targets,
|
||||||
zone,
|
zone,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
@ -21,9 +21,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"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"
|
||||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
@ -47,44 +46,43 @@ type PrivateRecordSetsClient interface {
|
|||||||
// AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service
|
// AzurePrivateDNSProvider implements the DNS provider for Microsoft's Azure Private DNS service
|
||||||
type AzurePrivateDNSProvider struct {
|
type AzurePrivateDNSProvider struct {
|
||||||
provider.BaseProvider
|
provider.BaseProvider
|
||||||
domainFilter endpoint.DomainFilter
|
domainFilter endpoint.DomainFilter
|
||||||
zoneIDFilter provider.ZoneIDFilter
|
zoneIDFilter provider.ZoneIDFilter
|
||||||
dryRun bool
|
dryRun bool
|
||||||
subscriptionID string
|
resourceGroup string
|
||||||
resourceGroup string
|
userAssignedIdentityClientID string
|
||||||
zonesClient PrivateZonesClient
|
zonesClient PrivateZonesClient
|
||||||
recordSetsClient PrivateRecordSetsClient
|
recordSetsClient PrivateRecordSetsClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
// NewAzurePrivateDNSProvider creates a new Azure Private DNS provider.
|
||||||
//
|
//
|
||||||
// Returns the provider or an error if a provider could not be created.
|
// 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) {
|
func NewAzurePrivateDNSProvider(configFile string, domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, resourceGroup, userAssignedIdentityClientID string, dryRun bool) (*AzurePrivateDNSProvider, error) {
|
||||||
authorizer, err := auth.NewAuthorizerFromEnvironment()
|
cfg, err := getConfig(configFile, resourceGroup, userAssignedIdentityClientID)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to get token: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
zonesClient := privatedns.NewPrivateZonesClientWithBaseURI(settings.Environment.ResourceManagerEndpoint, subscriptionID)
|
zonesClient := privatedns.NewPrivateZonesClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||||
zonesClient.Authorizer = authorizer
|
zonesClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||||
recordSetsClient := privatedns.NewRecordSetsClientWithBaseURI(settings.Environment.ResourceManagerEndpoint, subscriptionID)
|
recordSetsClient := privatedns.NewRecordSetsClientWithBaseURI(cfg.Environment.ResourceManagerEndpoint, cfg.SubscriptionID)
|
||||||
recordSetsClient.Authorizer = authorizer
|
recordSetsClient.Authorizer = autorest.NewBearerAuthorizer(token)
|
||||||
|
|
||||||
provider := &AzurePrivateDNSProvider{
|
return &AzurePrivateDNSProvider{
|
||||||
domainFilter: domainFilter,
|
domainFilter: domainFilter,
|
||||||
zoneIDFilter: zoneIDFilter,
|
zoneIDFilter: zoneIDFilter,
|
||||||
dryRun: dryRun,
|
dryRun: dryRun,
|
||||||
subscriptionID: subscriptionID,
|
resourceGroup: cfg.ResourceGroup,
|
||||||
resourceGroup: resourceGroup,
|
userAssignedIdentityClientID: cfg.UserAssignedIdentityID,
|
||||||
zonesClient: zonesClient,
|
zonesClient: zonesClient,
|
||||||
recordSetsClient: recordSetsClient,
|
recordSetsClient: recordSetsClient,
|
||||||
}
|
}, nil
|
||||||
return provider, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Records gets the current records.
|
// 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))
|
log.Debugf("Records to be deleted: %d", len(deleted))
|
||||||
// Delete records first
|
// Delete records first
|
||||||
for zone, endpoints := range deleted {
|
for zone, endpoints := range deleted {
|
||||||
for _, endpoint := range endpoints {
|
for _, ep := range endpoints {
|
||||||
name := p.recordSetNameForZone(zone, endpoint)
|
name := p.recordSetNameForZone(zone, ep)
|
||||||
if p.dryRun {
|
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 {
|
} else {
|
||||||
log.Infof("Deleting %s record named '%s' for Azure Private DNS zone '%s'.", endpoint.RecordType, name, zone)
|
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(endpoint.RecordType), name, ""); err != nil {
|
if _, err := p.recordSetsClient.Delete(ctx, p.resourceGroup, zone, privatedns.RecordType(ep.RecordType), name, ""); err != nil {
|
||||||
log.Errorf(
|
log.Errorf(
|
||||||
"Failed to delete %s record named '%s' for Azure Private DNS zone '%s': %v",
|
"Failed to delete %s record named '%s' for Azure Private DNS zone '%s': %v",
|
||||||
endpoint.RecordType,
|
ep.RecordType,
|
||||||
name,
|
name,
|
||||||
zone,
|
zone,
|
||||||
err,
|
err,
|
||||||
@ -279,14 +277,14 @@ func (p *AzurePrivateDNSProvider) deleteRecords(ctx context.Context, deleted azu
|
|||||||
func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azurePrivateDNSChangeMap) {
|
func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azurePrivateDNSChangeMap) {
|
||||||
log.Debugf("Records to be updated: %d", len(updated))
|
log.Debugf("Records to be updated: %d", len(updated))
|
||||||
for zone, endpoints := range updated {
|
for zone, endpoints := range updated {
|
||||||
for _, endpoint := range endpoints {
|
for _, ep := range endpoints {
|
||||||
name := p.recordSetNameForZone(zone, endpoint)
|
name := p.recordSetNameForZone(zone, ep)
|
||||||
if p.dryRun {
|
if p.dryRun {
|
||||||
log.Infof(
|
log.Infof(
|
||||||
"Would update %s record named '%s' to '%s' for Azure Private DNS zone '%s'.",
|
"Would update %s record named '%s' to '%s' for Azure Private DNS zone '%s'.",
|
||||||
endpoint.RecordType,
|
ep.RecordType,
|
||||||
name,
|
name,
|
||||||
endpoint.Targets,
|
ep.Targets,
|
||||||
zone,
|
zone,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
@ -294,19 +292,19 @@ func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azu
|
|||||||
|
|
||||||
log.Infof(
|
log.Infof(
|
||||||
"Updating %s record named '%s' to '%s' for Azure Private DNS zone '%s'.",
|
"Updating %s record named '%s' to '%s' for Azure Private DNS zone '%s'.",
|
||||||
endpoint.RecordType,
|
ep.RecordType,
|
||||||
name,
|
name,
|
||||||
endpoint.Targets,
|
ep.Targets,
|
||||||
zone,
|
zone,
|
||||||
)
|
)
|
||||||
|
|
||||||
recordSet, err := p.newRecordSet(endpoint)
|
recordSet, err := p.newRecordSet(ep)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, err = p.recordSetsClient.CreateOrUpdate(
|
_, err = p.recordSetsClient.CreateOrUpdate(
|
||||||
ctx,
|
ctx,
|
||||||
p.resourceGroup,
|
p.resourceGroup,
|
||||||
zone,
|
zone,
|
||||||
privatedns.RecordType(endpoint.RecordType),
|
privatedns.RecordType(ep.RecordType),
|
||||||
name,
|
name,
|
||||||
recordSet,
|
recordSet,
|
||||||
"",
|
"",
|
||||||
@ -316,9 +314,9 @@ func (p *AzurePrivateDNSProvider) updateRecords(ctx context.Context, updated azu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf(
|
log.Errorf(
|
||||||
"Failed to update %s record named '%s' to '%s' for Azure Private DNS zone '%s': %v",
|
"Failed to update %s record named '%s' to '%s' for Azure Private DNS zone '%s': %v",
|
||||||
endpoint.RecordType,
|
ep.RecordType,
|
||||||
name,
|
name,
|
||||||
endpoint.Targets,
|
ep.Targets,
|
||||||
zone,
|
zone,
|
||||||
err,
|
err,
|
||||||
)
|
)
|
||||||
|
@ -18,16 +18,11 @@ package azure
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/services/privatedns/mgmt/2018-09-01/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"
|
||||||
"github.com/Azure/go-autorest/autorest/azure"
|
|
||||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
|
||||||
"github.com/Azure/go-autorest/autorest/to"
|
"github.com/Azure/go-autorest/autorest/to"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"sigs.k8s.io/external-dns/endpoint"
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
"sigs.k8s.io/external-dns/plan"
|
"sigs.k8s.io/external-dns/plan"
|
||||||
"sigs.k8s.io/external-dns/provider"
|
"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) {
|
func TestAzurePrivateDNSRecord(t *testing.T) {
|
||||||
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
|
provider, err := newMockedAzurePrivateDNSProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), true, "k8s",
|
||||||
&[]privatedns.PrivateZone{
|
&[]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{}
|
records := []*domain.Record{}
|
||||||
|
|
||||||
for _, target := range ep.Targets {
|
for _, target := range ep.Targets {
|
||||||
|
finalTargetName := target
|
||||||
|
if domain.RecordType(ep.RecordType) == domain.RecordTypeCNAME {
|
||||||
|
finalTargetName = provider.EnsureTrailingDot(target)
|
||||||
|
}
|
||||||
|
|
||||||
records = append(records, &domain.Record{
|
records = append(records, &domain.Record{
|
||||||
Data: target,
|
Data: finalTargetName,
|
||||||
Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "),
|
Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "),
|
||||||
Priority: priority,
|
Priority: priority,
|
||||||
TTL: ttl,
|
TTL: ttl,
|
||||||
@ -285,9 +290,14 @@ func endpointToScalewayRecordsChangeDelete(zoneName string, ep *endpoint.Endpoin
|
|||||||
records := []*domain.RecordChange{}
|
records := []*domain.RecordChange{}
|
||||||
|
|
||||||
for _, target := range ep.Targets {
|
for _, target := range ep.Targets {
|
||||||
|
finalTargetName := target
|
||||||
|
if domain.RecordType(ep.RecordType) == domain.RecordTypeCNAME {
|
||||||
|
finalTargetName = provider.EnsureTrailingDot(target)
|
||||||
|
}
|
||||||
|
|
||||||
records = append(records, &domain.RecordChange{
|
records = append(records, &domain.RecordChange{
|
||||||
Delete: &domain.RecordChangeDelete{
|
Delete: &domain.RecordChangeDelete{
|
||||||
Data: target,
|
Data: finalTargetName,
|
||||||
Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "),
|
Name: strings.Trim(strings.TrimSuffix(ep.DNSName, zoneName), ". "),
|
||||||
Type: domain.RecordType(ep.RecordType),
|
Type: domain.RecordType(ep.RecordType),
|
||||||
},
|
},
|
||||||
|
@ -93,7 +93,7 @@ func (m *mockScalewayDomain) ListDNSZoneRecords(req *domain.ListDNSZoneRecordsRe
|
|||||||
Type: domain.RecordTypeA,
|
Type: domain.RecordTypeA,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Data: "test.example.com",
|
Data: "test.example.com.",
|
||||||
Name: "two",
|
Name: "two",
|
||||||
TTL: 600,
|
TTL: 600,
|
||||||
Priority: 30,
|
Priority: 30,
|
||||||
@ -330,7 +330,7 @@ func TestScalewayProvider_generateApplyRequests(t *testing.T) {
|
|||||||
Add: &domain.RecordChangeAdd{
|
Add: &domain.RecordChangeAdd{
|
||||||
Records: []*domain.Record{
|
Records: []*domain.Record{
|
||||||
{
|
{
|
||||||
Data: "example.com",
|
Data: "example.com.",
|
||||||
Name: "",
|
Name: "",
|
||||||
TTL: 600,
|
TTL: 600,
|
||||||
Type: domain.RecordTypeCNAME,
|
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 {
|
for _, port := range svc.Spec.Ports {
|
||||||
if port.NodePort > 0 {
|
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
|
// 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)
|
target := fmt.Sprintf("0 50 %d %s", port.NodePort, hostname)
|
||||||
|
|
||||||
// figure out the portname
|
// take the service name from the K8s Service object
|
||||||
portName := port.Name
|
// it is safe to use since it is DNS compatible
|
||||||
if portName == "" {
|
// see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names
|
||||||
portName = fmt.Sprintf("%d", port.NodePort)
|
serviceName := svc.ObjectMeta.Name
|
||||||
}
|
|
||||||
|
|
||||||
// figure out the protocol
|
// figure out the protocol
|
||||||
protocol := strings.ToLower(string(port.Protocol))
|
protocol := strings.ToLower(string(port.Protocol))
|
||||||
@ -643,7 +646,7 @@ func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, nodeTargets e
|
|||||||
protocol = "tcp"
|
protocol = "tcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
recordName := fmt.Sprintf("_%s._%s.%s", portName, protocol, hostname)
|
recordName := fmt.Sprintf("_%s._%s.%s", serviceName, protocol, hostname)
|
||||||
|
|
||||||
var ep *endpoint.Endpoint
|
var ep *endpoint.Endpoint
|
||||||
if ttl.IsConfigured() {
|
if ttl.IsConfigured() {
|
||||||
|
@ -1642,7 +1642,7 @@ func TestNodePortServices(t *testing.T) {
|
|||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
[]*endpoint.Endpoint{
|
[]*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},
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@ -1729,7 +1729,7 @@ func TestNodePortServices(t *testing.T) {
|
|||||||
map[string]string{},
|
map[string]string{},
|
||||||
nil,
|
nil,
|
||||||
[]*endpoint.Endpoint{
|
[]*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},
|
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@ -1775,7 +1775,7 @@ func TestNodePortServices(t *testing.T) {
|
|||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
[]*endpoint.Endpoint{
|
[]*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},
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@ -1819,7 +1819,7 @@ func TestNodePortServices(t *testing.T) {
|
|||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
[]*endpoint.Endpoint{
|
[]*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},
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@ -1865,7 +1865,7 @@ func TestNodePortServices(t *testing.T) {
|
|||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
[]*endpoint.Endpoint{
|
[]*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},
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@ -1912,7 +1912,7 @@ func TestNodePortServices(t *testing.T) {
|
|||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
[]*endpoint.Endpoint{
|
[]*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},
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@ -1959,7 +1959,7 @@ func TestNodePortServices(t *testing.T) {
|
|||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
[]*endpoint.Endpoint{
|
[]*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},
|
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
|
@ -83,12 +83,12 @@ type SingletonClientGenerator struct {
|
|||||||
kubeClient kubernetes.Interface
|
kubeClient kubernetes.Interface
|
||||||
istioClient *istioclient.Clientset
|
istioClient *istioclient.Clientset
|
||||||
cfClient *cfclient.Client
|
cfClient *cfclient.Client
|
||||||
contourClient dynamic.Interface
|
dynKubeClient dynamic.Interface
|
||||||
openshiftClient openshift.Interface
|
openshiftClient openshift.Interface
|
||||||
kubeOnce sync.Once
|
kubeOnce sync.Once
|
||||||
istioOnce sync.Once
|
istioOnce sync.Once
|
||||||
cfOnce sync.Once
|
cfOnce sync.Once
|
||||||
contourOnce sync.Once
|
dynCliOnce sync.Once
|
||||||
openshiftOnce sync.Once
|
openshiftOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,13 +134,13 @@ func NewCFClient(cfAPIEndpoint string, cfUsername string, cfPassword string) (*c
|
|||||||
return client, nil
|
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) {
|
func (p *SingletonClientGenerator) DynamicKubernetesClient() (dynamic.Interface, error) {
|
||||||
var err error
|
var err error
|
||||||
p.contourOnce.Do(func() {
|
p.dynCliOnce.Do(func() {
|
||||||
p.contourClient, err = NewDynamicKubernetesClient(p.KubeConfig, p.APIServerURL, p.RequestTimeout)
|
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
|
// 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 nil, err
|
||||||
}
|
}
|
||||||
return NewCloudFoundrySource(cfClient)
|
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":
|
case "contour-ingressroute":
|
||||||
kubernetesClient, err := p.KubeClient()
|
kubernetesClient, err := p.KubeClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user