Commit aa551ac7 authored by Jonas Michel's avatar Jonas Michel
Browse files

Add Contour IngressRoute source implementation

parent 454eb596
Showing with 1618 additions and 27 deletions
+1618 -27
......@@ -3,7 +3,7 @@ module github.com/kubernetes-incubator/external-dns
go 1.12
require (
cloud.google.com/go v0.34.0
cloud.google.com/go v0.37.4
github.com/Azure/azure-sdk-for-go v10.0.4-beta+incompatible
github.com/Azure/go-autorest v10.9.0+incompatible
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
......@@ -25,17 +25,10 @@ require (
github.com/digitalocean/godo v1.1.1
github.com/dnaeon/go-vcr v1.0.1 // indirect
github.com/dnsimple/dnsimple-go v0.14.0
github.com/envoyproxy/go-control-plane v0.6.9 // indirect
github.com/exoscale/egoscale v0.11.0
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
github.com/go-resty/resty v1.8.0 // indirect
github.com/gogo/googleapis v1.1.0 // indirect
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/golang/mock v1.2.0 // indirect
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a // indirect
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c // indirect
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
......@@ -43,7 +36,7 @@ require (
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
github.com/hashicorp/go-multierror v1.0.0 // indirect
github.com/imdario/mergo v0.3.5 // indirect
github.com/heptio/contour v0.13.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/infobloxopen/infoblox-go-client v0.0.0-20180606155407-61dc5f9b0a65
github.com/jonboulle/clockwork v0.1.0 // indirect
......@@ -54,7 +47,6 @@ require (
github.com/mattn/go-isatty v0.0.7 // indirect
github.com/miekg/dns v1.0.8
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible // indirect
github.com/nesv/go-dynect v0.6.0
......@@ -62,18 +54,16 @@ require (
github.com/onsi/ginkgo v1.8.0 // indirect
github.com/onsi/gomega v1.5.0 // indirect
github.com/oracle/oci-go-sdk v1.8.0
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829
github.com/sanyu/dynectsoap v0.0.0-20181203081243-b83de5edc4e0
github.com/satori/go.uuid v1.2.0 // indirect
github.com/sergi/go-diff v1.0.0 // indirect
github.com/sirupsen/logrus v1.2.0
github.com/sirupsen/logrus v1.4.1
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
github.com/soheilhy/cmux v0.1.3 // indirect
github.com/spf13/cobra v0.0.3 // indirect
github.com/spf13/pflag v1.0.2 // indirect
github.com/stretchr/testify v1.2.2
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 // indirect
......@@ -86,19 +76,18 @@ require (
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
golang.org/x/net v0.0.0-20190311183353-d8887717615a
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
google.golang.org/api v0.3.0
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
google.golang.org/api v0.3.1
google.golang.org/appengine v1.5.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190322154155-0dafb5275fd1
gopkg.in/yaml.v2 v2.2.2
istio.io/api v0.0.0-20190321180614-db16d82d3672
istio.io/istio v0.0.0-20190322063008-2b1331886076
k8s.io/api v0.0.0-20180628040859-072894a440bd
k8s.io/apiextensions-apiserver v0.0.0-20180628053655-3de98c57bc05 // indirect
k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d
k8s.io/api v0.0.0-20190503184017-f1b257a4ce96
k8s.io/apiextensions-apiserver v0.0.0-20190503184539-c338b28ceaa1
k8s.io/apimachinery v0.0.0-20190223001710-c182ff3b9841
k8s.io/client-go v8.0.0+incompatible
k8s.io/kube-openapi v0.0.0-20190401085232-94e1e7b7574c // indirect
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect
......
This diff is collapsed.
......@@ -85,6 +85,7 @@ func main() {
CFAPIEndpoint: cfg.CFAPIEndpoint,
CFUsername: cfg.CFUsername,
CFPassword: cfg.CFPassword,
ContourLoadBalancerService: cfg.ContourLoadBalancerService,
}
// Lookup all the selected sources by names and pass them the desired configuration.
......
......@@ -41,6 +41,7 @@ type Config struct {
KubeConfig string
RequestTimeout time.Duration
IstioIngressGatewayServices []string
ContourLoadBalancerService string
Sources []string
Namespace string
AnnotationFilter string
......@@ -129,6 +130,7 @@ var defaultConfig = &Config{
KubeConfig: "",
RequestTimeout: time.Second * 30,
IstioIngressGatewayServices: []string{"istio-system/istio-ingressgateway"},
ContourLoadBalancerService: "heptio-contour/contour",
Sources: nil,
Namespace: "",
AnnotationFilter: "",
......@@ -260,8 +262,11 @@ func (cfg *Config) ParseFlags(args []string) error {
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 Contour
app.Flag("contour-load-balancer", "The fully-qualified name of the Contour load balancer service. (default: heptio-contour/contour)").Default("heptio-contour/contour").StringVar(&cfg.ContourLoadBalancerService)
// 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, cloudfoundry, crd, empty").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "istio-gateway", "cloudfoundry", "fake", "connector", "crd", "empty")
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, cloudfoundry, contour-ingressroute, crd, empty").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "istio-gateway", "cloudfoundry", "contour-ingressroute", "fake", "connector", "crd", "empty")
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)
......
......@@ -33,6 +33,7 @@ var (
KubeConfig: "",
RequestTimeout: time.Second * 30,
IstioIngressGatewayServices: []string{"istio-system/istio-ingressgateway"},
ContourLoadBalancerService: "heptio-contour/contour",
Sources: []string{"service"},
Namespace: "",
FQDNTemplate: "",
......@@ -93,6 +94,7 @@ var (
KubeConfig: "/some/path",
RequestTimeout: time.Second * 77,
IstioIngressGatewayServices: []string{"istio-other/istio-otheringressgateway"},
ContourLoadBalancerService: "heptio-contour-other/contour-other",
Sources: []string{"service", "ingress", "connector"},
Namespace: "namespace",
IgnoreHostnameAnnotation: true,
......@@ -162,6 +164,7 @@ var (
RequestTimeout: time.Second * 30,
IstioIngressGatewayServices: []string{"istio-system/istio-ingressgateway", "istio-other/istio-otheringressgateway"},
Sources: []string{"istio-gateway"},
ContourLoadBalancerService: "heptio-contour/contour",
Namespace: "",
FQDNTemplate: "",
Compatibility: "",
......@@ -237,6 +240,7 @@ func TestParseFlags(t *testing.T) {
"--kubeconfig=/some/path",
"--request-timeout=77s",
"--istio-ingress-gateway=istio-other/istio-otheringressgateway",
"--contour-load-balancer=heptio-contour-other/contour-other",
"--source=service",
"--source=ingress",
"--source=connector",
......@@ -314,6 +318,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_KUBECONFIG": "/some/path",
"EXTERNAL_DNS_REQUEST_TIMEOUT": "77s",
"EXTERNAL_DNS_ISTIO_INGRESS_GATEWAY": "istio-other/istio-otheringressgateway",
"EXTERNAL_DNS_CONTOUR_LOAD_BALANCER": "heptio-contour-other/contour-other",
"EXTERNAL_DNS_SOURCE": "service\ningress\nconnector",
"EXTERNAL_DNS_NAMESPACE": "namespace",
"EXTERNAL_DNS_FQDN_TEMPLATE": "{{.Name}}.service.example.com",
......
/*
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 source
import (
"bytes"
"fmt"
"sort"
"strings"
"text/template"
"time"
contourapi "github.com/heptio/contour/apis/contour/v1beta1"
contour "github.com/heptio/contour/apis/generated/clientset/versioned"
contourinformers "github.com/heptio/contour/apis/generated/informers/externalversions"
extinformers "github.com/heptio/contour/apis/generated/informers/externalversions/contour/v1beta1"
"github.com/kubernetes-incubator/external-dns/endpoint"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
)
// ingressRouteSource is an implementation of Source for Heptio Contour IngressRoute objects.
// The IngressRoute implementation uses the spec.virtualHost.fqdn value for the hostname.
// Use targetAnnotationKey to explicitly set Endpoint.
type ingressRouteSource struct {
kubeClient kubernetes.Interface
contourClient contour.Interface
contourLoadBalancerService string
namespace string
annotationFilter string
fqdnTemplate *template.Template
combineFQDNAnnotation bool
ignoreHostnameAnnotation bool
ingressRouteInformer extinformers.IngressRouteInformer
}
// NewIngressRouteSource creates a new ingressRouteSource with the given config.
func NewContourIngressRouteSource(
kubeClient kubernetes.Interface,
contourClient contour.Interface,
contourLoadBalancerService string,
namespace string,
annotationFilter string,
fqdnTemplate string,
combineFqdnAnnotation bool,
ignoreHostnameAnnotation bool,
) (Source, error) {
var (
tmpl *template.Template
err error
)
if fqdnTemplate != "" {
tmpl, err = template.New("endpoint").Funcs(template.FuncMap{
"trimPrefix": strings.TrimPrefix,
}).Parse(fqdnTemplate)
if err != nil {
return nil, err
}
}
if _, _, err = parseContourLoadBalancerService(contourLoadBalancerService); err != nil {
return nil, err
}
// Use shared informer to listen for add/update/delete of ingressroutes in the specified namespace.
// Set resync period to 0, to prevent processing when nothing has changed.
informerFactory := contourinformers.NewSharedInformerFactoryWithOptions(
contourClient,
0,
contourinformers.WithNamespace(namespace),
)
ingressRouteInformer := informerFactory.Contour().V1beta1().IngressRoutes()
// Add default resource event handlers to properly initialize informer.
ingressRouteInformer.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 = wait.Poll(time.Second, 60*time.Second, func() (bool, error) {
return ingressRouteInformer.Informer().HasSynced() == true, nil
})
if err != nil {
return nil, fmt.Errorf("failed to sync cache: %v", err)
}
return &ingressRouteSource{
kubeClient: kubeClient,
contourClient: contourClient,
contourLoadBalancerService: contourLoadBalancerService,
namespace: namespace,
annotationFilter: annotationFilter,
fqdnTemplate: tmpl,
combineFQDNAnnotation: combineFqdnAnnotation,
ignoreHostnameAnnotation: ignoreHostnameAnnotation,
ingressRouteInformer: ingressRouteInformer,
}, nil
}
// Endpoints returns endpoint objects for each host-target combination that should be processed.
// Retrieves all ingressroute resources in the source's namespace(s).
func (sc *ingressRouteSource) Endpoints() ([]*endpoint.Endpoint, error) {
irs, err := sc.ingressRouteInformer.Lister().IngressRoutes(sc.namespace).List(labels.Everything())
if err != nil {
return nil, err
}
irs, err = sc.filterByAnnotations(irs)
if err != nil {
return nil, err
}
endpoints := []*endpoint.Endpoint{}
for _, ir := range irs {
// Check controller annotation to see if we are responsible.
controller, ok := ir.Annotations[controllerAnnotationKey]
if ok && controller != controllerAnnotationValue {
log.Debugf("Skipping ingressroute %s/%s because controller value does not match, found: %s, required: %s",
ir.Namespace, ir.Name, controller, controllerAnnotationValue)
continue
} else if ir.CurrentStatus != "valid" {
log.Debugf("Skipping ingressroute %s/%s because it is not valid", ir.Namespace, ir.Name)
continue
}
irEndpoints, err := sc.endpointsFromIngressRoute(ir)
if err != nil {
return nil, err
}
// apply template if fqdn is missing on ingressroute
if (sc.combineFQDNAnnotation || len(irEndpoints) == 0) && sc.fqdnTemplate != nil {
tmplEndpoints, err := sc.endpointsFromTemplate(ir)
if err != nil {
return nil, err
}
if sc.combineFQDNAnnotation {
irEndpoints = append(irEndpoints, tmplEndpoints...)
} else {
irEndpoints = tmplEndpoints
}
}
if len(irEndpoints) == 0 {
log.Debugf("No endpoints could be generated from ingressroute %s/%s", ir.Namespace, ir.Name)
continue
}
log.Debugf("Endpoints generated from ingressroute: %s/%s: %v", ir.Namespace, ir.Name, irEndpoints)
sc.setResourceLabel(ir, irEndpoints)
endpoints = append(endpoints, irEndpoints...)
}
for _, ep := range endpoints {
sort.Sort(ep.Targets)
}
return endpoints, nil
}
func (sc *ingressRouteSource) endpointsFromTemplate(ingressRoute *contourapi.IngressRoute) ([]*endpoint.Endpoint, error) {
// Process the whole template string
var buf bytes.Buffer
err := sc.fqdnTemplate.Execute(&buf, ingressRoute)
if err != nil {
return nil, fmt.Errorf("failed to apply template on ingressroute %s/%s: %v", ingressRoute.Namespace, ingressRoute.Name, err)
}
hostnames := buf.String()
ttl, err := getTTLFromAnnotations(ingressRoute.Annotations)
if err != nil {
log.Warn(err)
}
targets := getTargetsFromTargetAnnotation(ingressRoute.Annotations)
if len(targets) == 0 {
targets, err = sc.targetsFromContourLoadBalancer()
if err != nil {
return nil, err
}
}
providerSpecific := getProviderSpecificAnnotations(ingressRoute.Annotations)
var endpoints []*endpoint.Endpoint
// splits the FQDN template and removes the trailing periods
hostnameList := strings.Split(strings.Replace(hostnames, " ", "", -1), ",")
for _, hostname := range hostnameList {
hostname = strings.TrimSuffix(hostname, ".")
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
}
return endpoints, nil
}
// filterByAnnotations filters a list of configs by a given annotation selector.
func (sc *ingressRouteSource) filterByAnnotations(ingressRoutes []*contourapi.IngressRoute) ([]*contourapi.IngressRoute, error) {
labelSelector, err := metav1.ParseToLabelSelector(sc.annotationFilter)
if err != nil {
return nil, err
}
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
if err != nil {
return nil, err
}
// empty filter returns original list
if selector.Empty() {
return ingressRoutes, nil
}
filteredList := []*contourapi.IngressRoute{}
for _, ingressRoute := range ingressRoutes {
// convert the ingressroute's annotations to an equivalent label selector
annotations := labels.Set(ingressRoute.Annotations)
// include ingressroute if its annotations match the selector
if selector.Matches(annotations) {
filteredList = append(filteredList, ingressRoute)
}
}
return filteredList, nil
}
func (sc *ingressRouteSource) setResourceLabel(ingressRoute *contourapi.IngressRoute, endpoints []*endpoint.Endpoint) {
for _, ep := range endpoints {
ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("ingressroute/%s/%s", ingressRoute.Namespace, ingressRoute.Name)
}
}
func (sc *ingressRouteSource) targetsFromContourLoadBalancer() (targets endpoint.Targets, err error) {
lbNamespace, lbName, err := parseContourLoadBalancerService(sc.contourLoadBalancerService)
if err != nil {
return nil, err
}
if svc, err := sc.kubeClient.CoreV1().Services(lbNamespace).Get(lbName, metav1.GetOptions{}); err != nil {
log.Warn(err)
} else {
for _, lb := range svc.Status.LoadBalancer.Ingress {
if lb.IP != "" {
targets = append(targets, lb.IP)
}
if lb.Hostname != "" {
targets = append(targets, lb.Hostname)
}
}
}
return
}
// endpointsFromIngressRouteConfig extracts the endpoints from a Contour IngressRoute object
func (sc *ingressRouteSource) endpointsFromIngressRoute(ingressRoute *contourapi.IngressRoute) ([]*endpoint.Endpoint, error) {
if ingressRoute.CurrentStatus != "valid" {
log.Warn(errors.Errorf("cannot generate endpoints for ingressroute with status %s", ingressRoute.CurrentStatus))
return nil, nil
}
var endpoints []*endpoint.Endpoint
ttl, err := getTTLFromAnnotations(ingressRoute.Annotations)
if err != nil {
log.Warn(err)
}
targets := getTargetsFromTargetAnnotation(ingressRoute.Annotations)
if len(targets) == 0 {
targets, err = sc.targetsFromContourLoadBalancer()
if err != nil {
return nil, err
}
}
providerSpecific := getProviderSpecificAnnotations(ingressRoute.Annotations)
host := ingressRoute.Spec.VirtualHost.Fqdn
if host != "" {
endpoints = append(endpoints, endpointsForHostname(host, targets, ttl, providerSpecific)...)
}
// Skip endpoints if we do not want entries from annotations
if !sc.ignoreHostnameAnnotation {
hostnameList := getHostnamesFromAnnotations(ingressRoute.Annotations)
for _, hostname := range hostnameList {
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific)...)
}
}
return endpoints, nil
}
func parseContourLoadBalancerService(service string) (namespace, name string, err error) {
parts := strings.Split(service, "/")
if len(parts) != 2 {
err = fmt.Errorf("invalid contour load balancer service (namespace/name) found '%v'", service)
} else {
namespace, name = parts[0], parts[1]
}
return
}
This diff is collapsed.
......@@ -26,6 +26,7 @@ import (
"sync"
cfclient "github.com/cloudfoundry-community/go-cfclient"
contour "github.com/heptio/contour/apis/generated/clientset/versioned"
"github.com/linki/instrumented_http"
log "github.com/sirupsen/logrus"
istiocrd "istio.io/istio/pilot/pkg/config/kube/crd"
......@@ -57,6 +58,7 @@ type Config struct {
CFAPIEndpoint string
CFUsername string
CFPassword string
ContourLoadBalancerService string
}
// ClientGenerator provides clients
......@@ -64,6 +66,7 @@ type ClientGenerator interface {
KubeClient() (kubernetes.Interface, error)
IstioClient() (istiomodel.ConfigStore, error)
CloudFoundryClient(cfAPPEndpoint string, cfUsername string, cfPassword string) (*cfclient.Client, error)
ContourClient() (contour.Interface, error)
}
// SingletonClientGenerator stores provider clients and guarantees that only one instance of client
......@@ -75,9 +78,11 @@ type SingletonClientGenerator struct {
kubeClient kubernetes.Interface
istioClient istiomodel.ConfigStore
cfClient *cfclient.Client
contourClient contour.Interface
kubeOnce sync.Once
istioOnce sync.Once
cfOnce sync.Once
contourOnce sync.Once
}
// KubeClient generates a kube client if it was not created before
......@@ -122,6 +127,15 @@ func NewCFClient(cfAPIEndpoint string, cfUsername string, cfPassword string) (*c
return client, nil
}
// ContourClient generates a contour client if it was not created before
func (p *SingletonClientGenerator) ContourClient() (contour.Interface, error) {
var err error
p.contourOnce.Do(func() {
p.contourClient, err = NewContourClient(p.KubeConfig, p.KubeMaster, p.RequestTimeout)
})
return p.contourClient, err
}
// ByNames returns multiple Sources given multiple names.
func ByNames(p ClientGenerator, names []string, cfg *Config) ([]Source, error) {
sources := []Source{}
......@@ -167,6 +181,16 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
return nil, err
}
return NewCloudFoundrySource(cfClient)
case "contour-ingressroute":
kubernetesClient, err := p.KubeClient()
if err != nil {
return nil, err
}
contourClient, err := p.ContourClient()
if err != nil {
return nil, err
}
return NewContourIngressRouteSource(kubernetesClient, contourClient, cfg.ContourLoadBalancerService, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation)
case "fake":
return NewFakeSource(cfg.FQDNTemplate)
case "connector":
......@@ -250,3 +274,39 @@ func NewIstioClient(kubeConfig string) (*istiocrd.Client, error) {
return client, nil
}
// NewContourClient returns a new Contour client object. It takes a Config and
// uses KubeMaster and KubeConfig attributes to connect to the cluster. If
// KubeConfig isn't provided it defaults to using the recommended default.
func NewContourClient(kubeConfig, kubeMaster string, requestTimeout time.Duration) (*contour.Clientset, error) {
if kubeConfig == "" {
if _, err := os.Stat(clientcmd.RecommendedHomeFile); err == nil {
kubeConfig = clientcmd.RecommendedHomeFile
}
}
config, err := clientcmd.BuildConfigFromFlags(kubeMaster, kubeConfig)
if err != nil {
return nil, err
}
config.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
return instrumented_http.NewTransport(rt, &instrumented_http.Callbacks{
PathProcessor: func(path string) string {
parts := strings.Split(path, "/")
return parts[len(parts)-1]
},
})
}
config.Timeout = requestTimeout
client, err := contour.NewForConfig(config)
if err != nil {
return nil, err
}
log.Infof("Created Contour client %s", config.Host)
return client, nil
}
......@@ -20,13 +20,14 @@ import (
"errors"
"testing"
istiomodel "istio.io/istio/pilot/pkg/model"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
cfclient "github.com/cloudfoundry-community/go-cfclient"
contour "github.com/heptio/contour/apis/generated/clientset/versioned"
fakeContour "github.com/heptio/contour/apis/generated/clientset/versioned/fake"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
istiomodel "istio.io/istio/pilot/pkg/model"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
)
type MockClientGenerator struct {
......@@ -34,6 +35,7 @@ type MockClientGenerator struct {
kubeClient kubernetes.Interface
istioClient istiomodel.ConfigStore
cloudFoundryClient *cfclient.Client
contourClient contour.Interface
}
func (m *MockClientGenerator) KubeClient() (kubernetes.Interface, error) {
......@@ -63,6 +65,15 @@ func (m *MockClientGenerator) CloudFoundryClient(cfAPIEndpoint string, cfUsernam
return nil, args.Error(1)
}
func (m *MockClientGenerator) ContourClient() (contour.Interface, error) {
args := m.Called()
if args.Error(1) == nil {
m.contourClient = args.Get(0).(contour.Interface)
return m.contourClient, nil
}
return nil, args.Error(1)
}
type ByNamesTestSuite struct {
suite.Suite
}
......@@ -71,10 +82,11 @@ func (suite *ByNamesTestSuite) TestAllInitialized() {
mockClientGenerator := new(MockClientGenerator)
mockClientGenerator.On("KubeClient").Return(fake.NewSimpleClientset(), nil)
mockClientGenerator.On("IstioClient").Return(NewFakeConfigStore(), nil)
mockClientGenerator.On("ContourClient").Return(fakeContour.NewSimpleClientset(), nil)
sources, err := ByNames(mockClientGenerator, []string{"service", "ingress", "istio-gateway", "fake"}, minimalConfig)
sources, err := ByNames(mockClientGenerator, []string{"service", "ingress", "istio-gateway", "contour-ingressroute", "fake"}, minimalConfig)
suite.NoError(err, "should not generate errors")
suite.Len(sources, 4, "should generate all four sources")
suite.Len(sources, 5, "should generate all five sources")
}
func (suite *ByNamesTestSuite) TestOnlyFake() {
......@@ -108,15 +120,22 @@ func (suite *ByNamesTestSuite) TestKubeClientFails() {
_, err = ByNames(mockClientGenerator, []string{"istio-gateway"}, minimalConfig)
suite.Error(err, "should return an error if kubernetes client cannot be created")
_, err = ByNames(mockClientGenerator, []string{"contour-ingressroute"}, minimalConfig)
suite.Error(err, "should return an error if kubernetes client cannot be created")
}
func (suite *ByNamesTestSuite) TestIstioClientFails() {
mockClientGenerator := new(MockClientGenerator)
mockClientGenerator.On("KubeClient").Return(fake.NewSimpleClientset(), nil)
mockClientGenerator.On("IstioClient").Return(nil, errors.New("foo"))
mockClientGenerator.On("ContourClient").Return(nil, errors.New("foo"))
_, err := ByNames(mockClientGenerator, []string{"istio-gateway"}, minimalConfig)
suite.Error(err, "should return an error if istio client cannot be created")
_, err = ByNames(mockClientGenerator, []string{"contour-ingressroute"}, minimalConfig)
suite.Error(err, "should return an error if contour client cannot be created")
}
func TestByNames(t *testing.T) {
......@@ -125,4 +144,5 @@ func TestByNames(t *testing.T) {
var minimalConfig = &Config{
IstioIngressGatewayServices: []string{"istio-system/istio-ingressgateway"},
ContourLoadBalancerService: "heptio-contour/contour",
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment