2016-06-06 12:49:53 -07:00
// Package kubernetes provides the kubernetes backend.
package kubernetes
import (
2016-07-07 01:40:58 -07:00
"errors"
2016-10-30 15:54:16 +00:00
"fmt"
2016-07-18 10:47:36 -07:00
"log"
2017-02-01 12:56:10 -05:00
"net"
2016-09-23 09:48:11 -03:00
"strings"
2016-06-06 12:49:53 -07:00
"time"
2017-02-21 22:51:47 -08:00
"github.com/coredns/coredns/middleware"
"github.com/coredns/coredns/middleware/etcd/msg"
"github.com/coredns/coredns/middleware/pkg/dnsutil"
dnsstrings "github.com/coredns/coredns/middleware/pkg/strings"
"github.com/coredns/coredns/middleware/proxy"
"github.com/coredns/coredns/request"
2016-06-06 12:49:53 -07:00
2016-07-07 01:40:58 -07:00
"github.com/miekg/dns"
2016-11-05 15:43:27 +00:00
"k8s.io/client-go/1.5/kubernetes"
2016-11-05 07:57:08 -04:00
"k8s.io/client-go/1.5/pkg/api"
unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned"
2016-11-05 15:43:27 +00:00
"k8s.io/client-go/1.5/pkg/labels"
2016-11-05 07:57:08 -04:00
"k8s.io/client-go/1.5/rest"
"k8s.io/client-go/1.5/tools/clientcmd"
clientcmdapi "k8s.io/client-go/1.5/tools/clientcmd/api"
2016-08-05 18:19:51 -07:00
)
2016-09-23 09:14:12 +01:00
// Kubernetes implements a middleware that connects to a Kubernetes cluster.
2016-06-06 12:49:53 -07:00
type Kubernetes struct {
2017-05-22 16:05:48 -04:00
Next middleware . Handler
Zones [ ] string
primaryZone int
Proxy proxy . Proxy // Proxy for looking up names during the resolution process
APIEndpoint string
APICertAuth string
APIClientCert string
APIClientKey string
APIConn dnsController
ResyncPeriod time . Duration
Namespaces [ ] string
2017-06-14 09:38:00 -04:00
Federations [ ] Federation
2017-05-22 16:05:48 -04:00
LabelSelector * unversionedapi . LabelSelector
Selector * labels . Selector
PodMode string
ReverseCidrs [ ] net . IPNet
Fallthrough bool
2017-06-02 02:25:33 -04:00
interfaceAddrs interfaceAddrser
2016-06-06 12:49:53 -07:00
}
2017-01-11 16:23:10 -05:00
const (
2017-01-29 12:06:26 -08:00
// PodModeDisabled is the default value where pod requests are ignored
PodModeDisabled = "disabled"
// PodModeVerified is where Pod requests are answered only if they exist
PodModeVerified = "verified"
// PodModeInsecure is where pod requests are answered without verfying they exist
PodModeInsecure = "insecure"
// DNSSchemaVersion is the schema version: https://github.com/kubernetes/dns/blob/master/docs/specification.md
2017-05-30 08:20:39 -04:00
DNSSchemaVersion = "1.0.1"
2017-01-11 16:23:10 -05:00
)
2017-01-05 10:09:59 -05:00
type endpoint struct {
addr api . EndpointAddress
port api . EndpointPort
}
type service struct {
name string
namespace string
addr string
ports [ ] api . ServicePort
endpoints [ ] endpoint
}
2017-01-11 16:23:10 -05:00
type pod struct {
name string
namespace string
addr string
}
2017-01-15 03:12:28 -05:00
type recordRequest struct {
2017-06-14 09:38:00 -04:00
port string
protocol string
endpoint string
service string
namespace string
typeName string
zone string
federation string
2017-01-15 03:12:28 -05:00
}
2017-06-14 09:38:00 -04:00
var localPodIP net . IP
2016-11-11 16:56:15 +00:00
var errNoItems = errors . New ( "no items found" )
var errNsNotExposed = errors . New ( "namespace is not exposed" )
2017-01-05 10:09:59 -05:00
var errInvalidRequest = errors . New ( "invalid query name" )
2017-05-22 16:05:48 -04:00
var errZoneNotFound = errors . New ( "zone not found" )
2017-05-25 12:08:34 -07:00
var errAPIBadPodType = errors . New ( "expected type *api.Pod" )
2017-05-22 16:05:48 -04:00
var errPodsDisabled = errors . New ( "pod records disabled" )
2016-11-10 16:24:06 -05:00
2016-10-30 15:54:16 +00:00
// Services implements the ServiceBackend interface.
2017-05-22 16:05:48 -04:00
func ( k * Kubernetes ) Services ( state request . Request , exact bool , opt middleware . Options ) ( svcs [ ] msg . Service , debug [ ] msg . Service , err error ) {
2017-01-15 03:12:28 -05:00
2017-05-22 16:05:48 -04:00
r , e := k . parseRequest ( state . Name ( ) , state . QType ( ) )
2017-01-15 03:12:28 -05:00
if e != nil {
return nil , nil , e
2017-01-05 10:09:59 -05:00
}
2017-01-15 14:37:18 +00:00
switch state . Type ( ) {
2017-05-30 08:20:39 -04:00
case "A" , "CNAME" :
2017-05-22 16:05:48 -04:00
if state . Type ( ) == "A" && isDefaultNS ( state . Name ( ) , r ) {
// If this is an A request for "ns.dns", respond with a "fake" record for coredns.
// SOA records always use this hardcoded name
svcs = append ( svcs , k . defaultNSMsg ( r ) )
return svcs , nil , nil
}
2017-01-15 14:37:18 +00:00
s , e := k . Records ( r )
return s , nil , e // Haven't implemented debug queries yet.
2017-05-30 08:20:39 -04:00
case "SRV" :
s , e := k . Records ( r )
// SRV for external services is not yet implemented, so remove those records
noext := [ ] msg . Service { }
for _ , svc := range s {
if t , _ := svc . HostType ( ) ; t != dns . TypeCNAME {
noext = append ( noext , svc )
}
}
return noext , nil , e
2017-01-15 14:37:18 +00:00
case "TXT" :
2017-05-22 16:05:48 -04:00
err := k . recordsForTXT ( r , & svcs )
return svcs , nil , err
case "NS" :
err = k . recordsForNS ( r , & svcs )
return svcs , nil , err
2017-01-15 14:37:18 +00:00
}
return nil , nil , nil
}
2017-05-22 16:05:48 -04:00
func ( k * Kubernetes ) recordsForTXT ( r recordRequest , svcs * [ ] msg . Service ) ( err error ) {
2017-01-15 14:37:18 +00:00
switch r . typeName {
case "dns-version" :
s := msg . Service {
2017-01-29 12:06:26 -08:00
Text : DNSSchemaVersion ,
2017-01-15 14:37:18 +00:00
TTL : 28800 ,
2017-05-22 16:05:48 -04:00
Key : msg . Path ( strings . Join ( [ ] string { r . typeName , r . zone } , "." ) , "coredns" ) }
* svcs = append ( * svcs , s )
return nil
2017-01-15 14:37:18 +00:00
}
2017-05-22 16:05:48 -04:00
return nil
2016-10-30 15:54:16 +00:00
}
2016-11-14 19:31:08 +00:00
// PrimaryZone will return the first non-reverse zone being handled by this middleware
2016-12-02 17:50:01 -05:00
func ( k * Kubernetes ) PrimaryZone ( ) string {
2016-11-14 19:31:08 +00:00
return k . Zones [ k . primaryZone ]
}
2016-11-05 15:43:27 +00:00
// Reverse implements the ServiceBackend interface.
func ( k * Kubernetes ) Reverse ( state request . Request , exact bool , opt middleware . Options ) ( [ ] msg . Service , [ ] msg . Service , error ) {
2017-05-22 16:05:48 -04:00
2016-11-05 15:43:27 +00:00
ip := dnsutil . ExtractAddressFromReverse ( state . Name ( ) )
if ip == "" {
return nil , nil , nil
}
records := k . getServiceRecordForIP ( ip , state . Name ( ) )
return records , nil , nil
}
2017-05-22 16:05:48 -04:00
func ( k * Kubernetes ) isRequestInReverseRange ( name string ) bool {
ip := dnsutil . ExtractAddressFromReverse ( name )
2017-02-01 12:56:10 -05:00
for _ , c := range k . ReverseCidrs {
if c . Contains ( net . ParseIP ( ip ) ) {
return true
}
}
return false
}
2016-10-30 15:54:16 +00:00
// Lookup implements the ServiceBackend interface.
func ( k * Kubernetes ) Lookup ( state request . Request , name string , typ uint16 ) ( * dns . Msg , error ) {
return k . Proxy . Lookup ( state , name , typ )
}
// IsNameError implements the ServiceBackend interface.
func ( k * Kubernetes ) IsNameError ( err error ) bool {
2017-01-05 10:09:59 -05:00
return err == errNoItems || err == errNsNotExposed || err == errInvalidRequest
2016-10-30 15:54:16 +00:00
}
// Debug implements the ServiceBackend interface.
2017-03-06 11:42:59 +00:00
func ( k * Kubernetes ) Debug ( ) string { return "debug" }
2016-10-30 15:54:16 +00:00
2016-11-05 07:57:08 -04:00
func ( k * Kubernetes ) getClientConfig ( ) ( * rest . Config , error ) {
2016-08-05 18:19:51 -07:00
// For a custom api server or running outside a k8s cluster
2016-08-08 14:30:04 -07:00
// set URL in env.KUBERNETES_MASTER or set endpoint in Corefile
2016-08-05 18:19:51 -07:00
loadingRules := clientcmd . NewDefaultClientConfigLoadingRules ( )
overrides := & clientcmd . ConfigOverrides { }
2016-09-23 18:07:06 -04:00
clusterinfo := clientcmdapi . Cluster { }
authinfo := clientcmdapi . AuthInfo { }
2016-08-22 23:15:21 -07:00
if len ( k . APIEndpoint ) > 0 {
2016-09-23 18:07:06 -04:00
clusterinfo . Server = k . APIEndpoint
2016-10-19 17:04:35 -04:00
} else {
2016-11-05 07:57:08 -04:00
cc , err := rest . InClusterConfig ( )
2016-10-19 17:04:35 -04:00
if err != nil {
return nil , err
}
return cc , err
2016-09-23 18:07:06 -04:00
}
if len ( k . APICertAuth ) > 0 {
clusterinfo . CertificateAuthority = k . APICertAuth
2016-08-05 18:19:51 -07:00
}
2016-09-23 18:07:06 -04:00
if len ( k . APIClientCert ) > 0 {
authinfo . ClientCertificate = k . APIClientCert
}
if len ( k . APIClientKey ) > 0 {
authinfo . ClientKey = k . APIClientKey
}
overrides . ClusterInfo = clusterinfo
overrides . AuthInfo = authinfo
2016-08-05 18:19:51 -07:00
clientConfig := clientcmd . NewNonInteractiveDeferredLoadingClientConfig ( loadingRules , overrides )
2016-09-23 18:07:06 -04:00
return clientConfig . ClientConfig ( )
}
// InitKubeCache initializes a new Kubernetes cache.
2017-05-22 16:05:48 -04:00
func ( k * Kubernetes ) InitKubeCache ( ) ( err error ) {
2016-09-23 18:07:06 -04:00
config , err := k . getClientConfig ( )
2016-08-05 18:19:51 -07:00
if err != nil {
return err
}
2016-11-05 07:57:08 -04:00
kubeClient , err := kubernetes . NewForConfig ( config )
2016-08-05 18:19:51 -07:00
if err != nil {
2016-10-30 15:54:16 +00:00
return fmt . Errorf ( "Failed to create kubernetes notification controller: %v" , err )
2016-08-05 18:19:51 -07:00
}
2016-10-30 15:54:16 +00:00
if k . LabelSelector != nil {
2016-08-19 17:14:17 -07:00
var selector labels . Selector
2016-08-22 23:15:21 -07:00
selector , err = unversionedapi . LabelSelectorAsSelector ( k . LabelSelector )
k . Selector = & selector
2016-08-19 17:14:17 -07:00
if err != nil {
2016-10-30 15:54:16 +00:00
return fmt . Errorf ( "Unable to create Selector for LabelSelector '%s'.Error was: %s" , k . LabelSelector , err )
2016-08-19 17:14:17 -07:00
}
2016-10-30 15:54:16 +00:00
}
2017-04-30 03:48:37 -04:00
if k . LabelSelector != nil {
2016-08-22 23:15:21 -07:00
log . Printf ( "[INFO] Kubernetes middleware configured with the label selector '%s'. Only kubernetes objects matching this label selector will be exposed." , unversionedapi . FormatLabelSelector ( k . LabelSelector ) )
2016-08-12 20:44:08 -07:00
}
2016-10-30 15:54:16 +00:00
2017-06-14 09:38:00 -04:00
opts := dnsControlOpts {
initPodCache : k . PodMode == PodModeVerified ,
}
k . APIConn = newdnsController ( kubeClient , k . ResyncPeriod , k . Selector , opts )
2016-08-22 23:15:21 -07:00
2016-08-05 18:19:51 -07:00
return err
}
2017-05-22 16:05:48 -04:00
func ( k * Kubernetes ) parseRequest ( lowerCasedName string , qtype uint16 ) ( r recordRequest , err error ) {
2017-01-15 03:12:28 -05:00
// 3 Possible cases
2017-06-14 09:38:00 -04:00
// SRV Request: _port._protocol.service.namespace.[federation.]type.zone
// A Request (endpoint): endpoint.service.namespace.[federation.]type.zone
// A Request (service): service.namespace.[federation.]type.zone
2017-01-15 03:12:28 -05:00
// separate zone from rest of lowerCasedName
var segs [ ] string
2016-08-22 23:15:21 -07:00
for _ , z := range k . Zones {
2017-01-15 03:12:28 -05:00
if dns . IsSubDomain ( z , lowerCasedName ) {
r . zone = z
2016-07-07 01:40:58 -07:00
2017-01-15 03:12:28 -05:00
segs = dns . SplitDomainName ( lowerCasedName )
segs = segs [ : len ( segs ) - dns . CountLabel ( r . zone ) ]
2016-07-07 01:40:58 -07:00
break
}
}
2017-01-15 03:12:28 -05:00
if r . zone == "" {
2017-05-22 16:05:48 -04:00
return r , errZoneNotFound
}
2017-06-14 09:38:00 -04:00
r . federation , segs = k . stripFederation ( segs )
2017-05-22 16:05:48 -04:00
if qtype == dns . TypeNS {
return r , nil
}
if qtype == dns . TypeA && isDefaultNS ( lowerCasedName , r ) {
return r , nil
2017-01-15 03:12:28 -05:00
}
2016-07-07 01:40:58 -07:00
2017-01-15 03:12:28 -05:00
offset := 0
2017-05-22 16:05:48 -04:00
if qtype == dns . TypeSRV {
2017-01-23 15:11:32 -05:00
if len ( segs ) != 5 {
return r , errInvalidRequest
}
2017-01-15 03:12:28 -05:00
// This is a SRV style request, get first two elements as port and
// protocol, stripping leading underscores if present.
if segs [ 0 ] [ 0 ] == '_' {
r . port = segs [ 0 ] [ 1 : ]
} else {
r . port = segs [ 0 ]
if ! symbolContainsWildcard ( r . port ) {
2017-01-23 15:11:32 -05:00
return r , errInvalidRequest
2017-01-15 03:12:28 -05:00
}
}
if segs [ 1 ] [ 0 ] == '_' {
r . protocol = segs [ 1 ] [ 1 : ]
if r . protocol != "tcp" && r . protocol != "udp" {
2017-01-23 15:11:32 -05:00
return r , errInvalidRequest
2017-01-15 03:12:28 -05:00
}
} else {
r . protocol = segs [ 1 ]
if ! symbolContainsWildcard ( r . protocol ) {
2017-01-23 15:11:32 -05:00
return r , errInvalidRequest
2017-01-15 03:12:28 -05:00
}
}
if r . port == "" || r . protocol == "" {
2017-01-23 15:11:32 -05:00
return r , errInvalidRequest
2017-01-15 03:12:28 -05:00
}
2017-01-23 15:11:32 -05:00
offset = 2
2017-01-05 10:09:59 -05:00
}
2017-05-22 16:05:48 -04:00
if qtype == dns . TypeA && len ( segs ) == 4 {
2017-01-23 15:11:32 -05:00
// This is an endpoint A record request. Get first element as endpoint.
r . endpoint = segs [ 0 ]
offset = 1
2017-01-15 03:12:28 -05:00
}
if len ( segs ) == ( offset + 3 ) {
r . service = segs [ offset ]
r . namespace = segs [ offset + 1 ]
r . typeName = segs [ offset + 2 ]
2017-01-05 10:09:59 -05:00
2017-01-15 03:12:28 -05:00
return r , nil
2017-01-05 10:09:59 -05:00
}
2017-01-15 03:12:28 -05:00
2017-05-22 16:05:48 -04:00
if len ( segs ) == 1 && qtype == dns . TypeTXT {
2017-01-15 14:37:18 +00:00
r . typeName = segs [ 0 ]
return r , nil
}
2017-01-23 15:11:32 -05:00
return r , errInvalidRequest
2017-01-15 03:12:28 -05:00
2017-01-05 10:09:59 -05:00
}
2016-10-30 15:54:16 +00:00
// Records looks up services in kubernetes. If exact is true, it will lookup
// just this name. This is used when find matches when completing SRV lookups
2016-06-06 12:49:53 -07:00
// for instance.
2017-01-15 03:12:28 -05:00
func ( k * Kubernetes ) Records ( r recordRequest ) ( [ ] msg . Service , error ) {
2016-07-14 14:50:14 -07:00
// Abort if the namespace does not contain a wildcard, and namespace is not published per CoreFile
// Case where namespace contains a wildcard is handled in Get(...) method.
2017-01-15 03:12:28 -05:00
if ( ! symbolContainsWildcard ( r . namespace ) ) && ( len ( k . Namespaces ) > 0 ) && ( ! dnsstrings . StringInSlice ( r . namespace , k . Namespaces ) ) {
2016-11-11 16:56:15 +00:00
return nil , errNsNotExposed
2016-07-07 01:40:58 -07:00
}
2016-06-06 12:49:53 -07:00
2017-01-29 12:06:26 -08:00
services , pods , err := k . get ( r )
2016-07-07 01:40:58 -07:00
if err != nil {
return nil , err
}
2017-01-11 16:23:10 -05:00
if len ( services ) == 0 && len ( pods ) == 0 {
2017-06-14 09:38:00 -04:00
// Did not find item in k8s, try federated
if r . federation != "" {
fedCNAME := k . federationCNAMERecord ( r )
if fedCNAME . Key != "" {
return [ ] msg . Service { fedCNAME } , nil
}
}
2016-11-11 16:56:15 +00:00
return nil , errNoItems
2016-07-07 01:40:58 -07:00
}
2016-06-06 12:49:53 -07:00
2017-06-14 09:38:00 -04:00
records := k . getRecordsForK8sItems ( services , pods , r )
2016-07-07 01:40:58 -07:00
return records , nil
}
2016-06-06 12:49:53 -07:00
2017-01-05 10:09:59 -05:00
func endpointHostname ( addr api . EndpointAddress ) string {
if addr . Hostname != "" {
return strings . ToLower ( addr . Hostname )
}
if strings . Contains ( addr . IP , "." ) {
return strings . Replace ( addr . IP , "." , "-" , - 1 )
}
if strings . Contains ( addr . IP , ":" ) {
return strings . ToLower ( strings . Replace ( addr . IP , ":" , "-" , - 1 ) )
}
return ""
}
2017-06-14 09:38:00 -04:00
func ( k * Kubernetes ) getRecordsForK8sItems ( services [ ] service , pods [ ] pod , r recordRequest ) ( records [ ] msg . Service ) {
zonePath := msg . Path ( r . zone , "coredns" )
2016-06-06 12:49:53 -07:00
2017-01-11 16:23:10 -05:00
for _ , svc := range services {
2017-01-05 10:09:59 -05:00
if svc . addr == api . ClusterIPNone {
// This is a headless service, create records for each endpoint
for _ , ep := range svc . endpoints {
s := msg . Service {
2017-05-22 16:05:48 -04:00
Host : ep . addr . IP ,
Port : int ( ep . port . Port ) ,
2016-12-02 17:50:01 -05:00
}
2017-06-14 09:38:00 -04:00
if r . federation != "" {
s . Key = strings . Join ( [ ] string { zonePath , "svc" , r . federation , svc . namespace , svc . name , endpointHostname ( ep . addr ) } , "/" )
} else {
s . Key = strings . Join ( [ ] string { zonePath , "svc" , svc . namespace , svc . name , endpointHostname ( ep . addr ) } , "/" )
}
2017-01-05 10:09:59 -05:00
records = append ( records , s )
2016-12-02 17:50:01 -05:00
}
} else {
// Create records for each exposed port...
2017-01-05 10:09:59 -05:00
for _ , p := range svc . ports {
2017-05-22 16:05:48 -04:00
s := msg . Service {
Host : svc . addr ,
Port : int ( p . Port ) }
2017-06-14 09:38:00 -04:00
if r . federation != "" {
s . Key = strings . Join ( [ ] string { zonePath , "svc" , r . federation , svc . namespace , svc . name } , "/" )
} else {
s . Key = strings . Join ( [ ] string { zonePath , "svc" , svc . namespace , svc . name } , "/" )
}
2016-12-02 17:50:01 -05:00
records = append ( records , s )
}
2017-05-30 08:20:39 -04:00
// If the addr is not an IP (i.e. an external service), add the record ...
s := msg . Service {
Key : strings . Join ( [ ] string { zonePath , "svc" , svc . namespace , svc . name } , "/" ) ,
Host : svc . addr }
if t , _ := s . HostType ( ) ; t == dns . TypeCNAME {
2017-06-14 09:38:00 -04:00
if r . federation != "" {
s . Key = strings . Join ( [ ] string { zonePath , "svc" , r . federation , svc . namespace , svc . name } , "/" )
} else {
s . Key = strings . Join ( [ ] string { zonePath , "svc" , svc . namespace , svc . name } , "/" )
}
2017-05-30 08:20:39 -04:00
records = append ( records , s )
}
2016-07-07 01:40:58 -07:00
}
}
2016-06-06 12:49:53 -07:00
2017-01-11 16:23:10 -05:00
for _ , p := range pods {
s := msg . Service {
2017-05-22 16:05:48 -04:00
Key : strings . Join ( [ ] string { zonePath , "pod" , p . namespace , p . name } , "/" ) ,
2017-01-11 16:23:10 -05:00
Host : p . addr ,
}
records = append ( records , s )
}
2016-07-07 01:40:58 -07:00
return records
2016-06-06 12:49:53 -07:00
}
2017-01-11 16:23:10 -05:00
func ipFromPodName ( podname string ) string {
if strings . Count ( podname , "-" ) == 3 && ! strings . Contains ( podname , "--" ) {
return strings . Replace ( podname , "-" , "." , - 1 )
}
return strings . Replace ( podname , "-" , ":" , - 1 )
}
func ( k * Kubernetes ) findPods ( namespace , podname string ) ( pods [ ] pod , err error ) {
if k . PodMode == PodModeDisabled {
2017-05-22 16:05:48 -04:00
return pods , errPodsDisabled
2017-01-11 16:23:10 -05:00
}
var ip string
if strings . Count ( podname , "-" ) == 3 && ! strings . Contains ( podname , "--" ) {
ip = strings . Replace ( podname , "-" , "." , - 1 )
} else {
ip = strings . Replace ( podname , "-" , ":" , - 1 )
}
if k . PodMode == PodModeInsecure {
s := pod { name : podname , namespace : namespace , addr : ip }
pods = append ( pods , s )
return pods , nil
}
2017-01-20 02:22:11 -05:00
// PodModeVerified
2017-05-22 16:05:48 -04:00
objList := k . APIConn . PodIndex ( ip )
2017-01-11 16:23:10 -05:00
2017-01-20 02:22:11 -05:00
nsWildcard := symbolContainsWildcard ( namespace )
for _ , o := range objList {
p , ok := o . ( * api . Pod )
if ! ok {
2017-05-25 12:08:34 -07:00
return nil , errAPIBadPodType
2017-01-20 02:22:11 -05:00
}
// If namespace has a wildcard, filter results against Corefile namespace list.
if nsWildcard && ( len ( k . Namespaces ) > 0 ) && ( ! dnsstrings . StringInSlice ( p . Namespace , k . Namespaces ) ) {
continue
}
// check for matching ip and namespace
if ip == p . Status . PodIP && symbolMatches ( namespace , p . Namespace , nsWildcard ) {
s := pod { name : podname , namespace : namespace , addr : ip }
pods = append ( pods , s )
return pods , nil
}
}
return pods , nil
2017-01-11 16:23:10 -05:00
}
2017-01-29 12:06:26 -08:00
// get retrieves matching data from the cache.
func ( k * Kubernetes ) get ( r recordRequest ) ( services [ ] service , pods [ ] pod , err error ) {
2017-01-15 03:12:28 -05:00
switch {
case r . typeName == "pod" :
pods , err = k . findPods ( r . namespace , r . service )
return nil , pods , err
default :
services , err = k . findServices ( r )
return services , nil , err
}
}
func ( k * Kubernetes ) findServices ( r recordRequest ) ( [ ] service , error ) {
2016-10-12 12:46:35 +01:00
serviceList := k . APIConn . ServiceList ( )
2016-06-06 12:49:53 -07:00
2017-01-05 10:09:59 -05:00
var resultItems [ ] service
2016-07-14 14:50:14 -07:00
2017-01-15 03:12:28 -05:00
nsWildcard := symbolContainsWildcard ( r . namespace )
serviceWildcard := symbolContainsWildcard ( r . service )
portWildcard := symbolContainsWildcard ( r . port ) || r . port == ""
protocolWildcard := symbolContainsWildcard ( r . protocol ) || r . protocol == ""
2017-01-05 10:09:59 -05:00
for _ , svc := range serviceList {
2017-01-15 03:12:28 -05:00
if ! ( symbolMatches ( r . namespace , svc . Namespace , nsWildcard ) && symbolMatches ( r . service , svc . Name , serviceWildcard ) ) {
2017-01-05 10:09:59 -05:00
continue
}
// If namespace has a wildcard, filter results against Corefile namespace list.
// (Namespaces without a wildcard were filtered before the call to this function.)
if nsWildcard && ( len ( k . Namespaces ) > 0 ) && ( ! dnsstrings . StringInSlice ( svc . Namespace , k . Namespaces ) ) {
continue
}
2017-05-30 08:20:39 -04:00
s := service { name : svc . Name , namespace : svc . Namespace }
// External Service
if svc . Spec . ExternalName != "" {
s . addr = svc . Spec . ExternalName
resultItems = append ( resultItems , s )
continue
}
// ClusterIP service
if svc . Spec . ClusterIP != api . ClusterIPNone {
s . addr = svc . Spec . ClusterIP
2017-01-05 10:09:59 -05:00
for _ , p := range svc . Spec . Ports {
2017-01-15 03:12:28 -05:00
if ! ( symbolMatches ( r . port , strings . ToLower ( p . Name ) , portWildcard ) && symbolMatches ( r . protocol , strings . ToLower ( string ( p . Protocol ) ) , protocolWildcard ) ) {
2017-01-05 10:09:59 -05:00
continue
}
s . ports = append ( s . ports , p )
}
resultItems = append ( resultItems , s )
continue
}
// Headless service
2017-05-30 08:20:39 -04:00
s . addr = svc . Spec . ClusterIP
2017-05-22 16:05:48 -04:00
endpointsList := k . APIConn . EndpointsList ( )
2017-01-05 10:09:59 -05:00
for _ , ep := range endpointsList . Items {
if ep . ObjectMeta . Name != svc . Name || ep . ObjectMeta . Namespace != svc . Namespace {
2016-07-14 14:50:14 -07:00
continue
}
2017-01-05 10:09:59 -05:00
for _ , eps := range ep . Subsets {
for _ , addr := range eps . Addresses {
for _ , p := range eps . Ports {
ephostname := endpointHostname ( addr )
2017-01-15 03:12:28 -05:00
if r . endpoint != "" && r . endpoint != ephostname {
2017-01-05 10:09:59 -05:00
continue
}
2017-01-15 03:12:28 -05:00
if ! ( symbolMatches ( r . port , strings . ToLower ( p . Name ) , portWildcard ) && symbolMatches ( r . protocol , strings . ToLower ( string ( p . Protocol ) ) , protocolWildcard ) ) {
2017-01-05 10:09:59 -05:00
continue
}
s . endpoints = append ( s . endpoints , endpoint { addr : addr , port : p } )
}
}
}
2016-07-14 14:50:14 -07:00
}
2017-01-05 10:09:59 -05:00
resultItems = append ( resultItems , s )
2016-07-14 14:50:14 -07:00
}
return resultItems , nil
2016-06-06 12:49:53 -07:00
}
2017-01-05 10:09:59 -05:00
func symbolMatches ( queryString , candidateString string , wildcard bool ) bool {
2017-01-15 03:12:28 -05:00
if wildcard {
return true
}
return queryString == candidateString
2016-07-14 14:50:14 -07:00
}
2017-01-05 10:09:59 -05:00
// getServiceRecordForIP: Gets a service record with a cluster ip matching the ip argument
// If a service cluster ip does not match, it checks all endpoints
2016-08-22 23:15:21 -07:00
func ( k * Kubernetes ) getServiceRecordForIP ( ip , name string ) [ ] msg . Service {
2017-01-05 10:09:59 -05:00
// First check services with cluster ips
2017-05-22 16:05:48 -04:00
svcList := k . APIConn . ServiceList ( )
2016-09-23 10:13:02 -03:00
for _ , service := range svcList {
2017-02-07 16:22:43 -05:00
if ( len ( k . Namespaces ) > 0 ) && ! dnsstrings . StringInSlice ( service . Namespace , k . Namespaces ) {
2017-01-05 10:09:59 -05:00
continue
}
2016-08-05 18:19:51 -07:00
if service . Spec . ClusterIP == ip {
2017-05-22 16:05:48 -04:00
domain := strings . Join ( [ ] string { service . Name , service . Namespace , "svc" , k . PrimaryZone ( ) } , "." )
2017-01-29 12:06:26 -08:00
return [ ] msg . Service { { Host : domain } }
2017-01-05 10:09:59 -05:00
}
}
// If no cluster ips match, search endpoints
2017-05-22 16:05:48 -04:00
epList := k . APIConn . EndpointsList ( )
2017-01-05 10:09:59 -05:00
for _ , ep := range epList . Items {
2017-02-07 16:22:43 -05:00
if ( len ( k . Namespaces ) > 0 ) && ! dnsstrings . StringInSlice ( ep . ObjectMeta . Namespace , k . Namespaces ) {
2017-01-05 10:09:59 -05:00
continue
}
for _ , eps := range ep . Subsets {
for _ , addr := range eps . Addresses {
if addr . IP == ip {
2017-05-22 16:05:48 -04:00
domain := strings . Join ( [ ] string { endpointHostname ( addr ) , ep . ObjectMeta . Name , ep . ObjectMeta . Namespace , "svc" , k . PrimaryZone ( ) } , "." )
2017-01-29 12:06:26 -08:00
return [ ] msg . Service { { Host : domain } }
2017-01-05 10:09:59 -05:00
}
}
2016-08-05 18:19:51 -07:00
}
2016-06-06 12:49:53 -07:00
}
2016-08-05 18:19:51 -07:00
return nil
2016-06-06 12:49:53 -07:00
}
2016-09-23 09:48:11 -03:00
// symbolContainsWildcard checks whether symbol contains a wildcard value
func symbolContainsWildcard ( symbol string ) bool {
2017-01-22 03:15:12 -05:00
return ( symbol == "*" || symbol == "any" )
2016-09-23 09:48:11 -03:00
}
2017-06-14 09:38:00 -04:00
func ( k * Kubernetes ) localPodIP ( ) net . IP {
if localPodIP != nil {
return localPodIP
}
addrs , _ := k . interfaceAddrs . interfaceAddrs ( )
for _ , addr := range addrs {
ip , _ , _ := net . ParseCIDR ( addr . String ( ) )
ip = ip . To4 ( )
if ip == nil || ip . IsLoopback ( ) {
continue
}
localPodIP = ip
return localPodIP
}
return nil
}