Switched Kubernetes provider to new client implementation: https://github.com/kubernetes/client-go

This commit is contained in:
Yves Peter 2016-11-11 23:50:20 +01:00 committed by Yves Peter
parent 82234cbbb2
commit 15540764a0
10 changed files with 510 additions and 1131 deletions

View file

@ -102,3 +102,5 @@ import:
- package: github.com/ArthurHlt/go-eureka-client - package: github.com/ArthurHlt/go-eureka-client
subpackages: subpackages:
- eureka - eureka
- package: k8s.io/client-go
version: ^v1.5.0

View file

@ -1,167 +1,206 @@
package k8s package k8s
import ( import (
"crypto/tls" "k8s.io/client-go/1.5/kubernetes"
"crypto/x509" "k8s.io/client-go/1.5/pkg/api"
"encoding/json" "k8s.io/client-go/1.5/pkg/api/v1"
"fmt" "k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1"
"github.com/containous/traefik/log" "k8s.io/client-go/1.5/pkg/fields"
"github.com/parnurzeal/gorequest" "k8s.io/client-go/1.5/pkg/labels"
"net/http" "k8s.io/client-go/1.5/pkg/runtime"
"net/url" "k8s.io/client-go/1.5/pkg/watch"
"strings" "k8s.io/client-go/1.5/rest"
) "k8s.io/client-go/1.5/tools/cache"
const (
// APIEndpoint defines the base path for kubernetes API resources.
APIEndpoint = "/api/v1"
extentionsEndpoint = "/apis/extensions/v1beta1"
defaultIngress = "/ingresses"
namespaces = "/namespaces/"
) )
// Client is a client for the Kubernetes master. // Client is a client for the Kubernetes master.
type Client interface { type Client interface {
GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error) GetIngresses(namespaces Namespaces) []*v1beta1.Ingress
GetService(name, namespace string) (Service, error) GetService(namespace, name string) (*v1.Service, bool, error)
GetEndpoints(name, namespace string) (Endpoints, error) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error)
WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, error)
} }
type clientImpl struct { type clientImpl struct {
endpointURL string ingController *cache.Controller
tls *tls.Config svcController *cache.Controller
token string epController *cache.Controller
caCert []byte
ingStore cache.Store
svcStore cache.Store
epStore cache.Store
clientset *kubernetes.Clientset
} }
// NewClient returns a new Kubernetes client. // NewInClusterClient returns a new Kubernetes client.
// The provided host is an url (scheme://hostname[:port]) of a // WatchAll starts the watch of the Kubernetes ressources and updates the stores.
// Kubernetes master without any path. // The stores can be accessed via the Get* functions.
// The provided client is an authorized http.Client used to perform requests to the Kubernetes API master. func NewInClusterClient() (Client, error) {
func NewClient(baseURL string, caCert []byte, token string) (Client, error) { config, err := rest.InClusterConfig()
validURL, err := url.Parse(baseURL)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err) return nil, err
} }
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &clientImpl{ return &clientImpl{
endpointURL: strings.TrimSuffix(validURL.String(), "/"), clientset: clientset,
token: token,
caCert: caCert,
}, nil }, nil
} }
func makeQueryString(baseParams map[string]string, labelSelector string) (string, error) { // NewInClusterClientWithEndpoint is the same as NewInClusterClient but uses the provided endpoint URL
if labelSelector != "" { func NewInClusterClientWithEndpoint(endpoint string) (Client, error) {
baseParams["labelSelector"] = labelSelector config, err := rest.InClusterConfig()
}
queryData, err := json.Marshal(baseParams)
if err != nil { if err != nil {
return "", err return nil, err
} }
return string(queryData), nil
config.Host = endpoint
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &clientImpl{
clientset: clientset,
}, nil
} }
// GetIngresses returns all ingresses in the cluster // GetIngresses returns all ingresses in the cluster
func (c *clientImpl) GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error) { func (c *clientImpl) GetIngresses(namespaces Namespaces) []*v1beta1.Ingress {
getURL := c.endpointURL + extentionsEndpoint + defaultIngress ingList := c.ingStore.List()
queryParams := map[string]string{} result := make([]*v1beta1.Ingress, 0, len(ingList))
queryData, err := makeQueryString(queryParams, labelSelector)
if err != nil { for _, obj := range ingList {
return nil, fmt.Errorf("Had problems constructing query string %s : %v", queryParams, err) ingress := obj.(*v1beta1.Ingress)
if HasNamespace(ingress, namespaces) {
result = append(result, ingress)
} }
body, err := c.do(c.request(getURL, queryData))
if err != nil {
return nil, fmt.Errorf("failed to create ingresses request: GET %q : %v", getURL, err)
} }
var ingressList IngressList return result
if err := json.Unmarshal(body, &ingressList); err != nil {
return nil, fmt.Errorf("failed to decode list of ingress resources: %v", err)
}
ingresses := ingressList.Items[:0]
for _, ingress := range ingressList.Items {
if predicate(ingress) {
ingresses = append(ingresses, ingress)
}
}
return ingresses, nil
} }
// WatchIngresses returns all ingresses in the cluster // WatchIngresses starts the watch of Kubernetes Ingresses resources and updates the corresponding store
func (c *clientImpl) WatchIngresses(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) WatchIngresses(labelSelector labels.Selector, stopCh <-chan struct{}) chan interface{} {
getURL := c.endpointURL + extentionsEndpoint + defaultIngress watchCh := make(chan interface{}, 10)
return c.watch(getURL, labelSelector, stopCh)
source := NewListWatchFromClient(
c.clientset.ExtensionsClient,
"ingresses",
api.NamespaceAll,
fields.Everything(),
labelSelector)
c.ingStore, c.ingController = cache.NewInformer(
source,
&v1beta1.Ingress{},
0,
newResourceEventHandlerFuncs(watchCh))
go c.ingController.Run(stopCh)
return watchCh
}
func newResourceEventHandlerFuncs(events chan interface{}) cache.ResourceEventHandlerFuncs {
return cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { events <- obj },
UpdateFunc: func(old, new interface{}) { events <- new },
DeleteFunc: func(obj interface{}) { events <- obj },
}
} }
// GetService returns the named service from the named namespace // GetService returns the named service from the named namespace
func (c *clientImpl) GetService(name, namespace string) (Service, error) { func (c *clientImpl) GetService(namespace, name string) (*v1.Service, bool, error) {
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name var service *v1.Service
item, exists, err := c.svcStore.GetByKey(namespace + "/" + name)
body, err := c.do(c.request(getURL, "")) if item != nil {
if err != nil { service = item.(*v1.Service)
return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err)
} }
var service Service return service, exists, err
if err := json.Unmarshal(body, &service); err != nil {
return Service{}, fmt.Errorf("failed to decode service resource: %v", err)
}
return service, nil
} }
// WatchServices returns all services in the cluster // WatchServices starts the watch of Kubernetes Service resources and updates the corresponding store
func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) WatchServices(stopCh <-chan struct{}) chan interface{} {
getURL := c.endpointURL + APIEndpoint + "/services" watchCh := make(chan interface{}, 10)
return c.watch(getURL, "", stopCh)
source := cache.NewListWatchFromClient(
c.clientset.CoreClient,
"services",
api.NamespaceAll,
fields.Everything())
c.svcStore, c.svcController = cache.NewInformer(
source,
&v1.Service{},
0,
newResourceEventHandlerFuncs(watchCh))
go c.svcController.Run(stopCh)
return watchCh
} }
// GetEndpoints returns the named Endpoints // GetEndpoints returns the named Endpoints
// Endpoints have the same name as the coresponding service // Endpoints have the same name as the coresponding service
func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) { func (c *clientImpl) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) {
getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name var endpoint *v1.Endpoints
item, exists, err := c.epStore.GetByKey(namespace + "/" + name)
body, err := c.do(c.request(getURL, "")) if item != nil {
if err != nil { endpoint = item.(*v1.Endpoints)
return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err)
} }
var endpoints Endpoints return endpoint, exists, err
if err := json.Unmarshal(body, &endpoints); err != nil {
return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err)
}
return endpoints, nil
} }
// WatchEndpoints returns endpoints in the cluster // WatchEndpoints starts the watch of Kubernetes Endpoints resources and updates the corresponding store
func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) { func (c *clientImpl) WatchEndpoints(stopCh <-chan struct{}) chan interface{} {
getURL := c.endpointURL + APIEndpoint + "/endpoints"
return c.watch(getURL, "", stopCh)
}
// WatchAll returns events in the cluster
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}, 10) watchCh := make(chan interface{}, 10)
errCh := make(chan error, 10)
stopIngresses := make(chan bool) source := cache.NewListWatchFromClient(
chanIngresses, chanIngressesErr, err := c.WatchIngresses(labelSelector, stopIngresses) c.clientset.CoreClient,
"endpoints",
api.NamespaceAll,
fields.Everything())
c.epStore, c.epController = cache.NewInformer(
source,
&v1.Endpoints{},
0,
newResourceEventHandlerFuncs(watchCh))
go c.epController.Run(stopCh)
return watchCh
}
// WatchAll returns events in the cluster and updates the stores via informer
// Filters ingresses by labelSelector
func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, error) {
watchCh := make(chan interface{}, 10)
kubeLabelSelector, err := labels.Parse(labelSelector)
if err != nil { if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) return nil, err
}
stopServices := make(chan bool)
chanServices, chanServicesErr, err := c.WatchServices(stopServices)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
}
stopEndpoints := make(chan bool)
chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err)
} }
stopIngresses := make(chan struct{})
chanIngresses := c.WatchIngresses(kubeLabelSelector, stopIngresses)
stopServices := make(chan struct{})
chanServices := c.WatchServices(stopServices)
stopEndpoints := make(chan struct{})
chanEndpoints := c.WatchEndpoints(stopEndpoints)
go func() { go func() {
defer close(watchCh) defer close(watchCh)
defer close(errCh)
defer close(stopIngresses) defer close(stopIngresses)
defer close(stopServices) defer close(stopServices)
defer close(stopEndpoints) defer close(stopEndpoints)
@ -169,128 +208,63 @@ func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan in
for { for {
select { select {
case <-stopCh: case <-stopCh:
stopIngresses <- true
stopServices <- true
stopEndpoints <- true
return return
case err := <-chanIngressesErr:
errCh <- err
case err := <-chanServicesErr:
errCh <- err
case err := <-chanEndpointsErr:
errCh <- err
case event := <-chanIngresses: case event := <-chanIngresses:
watchCh <- event c.fireEvent(event, watchCh)
case event := <-chanServices: case event := <-chanServices:
watchCh <- event c.fireEvent(event, watchCh)
case event := <-chanEndpoints: case event := <-chanEndpoints:
c.fireEvent(event, watchCh)
}
}
}()
return watchCh, nil
}
// fireEvent checks if all controllers have synced before firing
// Used after startup or a reconnect
func (c *clientImpl) fireEvent(event interface{}, watchCh chan interface{}) {
if c.ingController.HasSynced() && c.svcController.HasSynced() && c.epController.HasSynced() {
watchCh <- event watchCh <- event
} }
}
}()
return watchCh, errCh, nil
} }
func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) { // HasNamespace checks if the ingress is in one of the namespaces
res, body, errs := request.EndBytes() func HasNamespace(ingress *v1beta1.Ingress, namespaces Namespaces) bool {
if errs != nil { if len(namespaces) == 0 {
return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs) return true
} }
defer res.Body.Close() for _, n := range namespaces {
if res.StatusCode != http.StatusOK { if ingress.ObjectMeta.Namespace == n {
return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body)) return true
} }
return body, nil }
return false
} }
func (c *clientImpl) request(reqURL string, queryContent interface{}) *gorequest.SuperAgent { // NewListWatchFromClient creates a new ListWatch from the specified client, resource, namespace, field selector and label selector.
// Make request to Kubernetes API // Extends cache.NewListWatchFromClient to support labelSelector
parsedURL, parseErr := url.Parse(reqURL) func NewListWatchFromClient(c cache.Getter, resource string, namespace string, fieldSelector fields.Selector, labelSelector labels.Selector) *cache.ListWatch {
if parseErr != nil { listFunc := func(options api.ListOptions) (runtime.Object, error) {
log.Errorf("Had issues parsing url %s. Trying anyway.", reqURL) return c.Get().
Namespace(namespace).
Resource(resource).
VersionedParams(&options, api.ParameterCodec).
FieldsSelectorParam(fieldSelector).
LabelsSelectorParam(labelSelector).
Do().
Get()
} }
request := gorequest.New().Get(reqURL) watchFunc := func(options api.ListOptions) (watch.Interface, error) {
request.Transport.DisableKeepAlives = true return c.Get().
Prefix("watch").
if parsedURL.Scheme == "https" { Namespace(namespace).
pool := x509.NewCertPool() Resource(resource).
pool.AppendCertsFromPEM(c.caCert) VersionedParams(&options, api.ParameterCodec).
c.tls = &tls.Config{RootCAs: pool} FieldsSelectorParam(fieldSelector).
request.TLSClientConfig(c.tls) LabelsSelectorParam(labelSelector).
Watch()
} }
if len(c.token) > 0 { return &cache.ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
request.Header["Authorization"] = "Bearer " + c.token
}
request.Query(queryContent)
return request
}
// GenericObject generic object
type GenericObject struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
}
func (c *clientImpl) watch(url string, labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{}, 10)
errCh := make(chan error, 10)
// get version
body, err := c.do(c.request(url, ""))
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to do version request: GET %q : %v", url, err)
}
var generic GenericObject
if err := json.Unmarshal(body, &generic); err != nil {
return watchCh, errCh, fmt.Errorf("failed to decode version %v", err)
}
resourceVersion := generic.ResourceVersion
queryParams := map[string]string{"watch": "", "resourceVersion": resourceVersion}
queryData, err := makeQueryString(queryParams, labelSelector)
if err != nil {
return watchCh, errCh, fmt.Errorf("Unable to construct query args")
}
request := c.request(url, queryData)
req, err := request.MakeRequest()
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err)
}
request.Client.Transport = request.Transport
res, err := request.Client.Do(req)
if err != nil {
return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err)
}
go func() {
finishCh := make(chan bool)
defer close(finishCh)
defer close(watchCh)
defer close(errCh)
go func() {
defer res.Body.Close()
for {
var eventList interface{}
if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil {
if !strings.Contains(err.Error(), "net/http: request canceled") {
errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err)
}
finishCh <- true
return
}
watchCh <- eventList
}
}()
select {
case <-stopCh:
go func() {
request.Transport.CancelRequest(req)
}()
<-finishCh
return
}
}()
return watchCh, errCh, nil
} }

