From b529a92d5bd8b8b7193e5d00c27cfeea11fed73b Mon Sep 17 00:00:00 2001 From: Dave Grizzanti Date: Sun, 31 Mar 2019 15:50:44 -0400 Subject: [PATCH] Add Cloud Foundry routes as a source --- main.go | 3 ++ pkg/apis/externaldns/types.go | 13 +++++++- source/route.go | 58 +++++++++++++++++++++++++++++++++++ source/store.go | 34 ++++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 source/route.go diff --git a/main.go b/main.go index fe8c5bb84..d707b645a 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,9 @@ func main() { KubeMaster: cfg.Master, ServiceTypeFilter: cfg.ServiceTypeFilter, IstioIngressGatewayServices: cfg.IstioIngressGatewayServices, + CFAPIEndpoint: cfg.CFAPIEndpoint, + CFUsername: cfg.CFUsername, + CFPassword: cfg.CFPassword, } // Lookup all the selected sources by names and pass them the desired configuration. diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index 1050d67aa..9bd884375 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -105,6 +105,9 @@ type Config struct { CRDSourceAPIVersion string CRDSourceKind string ServiceTypeFilter []string + CFAPIEndpoint string + CFUsername string + CFPassword string RFC2136Host string RFC2136Port int RFC2136Zone string @@ -182,6 +185,9 @@ var defaultConfig = &Config{ CRDSourceAPIVersion: "externaldns.k8s.io/v1alpha1", CRDSourceKind: "DNSEndpoint", ServiceTypeFilter: []string{}, + CFAPIEndpoint: "", + CFUsername: "", + CFPassword: "" RFC2136Host: "", RFC2136Port: 0, RFC2136Zone: "", @@ -245,8 +251,13 @@ func (cfg *Config) ParseFlags(args []string) error { // Flags related to Istio app.Flag("istio-ingress-gateway", "The fully-qualified name of the Istio ingress gateway service. Flag can be specified multiple times (default: istio-system/istio-ingressgateway)").Default("istio-system/istio-ingressgateway").StringsVar(&cfg.IstioIngressGatewayServices) + // Flags related to cloud foundry + app.Flag("cf-api-endpoint", "The fully-qualified domain name of the cloud foundry instance you are targeting").Default(defaultConfig.CFAPIEndpoint).StringVar(&cfg.CFAPIEndpoint) + app.Flag("cf-username", "The username to log into the cloud foundry API").Default(defaultConfig.CFUsername).StringVar(&cfg.CFUsername) + app.Flag("cf-password", "The password to log into the cloud foundry API").Default(defaultConfig.CFPassword).StringVar(&cfg.CFPassword) + // 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, fake, connector, istio-gateway, crd").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "istio-gateway", "fake", "connector", "crd") + app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, fake, connector, istio-gateway, crd").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "istio-gateway", "route", "fake", "connector", "crd") 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("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate) diff --git a/source/route.go b/source/route.go new file mode 100644 index 000000000..8b914634f --- /dev/null +++ b/source/route.go @@ -0,0 +1,58 @@ +/* +Copyright 2018 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 ( + "net/url" + + cfclient "github.com/cloudfoundry-community/go-cfclient" + "github.com/kubernetes-incubator/external-dns/endpoint" +) + +type routeSource struct { + client *cfclient.Client +} + +// NewRouteSource creates a new routeSource with the given config +func NewRouteSource(cfClient *cfclient.Client) (Source, error) { + return &routeSource{ + client: cfClient, + }, nil +} + +// Endpoints returns endpoint objects +func (rs *routeSource) Endpoints() ([]*endpoint.Endpoint, error) { + endpoints := []*endpoint.Endpoint{} + + u, err := url.Parse(rs.client.Config.ApiAddress) + if err != nil { + panic(err) + } + + domains, _ := rs.client.ListDomains() + for _, domain := range domains { + q := url.Values{} + q.Set("q", "domain_guid:"+domain.Guid) + routes, _ := rs.client.ListRoutesByQuery(q) + for _, element := range routes { + endpoints = append(endpoints, + endpoint.NewEndpointWithTTL(element.Host+"."+domain.Name, endpoint.RecordTypeCNAME, 300, u.Host)) + } + } + + return endpoints, nil +} diff --git a/source/store.go b/source/store.go index 4a143e13d..25baf2426 100644 --- a/source/store.go +++ b/source/store.go @@ -25,6 +25,7 @@ import ( "sync" + cfclient "github.com/cloudfoundry-community/go-cfclient" "github.com/linki/instrumented_http" log "github.com/sirupsen/logrus" istiocrd "istio.io/istio/pilot/pkg/config/kube/crd" @@ -53,12 +54,16 @@ type Config struct { KubeMaster string ServiceTypeFilter []string IstioIngressGatewayServices []string + CFAPIEndpoint string + CFUsername string + CFPassword string } // ClientGenerator provides clients type ClientGenerator interface { KubeClient() (kubernetes.Interface, error) IstioClient() (istiomodel.ConfigStore, error) + CloudFoundryClient(cfAPPEndpoint string, cfUsername string, cfPassword string) (*cfclient.Client, error) } // SingletonClientGenerator stores provider clients and guarantees that only one instance of client @@ -69,8 +74,10 @@ type SingletonClientGenerator struct { RequestTimeout time.Duration kubeClient kubernetes.Interface istioClient istiomodel.ConfigStore + cfClient *cfclient.Client kubeOnce sync.Once istioOnce sync.Once + cfOnce sync.Once } // KubeClient generates a kube client if it was not created before @@ -91,6 +98,27 @@ func (p *SingletonClientGenerator) IstioClient() (istiomodel.ConfigStore, error) return p.istioClient, err } +// CloudFoundryClient generates a cf client if it was not created before +func (p *SingletonClientGenerator) CloudFoundryClient(cfAPIEndpoint string, cfUsername string, cfPassword string) (*cfclient.Client, error) { + var err error + p.cfOnce.Do(func() { + p.cfClient, err = NewCFClient(cfAPIEndpoint, cfUsername, cfPassword) + }) + return p.cfClient, err +} + +// NewCFClient return a new CF client object. +func NewCFClient(cfAPIEndpoint string, cfUsername string, cfPassword string) (*cfclient.Client, error) { + c := &cfclient.Config{ + ApiAddress: "https://" + cfAPIEndpoint, + Username: cfUsername, + Password: cfPassword, + } + client, _ := cfclient.NewClient(c) + + return client, nil +} + // ByNames returns multiple Sources given multiple names. func ByNames(p ClientGenerator, names []string, cfg *Config) ([]Source, error) { sources := []Source{} @@ -130,6 +158,12 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err return nil, err } return NewIstioGatewaySource(kubernetesClient, istioClient, cfg.IstioIngressGatewayServices, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation) + case "route": + cfClient, err := p.CloudFoundryClient(cfg.CFAPIEndpoint, cfg.CFUsername, cfg.CFPassword) + if err != nil { + return nil, err + } + return NewRouteSource(cfClient) case "fake": return NewFakeSource(cfg.FQDNTemplate) case "connector":