View file

@ -1,84 +0,0 @@
package k8s
// Endpoints is a collection of endpoints that implement the actual service. Example:
// Name: "mysvc",
// Subsets: [
// {
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
// },
// {
// Addresses: [{"ip": "10.10.3.3"}],
// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}]
// },
// ]
type Endpoints struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// The set of all endpoints is the union of all subsets.
Subsets []EndpointSubset
}
// EndpointSubset is a group of addresses with a common set of ports. The
// expanded set of endpoints is the Cartesian product of Addresses x Ports.
// For example, given:
// {
// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}],
// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}]
// }
// The resulting set of endpoints can be viewed as:
// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ],
// b: [ 10.10.1.1:309, 10.10.2.2:309 ]
type EndpointSubset struct {
Addresses []EndpointAddress
NotReadyAddresses []EndpointAddress
Ports []EndpointPort
}
// EndpointAddress is a tuple that describes single IP address.
type EndpointAddress struct {
// The IP of this endpoint.
// IPv6 is also accepted but not fully supported on all platforms. Also, certain
// kubernetes components, like kube-proxy, are not IPv6 ready.
// TODO: This should allow hostname or IP, see #4447.
IP string
// Optional: Hostname of this endpoint
// Meant to be used by DNS servers etc.
Hostname string `json:"hostname,omitempty"`
// Optional: The kubernetes object related to the entry point.
TargetRef *ObjectReference
}
// EndpointPort is a tuple that describes a single port.
type EndpointPort struct {
// The name of this port (corresponds to ServicePort.Name). Optional
// if only one port is defined. Must be a DNS_LABEL.
Name string
// The port number.
Port int32
// The IP protocol for this port.
Protocol Protocol
}
// ObjectReference contains enough information to let you inspect or modify the referred object.
type ObjectReference struct {
Kind string `json:"kind,omitempty"`
Namespace string `json:"namespace,omitempty"`
Name string `json:"name,omitempty"`
UID UID `json:"uid,omitempty"`
APIVersion string `json:"apiVersion,omitempty"`
ResourceVersion string `json:"resourceVersion,omitempty"`
// Optional. If referring to a piece of an object instead of an entire object, this string
// should contain information to identify the sub-object. For example, if the object
// reference is to a container within a pod, this would take on a value like:
// "spec.containers{name}" (where "name" refers to the name of the container that triggered
// the event) or if no container name is specified "spec.containers[2]" (container with
// index 2 in this pod). This syntax is chosen only to have some well-defined way of
// referencing a part of an object.
// TODO: this design is not final and this field is subject to change in the future.
FieldPath string `json:"fieldPath,omitempty"`
}

View file

@ -1,151 +0,0 @@
package k8s
// Ingress is a collection of rules that allow inbound connections to reach the
// endpoints defined by a backend. An Ingress can be configured to give services
// externally-reachable urls, load balance traffic, terminate SSL, offer name
// based virtual hosting etc.
type Ingress struct {
TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
ObjectMeta `json:"metadata,omitempty"`
// Spec is the desired state of the Ingress.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
Spec IngressSpec `json:"spec,omitempty"`
// Status is the current state of the Ingress.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status
Status IngressStatus `json:"status,omitempty"`
}
// IngressList is a collection of Ingress.
type IngressList struct {
TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
ListMeta `json:"metadata,omitempty"`
// Items is the list of Ingress.
Items []Ingress `json:"items"`
}
// IngressSpec describes the Ingress the user wishes to exist.
type IngressSpec struct {
// A default backend capable of servicing requests that don't match any
// rule. At least one of 'backend' or 'rules' must be specified. This field
// is optional to allow the loadbalancer controller or defaulting logic to
// specify a global default.
Backend *IngressBackend `json:"backend,omitempty"`
// TLS configuration. Currently the Ingress only supports a single TLS
// port, 443. If multiple members of this list specify different hosts, they
// will be multiplexed on the same port according to the hostname specified
// through the SNI TLS extension, if the ingress controller fulfilling the
// ingress supports SNI.
TLS []IngressTLS `json:"tls,omitempty"`
// A list of host rules used to configure the Ingress. If unspecified, or
// no rule matches, all traffic is sent to the default backend.
Rules []IngressRule `json:"rules,omitempty"`
// TODO: Add the ability to specify load-balancer IP through claims
}
// IngressTLS describes the transport layer security associated with an Ingress.
type IngressTLS struct {
// Hosts are a list of hosts included in the TLS certificate. The values in
// this list must match the name/s used in the tlsSecret. Defaults to the
// wildcard host setting for the loadbalancer controller fulfilling this
// Ingress, if left unspecified.
Hosts []string `json:"hosts,omitempty"`
// SecretName is the name of the secret used to terminate SSL traffic on 443.
// Field is left optional to allow SSL routing based on SNI hostname alone.
// If the SNI host in a listener conflicts with the "Host" header field used
// by an IngressRule, the SNI host is used for termination and value of the
// Host header is used for routing.
SecretName string `json:"secretName,omitempty"`
// TODO: Consider specifying different modes of termination, protocols etc.
}
// IngressStatus describe the current state of the Ingress.
type IngressStatus struct {
// LoadBalancer contains the current status of the load-balancer.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// IngressRule represents the rules mapping the paths under a specified host to
// the related backend services. Incoming requests are first evaluated for a host
// match, then routed to the backend associated with the matching IngressRuleValue.
type IngressRule struct {
// Host is the fully qualified domain name of a network host, as defined
// by RFC 3986. Note the following deviations from the "host" part of the
// URI as defined in the RFC:
// 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the
// IP in the Spec of the parent Ingress.
// 2. The `:` delimiter is not respected because ports are not allowed.
// Currently the port of an Ingress is implicitly :80 for http and
// :443 for https.
// Both these may change in the future.
// Incoming requests are matched against the host before the IngressRuleValue.
// If the host is unspecified, the Ingress routes all traffic based on the
// specified IngressRuleValue.
Host string `json:"host,omitempty"`
// IngressRuleValue represents a rule to route requests for this IngressRule.
// If unspecified, the rule defaults to a http catch-all. Whether that sends
// just traffic matching the host to the default backend or all traffic to the
// default backend, is left to the controller fulfilling the Ingress. Http is
// currently the only supported IngressRuleValue.
IngressRuleValue `json:",inline,omitempty"`
}
// IngressRuleValue represents a rule to apply against incoming requests. If the
// rule is satisfied, the request is routed to the specified backend. Currently
// mixing different types of rules in a single Ingress is disallowed, so exactly
// one of the following must be set.
type IngressRuleValue struct {
//TODO:
// 1. Consider renaming this resource and the associated rules so they
// aren't tied to Ingress. They can be used to route intra-cluster traffic.
// 2. Consider adding fields for ingress-type specific global options
// usable by a loadbalancer, like http keep-alive.
HTTP *HTTPIngressRuleValue `json:"http,omitempty"`
}
// HTTPIngressRuleValue is a list of http selectors pointing to backends.
// In the example: http://<host>/<path>?<searchpart> -> backend where
// where parts of the url correspond to RFC 3986, this resource will be used
// to match against everything after the last '/' and before the first '?'
// or '#'.
type HTTPIngressRuleValue struct {
// A collection of paths that map requests to backends.
Paths []HTTPIngressPath `json:"paths"`
// TODO: Consider adding fields for ingress-type specific global
// options usable by a loadbalancer, like http keep-alive.
}
// HTTPIngressPath associates a path regex with a backend. Incoming urls matching
// the path are forwarded to the backend.
type HTTPIngressPath struct {
// Path is a extended POSIX regex as defined by IEEE Std 1003.1,
// (i.e this follows the egrep/unix syntax, not the perl syntax)
// matched against the path of an incoming request. Currently it can
// contain characters disallowed from the conventional "path"
// part of a URL as defined by RFC 3986. Paths must begin with
// a '/'. If unspecified, the path defaults to a catch all sending
// traffic to the backend.
Path string `json:"path,omitempty"`
// Backend defines the referenced service endpoint to which the traffic
// will be forwarded to.
Backend IngressBackend `json:"backend"`
}
// IngressBackend describes all endpoints for a given service and port.
type IngressBackend struct {
// Specifies the name of the referenced service.
ServiceName string `json:"serviceName"`
// Specifies the port of the referenced service.
ServicePort IntOrString `json:"servicePort"`
}

32
provider/k8s/namespace.go Normal file
View file

@ -0,0 +1,32 @@
package k8s
import (
"fmt"
"strings"
)
// Namespaces holds kubernetes namespaces
type Namespaces []string
//Set adds strings elem into the the parser
//it splits str on , and ;
func (ns *Namespaces) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
*ns = append(*ns, slice...)
return nil
}
//Get []string
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
//String return slice in a string
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
//SetValue sets []string into the parser
func (ns *Namespaces) SetValue(val interface{}) {
*ns = Namespaces(val.(Namespaces))
}

View file

@ -1,326 +0,0 @@
package k8s
import (
"encoding/json"
"strconv"
"time"
)
// TypeMeta describes an individual object in an API response or request
// with strings representing the type of the object and its API schema version.
// Structures that are versioned or persisted should inline TypeMeta.
type TypeMeta struct {
// Kind is a string value representing the REST resource this object represents.
// Servers may infer this from the endpoint the client submits requests to.
// Cannot be updated.
// In CamelCase.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds
Kind string `json:"kind,omitempty"`
// APIVersion defines the versioned schema of this representation of an object.
// Servers should convert recognized schemas to the latest internal value, and
// may reject unrecognized values.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources
APIVersion string `json:"apiVersion,omitempty"`
}
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {
// Name is unique within a namespace. Name is required when creating resources, although
// some resources may allow a client to request the generation of an appropriate name
// automatically. Name is primarily intended for creation idempotence and configuration
// definition.
Name string `json:"name,omitempty"`
// GenerateName indicates that the name should be made unique by the server prior to persisting
// it. A non-empty value for the field indicates the name will be made unique (and the name
// returned to the client will be different than the name passed). The value of this field will
// be combined with a unique suffix on the server if the Name field has not been provided.
// The provided value must be valid within the rules for Name, and may be truncated by the length
// of the suffix required to make the value unique on the server.
//
// If this field is specified, and Name is not present, the server will NOT return a 409 if the
// generated name exists - instead, it will either return 201 Created or 500 with Reason
// ServerTimeout indicating a unique name could not be found in the time allotted, and the client
// should retry (optionally after the time indicated in the Retry-After header).
GenerateName string `json:"generateName,omitempty"`
// Namespace defines the space within which name must be unique. An empty namespace is
// equivalent to the "default" namespace, but "default" is the canonical representation.
// Not all objects are required to be scoped to a namespace - the value of this field for
// those objects will be empty.
Namespace string `json:"namespace,omitempty"`
// SelfLink is a URL representing this object.
SelfLink string `json:"selfLink,omitempty"`
// UID is the unique in time and space value for this object. It is typically generated by
// the server on successful creation of a resource and is not allowed to change on PUT
// operations.
UID UID `json:"uid,omitempty"`
// An opaque value that represents the version of this resource. May be used for optimistic
// concurrency, change detection, and the watch operation on a resource or set of resources.
// Clients must treat these values as opaque and values may only be valid for a particular
// resource or set of resources. Only servers will generate resource versions.
ResourceVersion string `json:"resourceVersion,omitempty"`
// A sequence number representing a specific generation of the desired state.
// Populated by the system. Read-only.
Generation int64 `json:"generation,omitempty"`
// CreationTimestamp is a timestamp representing the server time when this object was
// created. It is not guaranteed to be set in happens-before order across separate operations.
// Clients may not set this value. It is represented in RFC3339 form and is in UTC.
CreationTimestamp Time `json:"creationTimestamp,omitempty"`
// DeletionTimestamp is the time after which this resource will be deleted. This
// field is set by the server when a graceful deletion is requested by the user, and is not
// directly settable by a client. The resource will be deleted (no longer visible from
// resource lists, and not reachable by name) after the time in this field. Once set, this
// value may not be unset or be set further into the future, although it may be shortened
// or the resource may be deleted prior to this time. For example, a user may request that
// a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination
// signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet
// will send a hard termination signal to the container.
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"`
// DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion
// was requested. Represents the most recent grace period, and may only be shortened once set.
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"`
// Labels are key value pairs that may be used to scope and select individual resources.
// Label keys are of the form:
// label-key ::= prefixed-name | name
// prefixed-name ::= prefix '/' name
// prefix ::= DNS_SUBDOMAIN
// name ::= DNS_LABEL
// The prefix is optional. If the prefix is not specified, the key is assumed to be private
// to the user. Other system components that wish to use labels must specify a prefix. The
// "kubernetes.io/" prefix is reserved for use by kubernetes components.
// TODO: replace map[string]string with labels.LabelSet type
Labels map[string]string `json:"labels,omitempty"`
// Annotations are unstructured key value data stored with a resource that may be set by
// external tooling. They are not queryable and should be preserved when modifying
// objects. Annotation keys have the same formatting restrictions as Label keys. See the
// comments on Labels for details.
Annotations map[string]string `json:"annotations,omitempty"`
}
// UID is a type that holds unique ID values, including UUIDs. Because we
// don't ONLY use UUIDs, this is an alias to string. Being a type captures
// intent and helps make sure that UIDs and names do not get conflated.
type UID string
// Time is a wrapper around time.Time which supports correct
// marshaling to YAML and JSON. Wrappers are provided for many
// of the factory methods that the time package offers.
//
// +protobuf.options.marshal=false
// +protobuf.as=Timestamp
type Time struct {
time.Time `protobuf:"-"`
}
// Service is a named abstraction of software service (for example, mysql) consisting of local port
// (for example 3306) that the proxy listens on, and the selector that determines which pods
// will answer requests sent through the proxy.
type Service struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
// Spec defines the behavior of a service.
Spec ServiceSpec `json:"spec,omitempty"`
// Status represents the current status of a service.
Status ServiceStatus `json:"status,omitempty"`
}
// ServiceSpec describes the attributes that a user creates on a service
type ServiceSpec struct {
// Type determines how the service will be exposed. Valid options: ClusterIP, NodePort, LoadBalancer
Type ServiceType `json:"type,omitempty"`
// Required: The list of ports that are exposed by this service.
Ports []ServicePort `json:"ports"`
// This service will route traffic to pods having labels matching this selector. If empty or not present,
// the service is assumed to have endpoints set by an external process and Kubernetes will not modify
// those endpoints.
Selector map[string]string `json:"selector"`
// ClusterIP is usually assigned by the master. If specified by the user
// we will try to respect it or else fail the request. This field can
// not be changed by updates.
// Valid values are None, empty string (""), or a valid IP address
// None can be specified for headless services when proxying is not required
ClusterIP string `json:"clusterIP,omitempty"`
// ExternalIPs are used by external load balancers, or can be set by
// users to handle external traffic that arrives at a node.
ExternalIPs []string `json:"externalIPs,omitempty"`
// Only applies to Service Type: LoadBalancer
// LoadBalancer will get created with the IP specified in this field.
// This feature depends on whether the underlying cloud-provider supports specifying
// the loadBalancerIP when a load balancer is created.
// This field will be ignored if the cloud-provider does not support the feature.
LoadBalancerIP string `json:"loadBalancerIP,omitempty"`
// Required: Supports "ClientIP" and "None". Used to maintain session affinity.
SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"`
}
// ServicePort service port
type ServicePort struct {
// Optional if only one ServicePort is defined on this service: The
// name of this port within the service. This must be a DNS_LABEL.
// All ports within a ServiceSpec must have unique names. This maps to
// the 'Name' field in EndpointPort objects.
Name string `json:"name"`
// The IP protocol for this port. Supports "TCP" and "UDP".
Protocol Protocol `json:"protocol"`
// The port that will be exposed on the service.
Port int `json:"port"`
// Optional: The target port on pods selected by this service. If this
// is a string, it will be looked up as a named port in the target
// Pod's container ports. If this is not specified, the value
// of the 'port' field is used (an identity map).
// This field is ignored for services with clusterIP=None, and should be
// omitted or set equal to the 'port' field.
TargetPort IntOrString `json:"targetPort"`
// The port on each node on which this service is exposed.
// Default is to auto-allocate a port if the ServiceType of this Service requires one.
NodePort int `json:"nodePort"`
}
// ServiceStatus represents the current status of a service
type ServiceStatus struct {
// LoadBalancer contains the current status of the load-balancer,
// if one is present.
LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"`
}
// LoadBalancerStatus represents the status of a load-balancer
type LoadBalancerStatus struct {
// Ingress is a list containing ingress points for the load-balancer;
// traffic intended for the service should be sent to these ingress points.
Ingress []LoadBalancerIngress `json:"ingress,omitempty"`
}
// LoadBalancerIngress represents the status of a load-balancer ingress point:
// traffic intended for the service should be sent to an ingress point.
type LoadBalancerIngress struct {
// IP is set for load-balancer ingress points that are IP based
// (typically GCE or OpenStack load-balancers)
IP string `json:"ip,omitempty"`
// Hostname is set for load-balancer ingress points that are DNS based
// (typically AWS load-balancers)
Hostname string `json:"hostname,omitempty"`
}
// ServiceAffinity Session Affinity Type string
type ServiceAffinity string
// ServiceType Service Type string describes ingress methods for a service
type ServiceType string
// Protocol defines network protocols supported for things like container ports.
type Protocol string
// IntOrString is a type that can hold an int32 or a string. When used in
// JSON or YAML marshalling and unmarshalling, it produces or consumes the
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
// TODO: Rename to Int32OrString
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type IntOrString struct {
Type Type
IntVal int32
StrVal string
}
// FromInt creates an IntOrString object with an int32 value. It is
// your responsibility not to call this method with a value greater
// than int32.
// TODO: convert to (val int32)
func FromInt(val int) IntOrString {
return IntOrString{Type: Int, IntVal: int32(val)}
}
// FromString creates an IntOrString object with a string value.
func FromString(val string) IntOrString {
return IntOrString{Type: String, StrVal: val}
}
// String returns the string value, or the Itoa of the int value.
func (intstr *IntOrString) String() string {
if intstr.Type == String {
return intstr.StrVal
}
return strconv.Itoa(intstr.IntValue())
}
// IntValue returns the IntVal if type Int, or if
// it is a String, will attempt a conversion to int.
func (intstr *IntOrString) IntValue() int {
if intstr.Type == String {
i, _ := strconv.Atoi(intstr.StrVal)
return i
}
return int(intstr.IntVal)
}
// UnmarshalJSON implements the json.Unmarshaller interface.
func (intstr *IntOrString) UnmarshalJSON(value []byte) error {
if value[0] == '"' {
intstr.Type = String
return json.Unmarshal(value, &intstr.StrVal)
}
intstr.Type = Int
return json.Unmarshal(value, &intstr.IntVal)
}
// Type represents the stored type of IntOrString.
type Type int
const (
// Int int
Int Type = iota // The IntOrString holds an int.
//String string
String // The IntOrString holds a string.
)
// ServiceList holds a list of services.
type ServiceList struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
Items []Service `json:"items"`
}
// ListMeta describes metadata that synthetic resources must have, including lists and
// various status objects. A resource may have only one of {ObjectMeta, ListMeta}.
type ListMeta struct {
// SelfLink is a URL representing this object.
// Populated by the system.
// Read-only.
SelfLink string `json:"selfLink,omitempty"`
// String that identifies the server's internal version of this object that
// can be used by clients to determine when objects have changed.
// Value must be treated as opaque by clients and passed unmodified back to the server.
// Populated by the system.
// Read-only.
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#concurrency-control-and-consistency
ResourceVersion string `json:"resourceVersion,omitempty"`
}

View file

@ -1,55 +1,24 @@
package provider package provider
import ( import (
"fmt"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"io/ioutil"
"os"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"text/template" "text/template"
"time" "time"
"k8s.io/client-go/1.5/pkg/api/v1"
"k8s.io/client-go/1.5/pkg/util/intstr"
"github.com/containous/traefik/log"
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/safe"
"github.com/containous/traefik/types"
"github.com/cenk/backoff" "github.com/cenk/backoff"
"github.com/containous/traefik/job" "github.com/containous/traefik/job"
) )
const (
serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token"
serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
defaultKubeEndpoint = "http://127.0.0.1:8080"
)
// Namespaces holds kubernetes namespaces
type Namespaces []string
//Set adds strings elem into the the parser
//it splits str on , and ;
func (ns *Namespaces) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
*ns = append(*ns, slice...)
return nil
}
//Get []string
func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) }
//String return slice in a string
func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) }
//SetValue sets []string into the parser
func (ns *Namespaces) SetValue(val interface{}) {
*ns = Namespaces(val.(Namespaces))
}
var _ Provider = (*Kubernetes)(nil) var _ Provider = (*Kubernetes)(nil)
// Kubernetes holds configurations of the Kubernetes provider. // Kubernetes holds configurations of the Kubernetes provider.
@ -57,45 +26,24 @@ type Kubernetes struct {
BaseProvider `mapstructure:",squash"` BaseProvider `mapstructure:",squash"`
Endpoint string `description:"Kubernetes server endpoint"` Endpoint string `description:"Kubernetes server endpoint"`
DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"` DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"`
Namespaces Namespaces `description:"Kubernetes namespaces"` Namespaces k8s.Namespaces `description:"Kubernetes namespaces"`
LabelSelector string `description:"Kubernetes api label selector to use"` LabelSelector string `description:"Kubernetes api label selector to use"`
lastConfiguration safe.Safe lastConfiguration safe.Safe
} }
func (provider *Kubernetes) createClient() (k8s.Client, error) { func (provider *Kubernetes) newK8sClient() (k8s.Client, error) {
var token string if provider.Endpoint != "" {
tokenBytes, err := ioutil.ReadFile(serviceAccountToken) log.Infof("Creating in cluster Kubernetes client with endpoint %", provider.Endpoint)
if err == nil { return k8s.NewInClusterClientWithEndpoint(provider.Endpoint)
token = string(tokenBytes)
log.Debugf("Kubernetes token: %s", token)
} else {
log.Errorf("Kubernetes load token error: %s", err)
} }
caCert, err := ioutil.ReadFile(serviceAccountCACert) log.Info("Creating in cluster Kubernetes client")
if err == nil { return k8s.NewInClusterClient()
log.Debugf("Kubernetes CA cert: %s", serviceAccountCACert)
} else {
log.Errorf("Kubernetes load token error: %s", err)
}
kubernetesHost := os.Getenv("KUBERNETES_SERVICE_HOST")
kubernetesPort := os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS")
// Prioritize user provided kubernetes endpoint since kube container runtime will almost always have it
if provider.Endpoint == "" && len(kubernetesPort) > 0 && len(kubernetesHost) > 0 {
log.Debugf("Using environment provided kubernetes endpoint")
provider.Endpoint = "https://" + kubernetesHost + ":" + kubernetesPort
}
if provider.Endpoint == "" {
log.Debugf("Using default kubernetes api endpoint")
provider.Endpoint = defaultKubeEndpoint
}
log.Debugf("Kubernetes endpoint: %s", provider.Endpoint)
return k8s.NewClient(provider.Endpoint, caCert, token)
} }
// Provide allows the provider to provide configurations to traefik // Provide allows the provider to provide configurations to traefik
// using the given configuration channel. // using the given configuration channel.
func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error {
k8sClient, err := provider.createClient() k8sClient, err := provider.newK8sClient()
if err != nil { if err != nil {
return err return err
} }
@ -107,7 +55,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
stopWatch := make(chan bool, 5) stopWatch := make(chan bool, 5)
defer close(stopWatch) defer close(stopWatch)
log.Debugf("Using label selector: '%s'", provider.LabelSelector) log.Debugf("Using label selector: '%s'", provider.LabelSelector)
eventsChan, errEventsChan, err := k8sClient.WatchAll(provider.LabelSelector, stopWatch) eventsChan, err := k8sClient.WatchAll(provider.LabelSelector, stopWatch)
if err != nil { if err != nil {
log.Errorf("Error watching kubernetes events: %v", err) log.Errorf("Error watching kubernetes events: %v", err)
timer := time.NewTimer(1 * time.Second) timer := time.NewTimer(1 * time.Second)
@ -123,9 +71,6 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
case <-stop: case <-stop:
stopWatch <- true stopWatch <- true
return nil return nil
case err, _ := <-errEventsChan:
stopWatch <- true
return err
case event := <-eventsChan: case event := <-eventsChan:
log.Debugf("Received event from kubernetes %+v", event) log.Debugf("Received event from kubernetes %+v", event)
templateObjects, err := provider.loadIngresses(k8sClient) templateObjects, err := provider.loadIngresses(k8sClient)
@ -133,7 +78,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
return err return err
} }
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) { if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
log.Debugf("Skipping event from kubernetes %+v", event) log.Debug("Skipping event")
} else { } else {
provider.lastConfiguration.Set(templateObjects) provider.lastConfiguration.Set(templateObjects)
configurationChan <- types.ConfigMessage{ configurationChan <- types.ConfigMessage{
@ -155,39 +100,12 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
} }
}) })
templateObjects, err := provider.loadIngresses(k8sClient)
if err != nil {
return err
}
if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) {
log.Debugf("Skipping configuration from kubernetes %+v", templateObjects)
} else {
provider.lastConfiguration.Set(templateObjects)
configurationChan <- types.ConfigMessage{
ProviderName: "kubernetes",
Configuration: provider.loadConfig(*templateObjects),
}
}
return nil return nil
} }
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) { func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
ingresses, err := k8sClient.GetIngresses(provider.LabelSelector, func(ingress k8s.Ingress) bool { ingresses := k8sClient.GetIngresses(provider.Namespaces)
if len(provider.Namespaces) == 0 {
return true
}
for _, n := range provider.Namespaces {
if ingress.ObjectMeta.Namespace == n {
return true
}
}
return false
})
if err != nil {
log.Errorf("Error retrieving ingresses: %+v", err)
return nil, err
}
templateObjects := types.Configuration{ templateObjects := types.Configuration{
map[string]*types.Backend{}, map[string]*types.Backend{},
map[string]*types.Frontend{}, map[string]*types.Frontend{},
@ -239,28 +157,28 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
Rule: ruleType + ":" + pa.Path, Rule: ruleType + ":" + pa.Path,
} }
} }
service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace) service, exists, err := k8sClient.GetService(i.ObjectMeta.Namespace, pa.Backend.ServiceName)
if err != nil { if err != nil || !exists {
log.Warnf("Error retrieving services: %v", err) log.Warnf("Error retrieving service %s/%s: %v", i.ObjectMeta.Namespace, pa.Backend.ServiceName, err)
delete(templateObjects.Frontends, r.Host+pa.Path) delete(templateObjects.Frontends, r.Host+pa.Path)
log.Warnf("Error retrieving services %s", pa.Backend.ServiceName)
continue continue
} }
protocol := "http" protocol := "http"
for _, port := range service.Spec.Ports { for _, port := range service.Spec.Ports {
if equalPorts(port, pa.Backend.ServicePort) { if equalPorts(port, pa.Backend.ServicePort) {
if port.Port == 443 { if port.Port == 443 {
protocol = "https" protocol = "https"
} }
endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace) endpoints, exists, err := k8sClient.GetEndpoints(service.ObjectMeta.Namespace, service.ObjectMeta.Name)
if err != nil { if err != nil || !exists {
log.Errorf("Error retrieving endpoints: %v", err) log.Errorf("Error retrieving endpoints %s/%s: %v", service.ObjectMeta.Namespace, service.ObjectMeta.Name, err)
continue continue
} }
if len(endpoints.Subsets) == 0 { if len(endpoints.Subsets) == 0 {
log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name) log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name)
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port), URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)),
Weight: 1, Weight: 1,
} }
} else { } else {
@ -287,7 +205,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur
return &templateObjects, nil return &templateObjects, nil
} }
func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int { func endpointPortNumber(servicePort v1.ServicePort, endpointPorts []v1.EndpointPort) int {
if len(endpointPorts) > 0 { if len(endpointPorts) > 0 {
//name is optional if there is only one port //name is optional if there is only one port
port := endpointPorts[0] port := endpointPorts[0]
@ -298,11 +216,11 @@ func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.Endpoin
} }
return int(port.Port) return int(port.Port)
} }
return servicePort.Port return int(servicePort.Port)
} }
func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool { func equalPorts(servicePort v1.ServicePort, ingressPort intstr.IntOrString) bool {
if servicePort.Port == ingressPort.IntValue() { if int(servicePort.Port) == ingressPort.IntValue() {
return true return true
} }
if servicePort.Name != "" && servicePort.Name == ingressPort.String() { if servicePort.Name != "" && servicePort.Name == ingressPort.String() {

View file

@ -2,29 +2,34 @@ package provider
import ( import (
"encoding/json" "encoding/json"
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/types"
"reflect" "reflect"
"testing" "testing"
"k8s.io/client-go/1.5/pkg/api/v1"
"k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1"
"k8s.io/client-go/1.5/pkg/util/intstr"
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/types"
) )
func TestLoadIngresses(t *testing.T) { func TestLoadIngresses(t *testing.T) {
ingresses := []k8s.Ingress{{ ingresses := []*v1beta1.Ingress{{
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "testing", Namespace: "testing",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo", Host: "foo",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar", Path: "/bar",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(80), ServicePort: intstr.FromInt(80),
}, },
}, },
}, },
@ -33,19 +38,19 @@ func TestLoadIngresses(t *testing.T) {
}, },
{ {
Host: "bar", Host: "bar",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service3", ServiceName: "service3",
ServicePort: k8s.FromString("https"), ServicePort: intstr.FromString("https"),
}, },
}, },
{ {
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service2", ServiceName: "service2",
ServicePort: k8s.FromInt(802), ServicePort: intstr.FromInt(802),
}, },
}, },
}, },
@ -55,16 +60,16 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
}} }}
services := []k8s.Service{ services := []*v1.Service{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service1", Name: "service1",
UID: "1", UID: "1",
Namespace: "testing", Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Port: 80, Port: 80,
}, },
@ -72,14 +77,14 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service2", Name: "service2",
UID: "2", UID: "2",
Namespace: "testing", Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.2", ClusterIP: "10.0.0.2",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Port: 802, Port: 802,
}, },
@ -87,14 +92,14 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service3", Name: "service3",
UID: "3", UID: "3",
Namespace: "testing", Namespace: "testing",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.3", ClusterIP: "10.0.0.3",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 80, Port: 80,
@ -107,33 +112,33 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
} }
endpoints := []k8s.Endpoints{ endpoints := []*v1.Endpoints{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service1", Name: "service1",
UID: "1", UID: "1",
Namespace: "testing", Namespace: "testing",
}, },
Subsets: []k8s.EndpointSubset{ Subsets: []v1.EndpointSubset{
{ {
Addresses: []k8s.EndpointAddress{ Addresses: []v1.EndpointAddress{
{ {
IP: "10.10.0.1", IP: "10.10.0.1",
}, },
}, },
Ports: []k8s.EndpointPort{ Ports: []v1.EndpointPort{
{ {
Port: 8080, Port: 8080,
}, },
}, },
}, },
{ {
Addresses: []k8s.EndpointAddress{ Addresses: []v1.EndpointAddress{
{ {
IP: "10.21.0.1", IP: "10.21.0.1",
}, },
}, },
Ports: []k8s.EndpointPort{ Ports: []v1.EndpointPort{
{ {
Port: 8080, Port: 8080,
}, },
@ -142,19 +147,19 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service3", Name: "service3",
UID: "3", UID: "3",
Namespace: "testing", Namespace: "testing",
}, },
Subsets: []k8s.EndpointSubset{ Subsets: []v1.EndpointSubset{
{ {
Addresses: []k8s.EndpointAddress{ Addresses: []v1.EndpointAddress{
{ {
IP: "10.15.0.1", IP: "10.15.0.1",
}, },
}, },
Ports: []k8s.EndpointPort{ Ports: []v1.EndpointPort{
{ {
Name: "http", Name: "http",
Port: 8080, Port: 8080,
@ -166,12 +171,12 @@ func TestLoadIngresses(t *testing.T) {
}, },
}, },
{ {
Addresses: []k8s.EndpointAddress{ Addresses: []v1.EndpointAddress{
{ {
IP: "10.15.0.2", IP: "10.15.0.2",
}, },
}, },
Ports: []k8s.EndpointPort{ Ports: []v1.EndpointPort{
{ {
Name: "http", Name: "http",
Port: 9080, Port: 9080,
@ -267,23 +272,23 @@ func TestLoadIngresses(t *testing.T) {
} }
func TestRuleType(t *testing.T) { func TestRuleType(t *testing.T) {
ingresses := []k8s.Ingress{ ingresses := []*v1beta1.Ingress{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefixStrip"}, //camel case Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefixStrip"}, //camel case
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo1", Host: "foo1",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar1", Path: "/bar1",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -294,21 +299,21 @@ func TestRuleType(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Annotations: map[string]string{"traefik.frontend.rule.type": "path"}, //lower case Annotations: map[string]string{"traefik.frontend.rule.type": "path"}, //lower case
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo1", Host: "foo1",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar2", Path: "/bar2",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -319,21 +324,21 @@ func TestRuleType(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefix"}, //path prefix Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefix"}, //path prefix
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo2", Host: "foo2",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar1", Path: "/bar1",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -344,21 +349,21 @@ func TestRuleType(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Annotations: map[string]string{"traefik.frontend.rule.type": "PathStrip"}, //path strip Annotations: map[string]string{"traefik.frontend.rule.type": "PathStrip"}, //path strip
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo2", Host: "foo2",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar2", Path: "/bar2",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -369,21 +374,21 @@ func TestRuleType(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Annotations: map[string]string{"traefik.frontend.rule.type": "PathXXStrip"}, //wrong rule Annotations: map[string]string{"traefik.frontend.rule.type": "PathXXStrip"}, //wrong rule
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo1", Host: "foo1",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar3", Path: "/bar3",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -394,15 +399,15 @@ func TestRuleType(t *testing.T) {
}, },
}, },
} }
services := []k8s.Service{ services := []*v1.Service{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service1", Name: "service1",
UID: "1", UID: "1",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 801, Port: 801,
@ -495,22 +500,22 @@ func TestRuleType(t *testing.T) {
} }
func TestGetPassHostHeader(t *testing.T) { func TestGetPassHostHeader(t *testing.T) {
ingresses := []k8s.Ingress{{ ingresses := []*v1beta1.Ingress{{
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "awesome", Namespace: "awesome",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo", Host: "foo",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar", Path: "/bar",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -520,16 +525,16 @@ func TestGetPassHostHeader(t *testing.T) {
}, },
}, },
}} }}
services := []k8s.Service{ services := []*v1.Service{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service1", Name: "service1",
Namespace: "awesome", Namespace: "awesome",
UID: "1", UID: "1",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 801, Port: 801,
@ -587,22 +592,22 @@ func TestGetPassHostHeader(t *testing.T) {
} }
func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) { func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
ingresses := []k8s.Ingress{ ingresses := []*v1beta1.Ingress{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "awesome", Namespace: "awesome",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo", Host: "foo",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service", ServiceName: "service",
ServicePort: k8s.FromInt(80), ServicePort: intstr.FromInt(80),
}, },
}, },
}, },
@ -613,16 +618,16 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
}, },
}, },
} }
services := []k8s.Service{ services := []*v1.Service{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service", Name: "service",
UID: "1", UID: "1",
Namespace: "awesome", Namespace: "awesome",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 80, Port: 80,
@ -631,14 +636,14 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service", Name: "service",
UID: "2", UID: "2",
Namespace: "not-awesome", Namespace: "not-awesome",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.2", ClusterIP: "10.0.0.2",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 80, Port: 80,
@ -693,23 +698,23 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) {
} }
func TestLoadNamespacedIngresses(t *testing.T) { func TestLoadNamespacedIngresses(t *testing.T) {
ingresses := []k8s.Ingress{ ingresses := []*v1beta1.Ingress{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "awesome", Namespace: "awesome",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo", Host: "foo",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar", Path: "/bar",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -718,19 +723,19 @@ func TestLoadNamespacedIngresses(t *testing.T) {
}, },
{ {
Host: "bar", Host: "bar",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service3", ServiceName: "service3",
ServicePort: k8s.FromInt(443), ServicePort: intstr.FromInt(443),
}, },
}, },
{ {
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service2", ServiceName: "service2",
ServicePort: k8s.FromInt(802), ServicePort: intstr.FromInt(802),
}, },
}, },
}, },
@ -741,21 +746,21 @@ func TestLoadNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "not-awesome", Namespace: "not-awesome",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "baz", Host: "baz",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/baz", Path: "/baz",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -766,16 +771,16 @@ func TestLoadNamespacedIngresses(t *testing.T) {
}, },
}, },
} }
services := []k8s.Service{ services := []*v1.Service{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "awesome", Namespace: "awesome",
Name: "service1", Name: "service1",
UID: "1", UID: "1",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 801, Port: 801,
@ -784,14 +789,14 @@ func TestLoadNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service1", Name: "service1",
Namespace: "not-awesome", Namespace: "not-awesome",
UID: "1", UID: "1",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 801, Port: 801,
@ -800,14 +805,14 @@ func TestLoadNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service2", Name: "service2",
Namespace: "awesome", Namespace: "awesome",
UID: "2", UID: "2",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.2", ClusterIP: "10.0.0.2",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Port: 802, Port: 802,
}, },
@ -815,14 +820,14 @@ func TestLoadNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service3", Name: "service3",
Namespace: "awesome", Namespace: "awesome",
UID: "3", UID: "3",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.3", ClusterIP: "10.0.0.3",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 443, Port: 443,
@ -906,23 +911,23 @@ func TestLoadNamespacedIngresses(t *testing.T) {
} }
func TestLoadMultipleNamespacedIngresses(t *testing.T) { func TestLoadMultipleNamespacedIngresses(t *testing.T) {
ingresses := []k8s.Ingress{ ingresses := []*v1beta1.Ingress{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "awesome", Namespace: "awesome",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "foo", Host: "foo",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar", Path: "/bar",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -931,19 +936,19 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
}, },
{ {
Host: "bar", Host: "bar",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service3", ServiceName: "service3",
ServicePort: k8s.FromInt(443), ServicePort: intstr.FromInt(443),
}, },
}, },
{ {
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service2", ServiceName: "service2",
ServicePort: k8s.FromInt(802), ServicePort: intstr.FromInt(802),
}, },
}, },
}, },
@ -954,21 +959,21 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "somewhat-awesome", Namespace: "somewhat-awesome",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "awesome", Host: "awesome",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/quix", Path: "/quix",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -979,21 +984,21 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "not-awesome", Namespace: "not-awesome",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
Host: "baz", Host: "baz",
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/baz", Path: "/baz",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -1004,16 +1009,16 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
}, },
}, },
} }
services := []k8s.Service{ services := []*v1.Service{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service1", Name: "service1",
Namespace: "awesome", Namespace: "awesome",
UID: "1", UID: "1",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 801, Port: 801,
@ -1022,14 +1027,14 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "somewhat-awesome", Namespace: "somewhat-awesome",
Name: "service1", Name: "service1",
UID: "17", UID: "17",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.4", ClusterIP: "10.0.0.4",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 801, Port: 801,
@ -1038,14 +1043,14 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "awesome", Namespace: "awesome",
Name: "service2", Name: "service2",
UID: "2", UID: "2",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.2", ClusterIP: "10.0.0.2",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Port: 802, Port: 802,
}, },
@ -1053,14 +1058,14 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
}, },
}, },
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "awesome", Namespace: "awesome",
Name: "service3", Name: "service3",
UID: "3", UID: "3",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.3", ClusterIP: "10.0.0.3",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 443, Port: 443,
@ -1167,21 +1172,21 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) {
} }
func TestHostlessIngress(t *testing.T) { func TestHostlessIngress(t *testing.T) {
ingresses := []k8s.Ingress{{ ingresses := []*v1beta1.Ingress{{
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Namespace: "awesome", Namespace: "awesome",
}, },
Spec: k8s.IngressSpec{ Spec: v1beta1.IngressSpec{
Rules: []k8s.IngressRule{ Rules: []v1beta1.IngressRule{
{ {
IngressRuleValue: k8s.IngressRuleValue{ IngressRuleValue: v1beta1.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{ HTTP: &v1beta1.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{ Paths: []v1beta1.HTTPIngressPath{
{ {
Path: "/bar", Path: "/bar",
Backend: k8s.IngressBackend{ Backend: v1beta1.IngressBackend{
ServiceName: "service1", ServiceName: "service1",
ServicePort: k8s.FromInt(801), ServicePort: intstr.FromInt(801),
}, },
}, },
}, },
@ -1191,16 +1196,16 @@ func TestHostlessIngress(t *testing.T) {
}, },
}, },
}} }}
services := []k8s.Service{ services := []*v1.Service{
{ {
ObjectMeta: k8s.ObjectMeta{ ObjectMeta: v1.ObjectMeta{
Name: "service1", Name: "service1",
Namespace: "awesome", Namespace: "awesome",
UID: "1", UID: "1",
}, },
Spec: k8s.ServiceSpec{ Spec: v1.ServiceSpec{
ClusterIP: "10.0.0.1", ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{ Ports: []v1.ServicePort{
{ {
Name: "http", Name: "http",
Port: 801, Port: 801,
@ -1255,42 +1260,45 @@ func TestHostlessIngress(t *testing.T) {
} }
type clientMock struct { type clientMock struct {
ingresses []k8s.Ingress ingresses []*v1beta1.Ingress
services []k8s.Service services []*v1.Service
endpoints []k8s.Endpoints endpoints []*v1.Endpoints
watchChan chan interface{} watchChan chan interface{}
} }
func (c clientMock) GetIngresses(labelString string, predicate func(k8s.Ingress) bool) ([]k8s.Ingress, error) { func (c clientMock) GetIngresses(namespaces k8s.Namespaces) []*v1beta1.Ingress {
var ingresses []k8s.Ingress result := make([]*v1beta1.Ingress, 0, len(c.ingresses))
for _, ingress := range c.ingresses { for _, ingress := range c.ingresses {
if predicate(ingress) { if k8s.HasNamespace(ingress, namespaces) {
ingresses = append(ingresses, ingress) result = append(result, ingress)
} }
} }
return ingresses, nil return result
} }
func (c clientMock) WatchIngresses(labelString string, predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
return c.watchChan, make(chan error), nil func (c clientMock) WatchIngresses(labelSelector string, stopCh <-chan struct{}) chan interface{} {
return c.watchChan
} }
func (c clientMock) GetService(name, namespace string) (k8s.Service, error) {
func (c clientMock) GetService(namespace, name string) (*v1.Service, bool, error) {
for _, service := range c.services { for _, service := range c.services {
if service.Namespace == namespace && service.Name == name { if service.Namespace == namespace && service.Name == name {
return service, nil return service, true, nil
} }
} }
return k8s.Service{}, nil return &v1.Service{}, true, nil
} }
func (c clientMock) GetEndpoints(name, namespace string) (k8s.Endpoints, error) { func (c clientMock) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) {
for _, endpoints := range c.endpoints { for _, endpoints := range c.endpoints {
if endpoints.Namespace == namespace && endpoints.Name == name { if endpoints.Namespace == namespace && endpoints.Name == name {
return endpoints, nil return endpoints, true, nil
} }
} }
return k8s.Endpoints{}, nil return &v1.Endpoints{}, true, nil
} }
func (c clientMock) WatchAll(labelString string, stopCh <-chan bool) (chan interface{}, chan error, error) { func (c clientMock) WatchAll(labelString string, stopCh <-chan bool) (chan interface{}, error) {
return c.watchChan, make(chan error), nil return c.watchChan, nil
} }

View file

@ -1,4 +1,10 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Fix for http://stackoverflow.com/questions/37284423/glog-flag-redefined-error
# go test github.com/containous/traefik/provider
#*/github.com/containous/traefik/provider/_test/provider.test flag redefined: log_dir
#panic: */github.com/containous/traefik/provider/_test/provider.test flag redefined: log_dir
rm -rf vendor/k8s.io/client-go/1.5/vendor/github.com/golang/glog/
go generate go generate

View file

@ -19,7 +19,7 @@ import (
"github.com/containous/traefik/cluster" "github.com/containous/traefik/cluster"
"github.com/containous/traefik/log" "github.com/containous/traefik/log"
"github.com/containous/traefik/middlewares" "github.com/containous/traefik/middlewares"
"github.com/containous/traefik/provider" "github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/types" "github.com/containous/traefik/types"
"github.com/containous/traefik/version" "github.com/containous/traefik/version"
"github.com/docker/libkv/store" "github.com/docker/libkv/store"
@ -144,7 +144,7 @@ Complete documentation is available at https://traefik.io`,
f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{}) f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{})
f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{}) f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{})
f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{}) f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{})
f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{}) f.AddParser(reflect.TypeOf(k8s.Namespaces{}), &k8s.Namespaces{})
f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{}) f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{})
//add commands //add commands