diff --git a/middleware/kubernetes/README.md b/middleware/kubernetes/README.md index 54dd98e57..85e2356b6 100644 --- a/middleware/kubernetes/README.md +++ b/middleware/kubernetes/README.md @@ -1,15 +1,14 @@ # kubernetes *kubernetes* enables reading zone data from a kubernetes cluster. Record names -are constructed as "myservice.mynamespace.coredns.local" where: +are constructed as "myservice.mynamespace.type.coredns.local" where: * "myservice" is the name of the k8s service (this may include multiple DNS labels, such as "c1.myservice"), * "mynamespace" is the k8s namespace for the service, and +* "type" is svc or pod * "coredns.local" is the zone configured for `kubernetes`. -The record name format can be changed by specifying a name template in the Corefile. - ## Syntax ~~~ @@ -50,9 +49,6 @@ This is the default kubernetes setup, with everything specified in full: # The tls cert, key and the CA cert filenames tls cert key cacert - # Assemble k8s record names with the template - template {service}.{namespace}.{type}.{zone} - # Only expose the k8s namespace "demo" namespaces demo @@ -64,15 +60,15 @@ This is the default kubernetes setup, with everything specified in full: # "application=nginx" in the staging or qa environments. #labels environment in (staging, qa),application=nginx - # The mode of responding to pod A record requests. - # e.g 1-2-3-4.ns.pod.zone. This option is provided to allow use of - # SSL certs when connecting directly to pods. - # Valid values: disabled, verified, insecure - # disabled: default. ignore pod requests, always returning NXDOMAIN - # insecure: Always return an A record with IP from request (without - # checking k8s). This option is is vulnerable to abuse if - # used maliciously in conjuction with wildcard SSL certs. - pods disabled + # The mode of responding to pod A record requests. + # e.g 1-2-3-4.ns.pod.zone. This option is provided to allow use of + # SSL certs when connecting directly to pods. + # Valid values: disabled, verified, insecure + # disabled: default. ignore pod requests, always returning NXDOMAIN + # insecure: Always return an A record with IP from request (without + # checking k8s). This option is is vulnerable to abuse if + # used maliciously in conjuction with wildcard SSL certs. + pods disabled } # Perform DNS response caching for the coredns.local zone # Cache timeout is specified by an integer in seconds @@ -82,22 +78,12 @@ This is the default kubernetes setup, with everything specified in full: Defaults: * If the `namespaces` keyword is omitted, all kubernetes namespaces are exposed. -* If the `template` keyword is omitted, the default template of "{service}.{namespace}.{type}.{zone}" is used. * If the `resyncperiod` keyword is omitted, the default resync period is 5 minutes. * The `labels` keyword is only used when filtering results based on kubernetes label selector syntax is required. The label selector syntax is described in the kubernetes API documentation at: http://kubernetes.io/docs/user-guide/labels/ * If the `pods` keyword is omitted, all pod type requests will result in NXDOMAIN -### Template Syntax -Record name templates can be constructed using the symbolic elements: - -| template symbol | description | -| `{service}` | Kubernetes object/service name. | -| `{namespace}` | The kubernetes namespace. | -| `{type}` | The type of the kubernetes object. Supports values 'svc' and 'pod'. | -| `{zone}` | The zone configured for the kubernetes middleware. | - ### Basic Setup #### Launch Kubernetes @@ -146,7 +132,6 @@ Build CoreDNS and launch using this configuration file: kubernetes coredns.local { resyncperiod 5m endpoint http://localhost:8080 - template {service}.{namespace}.{type}.{zone} namespaces demo # Only expose the records for kubernetes objects # that matches this label selector. diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go index b93358863..be0a57778 100644 --- a/middleware/kubernetes/kubernetes.go +++ b/middleware/kubernetes/kubernetes.go @@ -10,7 +10,6 @@ import ( "github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware/etcd/msg" - "github.com/miekg/coredns/middleware/kubernetes/nametemplate" "github.com/miekg/coredns/middleware/pkg/dnsutil" dnsstrings "github.com/miekg/coredns/middleware/pkg/strings" "github.com/miekg/coredns/middleware/proxy" @@ -38,7 +37,6 @@ type Kubernetes struct { APIClientKey string APIConn *dnsController ResyncPeriod time.Duration - NameTemplate *nametemplate.Template Namespaces []string LabelSelector *unversionedapi.LabelSelector Selector *labels.Selector @@ -69,16 +67,22 @@ type pod struct { addr string } +type recordRequest struct { + port, protocol, endpoint, service, namespace, typeName, zone string +} + var errNoItems = errors.New("no items found") var errNsNotExposed = errors.New("namespace is not exposed") var errInvalidRequest = errors.New("invalid query name") // Services implements the ServiceBackend interface. func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, []msg.Service, error) { - if state.Type() == "SRV" && !ValidSRV(state.Name()) { - return nil, nil, errInvalidRequest + + r, e := k.parseRequest(state.Name(), state.Type()) + if e != nil { + return nil, nil, e } - s, e := k.Records(state.Name(), exact) + s, e := k.Records(r) return s, nil, e // Haven't implemented debug queries yet. } @@ -177,85 +181,94 @@ func (k *Kubernetes) InitKubeCache() error { return err } -// getZoneForName returns the zone string that matches the name and a -// list of the DNS labels from name that are within the zone. -// For example, if "coredns.local" is a zone configured for the -// Kubernetes middleware, then getZoneForName("a.b.coredns.local") -// will return ("coredns.local", ["a", "b"]). -func (k *Kubernetes) getZoneForName(name string) (string, []string) { - var zone string - var serviceSegments []string +func (k *Kubernetes) parseRequest(lowerCasedName, qtype string) (r recordRequest, err error) { + // 3 Possible cases + // SRV Request: _port._protocol.service.namespace.type.zone + // A Request (endpoint): endpoint.service.namespace.type.zone + // A Request (service): service.namespace.type.zone + // separate zone from rest of lowerCasedName + var segs []string for _, z := range k.Zones { - if dns.IsSubDomain(z, name) { - zone = z + if dns.IsSubDomain(z, lowerCasedName) { + r.zone = z - serviceSegments = dns.SplitDomainName(name) - serviceSegments = serviceSegments[:len(serviceSegments)-dns.CountLabel(zone)] + segs = dns.SplitDomainName(lowerCasedName) + segs = segs[:len(segs)-dns.CountLabel(r.zone)] break } } - - return zone, serviceSegments -} - -// stripSRVPrefix separates out the port and protocol segments, if present -// If not present, assume all ports/protocols (e.g. wildcard) -func stripSRVPrefix(name []string) (string, string, []string) { - if name[0][0] == '_' && name[1][0] == '_' { - return name[0][1:], name[1][1:], name[2:] + if r.zone == "" { + return r, errors.New("zone not found") } - // no srv prefix present - return "*", "*", name -} -func stripEndpointName(name []string) (endpoint string, nameOut []string) { - if len(name) == 4 { - return strings.ToLower(name[0]), name[1:] + offset := 0 + if len(segs) == 5 { + // 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) { + return r, errors.New("srv port must start with an underscore or be a wildcard") + } + } + if segs[1][0] == '_' { + r.protocol = segs[1][1:] + if r.protocol != "tcp" && r.protocol != "udp" { + return r, errors.New("invalid srv protocol: " + r.protocol) + } + } else { + r.protocol = segs[1] + if !symbolContainsWildcard(r.protocol) { + return r, errors.New("srv protocol must start with an underscore or be a wildcard") + } + } + offset = 2 + } else if len(segs) == 4 { + // This is an endpoint A style request. Get first element as endpoint. + r.endpoint = segs[0] + offset = 1 } - return "", name + + // SRV requests require a port and protocol + if qtype == "SRV" { + if r.port == "" || r.protocol == "" { + return r, errors.New("invalid srv request") + } + } + // A requests cannot have port/protocol + if qtype == "A" { + if r.port != "" && r.protocol != "" { + return r, errors.New("invalid a request") + } + } + + if len(segs) == (offset + 3) { + r.service = segs[offset] + r.namespace = segs[offset+1] + r.typeName = segs[offset+2] + + return r, nil + } + + return r, errors.New("invalid request") + } // 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 // for instance. -func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { - var ( - serviceName string - namespace string - typeName string - ) - - zone, serviceSegments := k.getZoneForName(name) - port, protocol, serviceSegments := stripSRVPrefix(serviceSegments) - endpointname, serviceSegments := stripEndpointName(serviceSegments) - if len(serviceSegments) < 3 { - return nil, errNoItems - } - - serviceName = serviceSegments[0] - namespace = serviceSegments[1] - typeName = serviceSegments[2] - - if namespace == "" { - err := errors.New("Parsing query string did not produce a namespace value. Assuming wildcard namespace.") - log.Printf("[WARN] %v\n", err) - namespace = "*" - } - - if serviceName == "" { - err := errors.New("Parsing query string did not produce a serviceName value. Assuming wildcard serviceName.") - log.Printf("[WARN] %v\n", err) - serviceName = "*" - } +func (k *Kubernetes) Records(r recordRequest) ([]msg.Service, error) { // 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. - if (!symbolContainsWildcard(namespace)) && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(namespace, k.Namespaces)) { + if (!symbolContainsWildcard(r.namespace)) && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(r.namespace, k.Namespaces)) { return nil, errNsNotExposed } - services, pods, err := k.Get(namespace, serviceName, endpointname, port, protocol, typeName) + services, pods, err := k.Get(r) if err != nil { return nil, err } @@ -264,7 +277,7 @@ func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { return nil, errNoItems } - records := k.getRecordsForK8sItems(services, pods, zone) + records := k.getRecordsForK8sItems(services, pods, r.zone) return records, nil } @@ -320,18 +333,6 @@ func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone return records } -// Get retrieves matching data from the cache. -func (k *Kubernetes) Get(namespace, servicename, endpointname, port, protocol, typeName string) (services []service, pods []pod, err error) { - switch { - case typeName == "pod": - pods, err = k.findPods(namespace, servicename) - return nil, pods, err - default: - services, err = k.findServices(namespace, servicename, endpointname, port, protocol) - return services, nil, err - } -} - func ipFromPodName(podname string) string { if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") { return strings.Replace(podname, "-", ".", -1) @@ -362,18 +363,30 @@ func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error) } -func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, protocol string) ([]service, error) { +// Get retrieves matching data from the cache. +func (k *Kubernetes) Get(r recordRequest) (services []service, pods []pod, err error) { + 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) { serviceList := k.APIConn.ServiceList() var resultItems []service - nsWildcard := symbolContainsWildcard(namespace) - serviceWildcard := symbolContainsWildcard(servicename) - portWildcard := symbolContainsWildcard(port) - protocolWildcard := symbolContainsWildcard(protocol) + nsWildcard := symbolContainsWildcard(r.namespace) + serviceWildcard := symbolContainsWildcard(r.service) + portWildcard := symbolContainsWildcard(r.port) || r.port == "" + protocolWildcard := symbolContainsWildcard(r.protocol) || r.protocol == "" for _, svc := range serviceList { - if !(symbolMatches(namespace, svc.Namespace, nsWildcard) && symbolMatches(servicename, svc.Name, serviceWildcard)) { + if !(symbolMatches(r.namespace, svc.Namespace, nsWildcard) && symbolMatches(r.service, svc.Name, serviceWildcard)) { continue } // If namespace has a wildcard, filter results against Corefile namespace list. @@ -384,7 +397,7 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr s := service{name: svc.Name, namespace: svc.Namespace, addr: svc.Spec.ClusterIP} if s.addr != api.ClusterIPNone { for _, p := range svc.Spec.Ports { - if !(symbolMatches(port, strings.ToLower(p.Name), portWildcard) && symbolMatches(protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) { + if !(symbolMatches(r.port, strings.ToLower(p.Name), portWildcard) && symbolMatches(r.protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) { continue } s.ports = append(s.ports, p) @@ -405,10 +418,10 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr for _, addr := range eps.Addresses { for _, p := range eps.Ports { ephostname := endpointHostname(addr) - if endpointname != "" && endpointname != ephostname { + if r.endpoint != "" && r.endpoint != ephostname { continue } - if !(symbolMatches(port, strings.ToLower(p.Name), portWildcard) && symbolMatches(protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) { + if !(symbolMatches(r.port, strings.ToLower(p.Name), portWildcard) && symbolMatches(r.protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) { continue } s.endpoints = append(s.endpoints, endpoint{addr: addr, port: p}) @@ -422,16 +435,10 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr } func symbolMatches(queryString, candidateString string, wildcard bool) bool { - result := false - switch { - case !wildcard: - result = (queryString == candidateString) - case queryString == "*": - result = true - case queryString == "any": - result = true + if wildcard { + return true } - return result + return queryString == candidateString } // getServiceRecordForIP: Gets a service record with a cluster ip matching the ip argument @@ -476,57 +483,3 @@ func (k *Kubernetes) getServiceRecordForIP(ip, name string) []msg.Service { func symbolContainsWildcard(symbol string) bool { return (strings.Contains(symbol, "*") || (symbol == "any")) } - -// ValidSRV parses a server record validating _port._proto. prefix labels. -// The valid schema is: -// * Fist two segments must start with an "_", -// * Second segment must be one of _tcp|_udp|_*|_any -func ValidSRV(name string) bool { - - // Does it start with a "_" ? - if len(name) > 0 && name[0] != '_' { - return false - } - - // First label - first, end := dns.NextLabel(name, 0) - if end { - return false - } - // Second label - off, end := dns.NextLabel(name, first) - if end { - return false - } - - // first:off has captured _tcp. or _udp. (if present) - second := name[first:off] - if len(second) > 0 && second[0] != '_' { - return false - } - - // A bit convoluted to avoid strings.ToLower - if len(second) == 5 { - // matches _tcp - if (second[1] == 't' || second[1] == 'T') && (second[2] == 'c' || second[2] == 'C') && - (second[3] == 'p' || second[3] == 'P') { - return true - } - // matches _udp - if (second[1] == 'u' || second[1] == 'U') && (second[2] == 'd' || second[2] == 'D') && - (second[3] == 'p' || second[3] == 'P') { - return true - } - // matches _any - if (second[1] == 'a' || second[1] == 'A') && (second[2] == 'n' || second[2] == 'N') && - (second[3] == 'y' || second[3] == 'Y') { - return true - } - } - // matches _* - if len(second) == 3 && second[1] == '*' { - return true - } - - return false -} diff --git a/middleware/kubernetes/kubernetes_test.go b/middleware/kubernetes/kubernetes_test.go index 53404ecf5..f7529a1d5 100644 --- a/middleware/kubernetes/kubernetes_test.go +++ b/middleware/kubernetes/kubernetes_test.go @@ -1,6 +1,7 @@ package kubernetes import "testing" +import "reflect" // Test data for TestSymbolContainsWildcard cases. var testdataSymbolContainsWildcard = []struct { @@ -23,3 +24,114 @@ func TestSymbolContainsWildcard(t *testing.T) { } } } + +func expectString(t *testing.T, function, qtype, query string, r *recordRequest, field, expected string) { + ref := reflect.ValueOf(r) + ref_f := reflect.Indirect(ref).FieldByName(field) + got := ref_f.String() + if got != expected { + t.Errorf("Expected %v(%v, \"%v\") to get %v == \"%v\". Instead got \"%v\".", function, query, qtype, field, expected, got) + } +} + +func TestParseRequest(t *testing.T) { + + var tcs map[string]string + + k := Kubernetes{Zones: []string{"inter.webs.test"}} + f := "parseRequest" + + // Test a valid SRV request + // + query := "_http._tcp.webs.mynamespace.svc.inter.webs.test." + r, e := k.parseRequest(query, "SRV") + if e != nil { + t.Errorf("Expected no error from parseRequest(%v, \"SRV\"). Instead got '%v'.", query, e) + } + + tcs = map[string]string{ + "port": "http", + "protocol": "tcp", + "endpoint": "", + "service": "webs", + "namespace": "mynamespace", + "typeName": "svc", + "zone": "inter.webs.test", + } + for field, expected := range tcs { + expectString(t, f, "SRV", query, &r, field, expected) + } + + // Test wildcard acceptance + // + query = "*.any.*.any.svc.inter.webs.test." + r, e = k.parseRequest(query, "SRV") + if e != nil { + t.Errorf("Expected no error from parseRequest(\"%v\", \"SRV\"). Instead got '%v'.", query, e) + } + + tcs = map[string]string{ + "port": "*", + "protocol": "any", + "endpoint": "", + "service": "*", + "namespace": "any", + "typeName": "svc", + "zone": "inter.webs.test", + } + for field, expected := range tcs { + expectString(t, f, "SRV", query, &r, field, expected) + } + + // Test A request of endpoint + // + query = "1-2-3-4.webs.mynamespace.svc.inter.webs.test." + r, e = k.parseRequest(query, "A") + if e != nil { + t.Errorf("Expected no error from parseRequest(\"%v\", \"A\"). Instead got '%v'.", query, e) + } + tcs = map[string]string{ + "port": "", + "protocol": "", + "endpoint": "1-2-3-4", + "service": "webs", + "namespace": "mynamespace", + "typeName": "svc", + "zone": "inter.webs.test", + } + for field, expected := range tcs { + expectString(t, f, "A", query, &r, field, expected) + } + + // Invalid query tests + // + + invalidAQueries := []string{ + "_http._tcp.webs.mynamespace.svc.inter.webs.test.", // A requests cannot have port or protocol + "servname.ns1.srv.inter.nets.test.", // A requests must have zone that matches corefile + + } + for _, q := range invalidAQueries { + _, e = k.parseRequest(q, "A") + if e == nil { + t.Errorf("Expected error from %v(\"%v\", \"A\").", f, q) + } + } + + invalidSRVQueries := []string{ + "webs.mynamespace.svc.inter.webs.test.", // SRV requests must have port and protocol + "_http._pcp.webs.mynamespace.svc.inter.webs.test.", // SRV protocol must be tcp or udp + "_http._tcp.ep.webs.ns.svc.inter.webs.test.", // SRV requests cannot have an endpoint + "_*._*.webs.mynamespace.svc.inter.webs.test.", // SRV request with invalid wildcards + "_http._tcp", + "_tcp.test.", + ".", + } + + for _, q := range invalidSRVQueries { + _, e = k.parseRequest(q, "SRV") + if e == nil { + t.Errorf("Expected error from %v(\"%v\", \"SRV\").", f, q) + } + } +} diff --git a/middleware/kubernetes/lookup.go b/middleware/kubernetes/lookup.go index 61689baac..a3c9fcbc0 100644 --- a/middleware/kubernetes/lookup.go +++ b/middleware/kubernetes/lookup.go @@ -12,7 +12,11 @@ import ( ) func (k Kubernetes) records(state request.Request, exact bool) ([]msg.Service, error) { - services, err := k.Records(state.Name(), exact) + r, err := k.parseRequest(state.Name(), state.Type()) + if err != nil { + return nil, err + } + services, err := k.Records(r) if err != nil { return nil, err } diff --git a/middleware/kubernetes/nametemplate/nametemplate.go b/middleware/kubernetes/nametemplate/nametemplate.go deleted file mode 100644 index 328572497..000000000 --- a/middleware/kubernetes/nametemplate/nametemplate.go +++ /dev/null @@ -1,195 +0,0 @@ -package nametemplate - -import ( - "errors" - "strings" - - dns_strings "github.com/miekg/coredns/middleware/pkg/strings" -) - -// Likely symbols that require support: -// {id} -// {ip} -// {portname} -// {protocolname} -// {servicename} -// {namespace} -// {type} "svc" or "pod" -// {zone} - -// SkyDNS normal services have an A-record of the form "{servicename}.{namespace}.{type}.{zone}" -// This resolves to the cluster IP of the service. - -// SkyDNS headless services have an A-record of the form "{servicename}.{namespace}.{type}.{zone}" -// This resolves to the set of IPs of the pods selected by the Service. Clients are expected to -// consume the set or else use round-robin selection from the set. - -var symbols = map[string]string{ - "service": "{service}", - "namespace": "{namespace}", - "type": "{type}", - "zone": "{zone}", -} - -var types = []string{ - "svc", - "pod", -} - -var requiredSymbols = []string{ - "namespace", - "service", -} - -// TODO: Validate that provided NameTemplate string only contains: -// * valid, known symbols, or -// * static strings - -// TODO: Support collapsing multiple segments into a symbol. Either: -// * all left-over segments are used as the "service" name, or -// * some scheme like "{namespace}.{namespace}" means use -// segments concatenated with a "." for the namespace, or -// * {namespace2:4} means use segements 2->4 for the namespace. - -// TODO: possibly need to store length of segmented format to handle cases -// where query string segments to a shorter or longer list than the template. -// When query string segments to shorter than template: -// * either wildcards are being used, or -// * we are not looking up an A, AAAA, or SRV record (eg NS), or -// * we can just short-circuit failure before hitting the k8s API. -// Where the query string is longer than the template, need to define which -// symbol consumes the other segments. Most likely this would be the servicename. -// Also consider how to handle static strings in the format template. - -// Template holds the kubernetes template. -type Template struct { - formatString string - splitFormat []string - // Element is a map of element name :: index in the segmented record name for the named element - Element map[string]int -} - -// SetTemplate use the string s the set the template. -func (t *Template) SetTemplate(s string) error { - var err error - - t.Element = map[string]int{} - - t.formatString = s - t.splitFormat = strings.Split(t.formatString, ".") - for templateIndex, v := range t.splitFormat { - elementPositionSet := false - for name, symbol := range symbols { - if v == symbol { - t.Element[name] = templateIndex - elementPositionSet = true - break - } - } - if !elementPositionSet { - if strings.Contains(v, "{") { - err = errors.New("Record name template contains the unknown symbol '" + v + "'") - return err - } - } - } - - if err == nil && !t.IsValid() { - err = errors.New("Record name template does not pass NameTemplate validation") - return err - } - - return err -} - -// TODO: Find a better way to pull the data segments out of the -// query string based on the template. Perhaps it is better -// to treat the query string segments as a reverse stack and -// step down the stack to find the right element. - -// ZoneFromSegmentArray returns the zone string from the segments. -func (t *Template) ZoneFromSegmentArray(segments []string) string { - index, ok := t.Element["zone"] - if !ok { - return "" - } - return strings.Join(segments[index:], ".") -} - -// NamespaceFromSegmentArray returns the namespace string from the segments. -func (t *Template) NamespaceFromSegmentArray(segments []string) string { - return t.symbolFromSegmentArray("namespace", segments) -} - -// ServiceFromSegmentArray returns the service string from the segments. -func (t *Template) ServiceFromSegmentArray(segments []string) string { - return t.symbolFromSegmentArray("service", segments) -} - -// TypeFromSegmentArray returns the type string from the segments. -func (t *Template) TypeFromSegmentArray(segments []string) string { - typeSegment := t.symbolFromSegmentArray("type", segments) - - // Limit type to known types symbols - if dns_strings.StringInSlice(typeSegment, types) { - return "" - } - - return typeSegment -} - -func (t *Template) symbolFromSegmentArray(symbol string, segments []string) string { - index, ok := t.Element[symbol] - if !ok { - return "" - } - return segments[index] -} - -// RecordNameFromNameValues returns the string produced by applying the -// values to the NameTemplate format string. -func (t *Template) RecordNameFromNameValues(values NameValues) string { - recordName := make([]string, len(t.splitFormat)) - copy(recordName[:], t.splitFormat) - - for name, index := range t.Element { - if index == -1 { - continue - } - switch name { - case "type": - recordName[index] = values.TypeName - case "service": - recordName[index] = values.ServiceName - case "namespace": - recordName[index] = values.Namespace - case "zone": - recordName[index] = values.Zone - } - } - return strings.Join(recordName, ".") -} - -// IsValid returns true if the template has all the required symbols, false otherwise. -func (t *Template) IsValid() bool { - result := true - - // Ensure that all requiredSymbols are found in NameTemplate - for _, symbol := range requiredSymbols { - if _, ok := t.Element[symbol]; !ok { - result = false - break - } - } - - return result -} - -// NameValues contains a number of values. -// TODO(...): better docs. -type NameValues struct { - ServiceName string - Namespace string - TypeName string - Zone string -} diff --git a/middleware/kubernetes/nametemplate/nametemplate_test.go b/middleware/kubernetes/nametemplate/nametemplate_test.go deleted file mode 100644 index 68c67af12..000000000 --- a/middleware/kubernetes/nametemplate/nametemplate_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package nametemplate - -import ( - "strings" - "testing" -) - -const ( - zone = 0 - namespace = 1 - service = 2 -) - -// Map of format string :: expected locations of name symbols in the format. -// -1 value indicates that symbol does not exist in format. -var exampleTemplates = map[string][]int{ - "{service}.{namespace}.{type}.{zone}": {3, 1, 0}, // service symbol expected @ position 0, namespace @ 1, zone @ 3 - "{namespace}.{type}.{zone}": {2, 0, -1}, - "": {-1, -1, -1}, -} - -func TestSetTemplate(t *testing.T) { - for s, expectedValue := range exampleTemplates { - - n := new(Template) - n.SetTemplate(s) - - // check the indexes resulting from calling SetTemplate() against expectedValues - if expectedValue[zone] != -1 { - if n.Element["zone"] != expectedValue[zone] { - t.Errorf("Expected zone at index '%v', instead found at index '%v' for format string '%v'", expectedValue[zone], n.Element["zone"], s) - } - } - } -} - -func TestServiceFromSegmentArray(t *testing.T) { - var ( - n *Template - formatString string - queryString string - splitQuery []string - expectedService string - actualService string - ) - - // Case where template contains {service} - n = new(Template) - formatString = "{service}.{namespace}.{type}.{zone}" - n.SetTemplate(formatString) - - queryString = "myservice.mynamespace.svc.coredns" - splitQuery = strings.Split(queryString, ".") - expectedService = "myservice" - actualService = n.ServiceFromSegmentArray(splitQuery) - - if actualService != expectedService { - t.Errorf("Expected service name '%v', instead got service name '%v' for query string '%v' and format '%v'", expectedService, actualService, queryString, formatString) - } - - // Case where template does not contain {service} - n = new(Template) - formatString = "{namespace}.{type}.{zone}" - n.SetTemplate(formatString) - - queryString = "mynamespace.svc.coredns" - splitQuery = strings.Split(queryString, ".") - expectedService = "" - actualService = n.ServiceFromSegmentArray(splitQuery) - - if actualService != expectedService { - t.Errorf("Expected service name '%v', instead got service name '%v' for query string '%v' and format '%v'", expectedService, actualService, queryString, formatString) - } -} - -func TestZoneFromSegmentArray(t *testing.T) { - var ( - n *Template - formatString string - queryString string - splitQuery []string - expectedZone string - actualZone string - ) - - // Case where template contains {zone} - n = new(Template) - formatString = "{service}.{namespace}.{type}.{zone}" - n.SetTemplate(formatString) - - queryString = "myservice.mynamespace.svc.coredns" - splitQuery = strings.Split(queryString, ".") - expectedZone = "coredns" - actualZone = n.ZoneFromSegmentArray(splitQuery) - - if actualZone != expectedZone { - t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString) - } - - // Case where template does not contain {zone} - n = new(Template) - formatString = "{service}.{namespace}.{type}" - n.SetTemplate(formatString) - - queryString = "mynamespace.coredns.svc" - splitQuery = strings.Split(queryString, ".") - expectedZone = "" - actualZone = n.ZoneFromSegmentArray(splitQuery) - - if actualZone != expectedZone { - t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString) - } - - // Case where zone is multiple segments - n = new(Template) - formatString = "{service}.{namespace}.{type}.{zone}" - n.SetTemplate(formatString) - - queryString = "myservice.mynamespace.svc.coredns.cluster.local" - splitQuery = strings.Split(queryString, ".") - expectedZone = "coredns.cluster.local" - actualZone = n.ZoneFromSegmentArray(splitQuery) - - if actualZone != expectedZone { - t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString) - } -} diff --git a/middleware/kubernetes/setup.go b/middleware/kubernetes/setup.go index 8c733a2df..0813ef7b3 100644 --- a/middleware/kubernetes/setup.go +++ b/middleware/kubernetes/setup.go @@ -8,7 +8,6 @@ import ( "github.com/miekg/coredns/core/dnsserver" "github.com/miekg/coredns/middleware" - "github.com/miekg/coredns/middleware/kubernetes/nametemplate" "github.com/mholt/caddy" unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned" @@ -52,8 +51,6 @@ func setup(c *caddy.Controller) error { func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { k8s := &Kubernetes{ResyncPeriod: defaultResyncPeriod} - k8s.NameTemplate = new(nametemplate.Template) - k8s.NameTemplate.SetTemplate(defaultNameTemplate) k8s.PodMode = PodModeDisabled for c.Next() { @@ -99,18 +96,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { continue } return nil, c.ArgErr() - - case "template": - args := c.RemainingArgs() - if len(args) > 0 { - template := strings.Join(args, "") - err := k8s.NameTemplate.SetTemplate(template) - if err != nil { - return nil, err - } - continue - } - return nil, c.ArgErr() case "namespaces": args := c.RemainingArgs() if len(args) > 0 { @@ -164,7 +149,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { } const ( - defaultNameTemplate = "{service}.{namespace}.{type}.{zone}" defaultResyncPeriod = 5 * time.Minute defaultPodMode = PodModeDisabled ) diff --git a/middleware/kubernetes/setup_test.go b/middleware/kubernetes/setup_test.go index 72b60e5ce..eb327baf3 100644 --- a/middleware/kubernetes/setup_test.go +++ b/middleware/kubernetes/setup_test.go @@ -16,7 +16,6 @@ func TestKubernetesParse(t *testing.T) { shouldErr bool // true if test case is exected to produce an error. expectedErrContent string // substring from the expected error. Empty for positive cases. expectedZoneCount int // expected count of defined zones. - expectedNTValid bool // NameTemplate to be initialized and valid expectedNSCount int // expected count of namespaces. expectedResyncPeriod time.Duration // expected resync period value expectedLabelSelector string // expected label selector value @@ -28,7 +27,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, 0, defaultResyncPeriod, "", @@ -39,7 +37,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 2, - true, 0, defaultResyncPeriod, "", @@ -51,7 +48,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, 0, defaultResyncPeriod, "", @@ -64,20 +60,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, - 0, - defaultResyncPeriod, - "", - }, - { - "template keyword with valid template", - `kubernetes coredns.local { - template {service}.{namespace}.{zone} -}`, - false, - "", - 1, - true, 0, defaultResyncPeriod, "", @@ -90,7 +72,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, 1, defaultResyncPeriod, "", @@ -103,7 +84,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, 2, defaultResyncPeriod, "", @@ -116,7 +96,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, 0, 30 * time.Second, "", @@ -129,7 +108,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, 0, 15 * time.Minute, "", @@ -142,7 +120,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, 0, defaultResyncPeriod, "environment=prod", @@ -155,7 +132,6 @@ func TestKubernetesParse(t *testing.T) { false, "", 1, - true, 0, defaultResyncPeriod, "application=nginx,environment in (production,qa,staging)", @@ -165,14 +141,12 @@ func TestKubernetesParse(t *testing.T) { `kubernetes coredns.local test.local { resyncperiod 15m endpoint http://localhost:8080 - template {service}.{namespace}.{zone} namespaces demo test labels environment in (production, staging, qa),application=nginx }`, false, "", 2, - true, 2, 15 * time.Minute, "application=nginx,environment in (production,qa,staging)", @@ -184,7 +158,6 @@ func TestKubernetesParse(t *testing.T) { true, "Kubernetes setup called without keyword 'kubernetes' in Corefile", -1, - false, -1, defaultResyncPeriod, "", @@ -195,7 +168,6 @@ func TestKubernetesParse(t *testing.T) { true, "Zone name must be provided for kubernetes middleware", -1, - true, 0, defaultResyncPeriod, "", @@ -208,37 +180,10 @@ func TestKubernetesParse(t *testing.T) { true, "Wrong argument count or unexpected line ending after 'endpoint'", -1, - true, -1, defaultResyncPeriod, "", }, - { - "template keyword without a template value", - `kubernetes coredns.local { - template -}`, - true, - "Wrong argument count or unexpected line ending after 'template'", - -1, - false, - 0, - defaultResyncPeriod, - "", - }, - { - "template keyword with an invalid template value", - `kubernetes coredns.local { - template {namespace}.{zone} -}`, - true, - "Record name template does not pass NameTemplate validation", - -1, - false, - 0, - defaultResyncPeriod, - "", - }, { "namespace keyword without a namespace value", `kubernetes coredns.local { @@ -247,7 +192,6 @@ func TestKubernetesParse(t *testing.T) { true, "Parse error: Wrong argument count or unexpected line ending after 'namespaces'", -1, - true, -1, defaultResyncPeriod, "", @@ -260,7 +204,6 @@ func TestKubernetesParse(t *testing.T) { true, "Wrong argument count or unexpected line ending after 'resyncperiod'", -1, - true, 0, 0 * time.Minute, "", @@ -273,7 +216,6 @@ func TestKubernetesParse(t *testing.T) { true, "Unable to parse resync duration value. Value provided was ", -1, - true, 0, 0 * time.Second, "", @@ -286,7 +228,6 @@ func TestKubernetesParse(t *testing.T) { true, "Unable to parse resync duration value. Value provided was ", -1, - true, 0, 0 * time.Second, "", @@ -299,7 +240,6 @@ func TestKubernetesParse(t *testing.T) { true, "Wrong argument count or unexpected line ending after 'labels'", -1, - true, 0, 0 * time.Second, "", @@ -312,7 +252,6 @@ func TestKubernetesParse(t *testing.T) { true, "Unable to parse label selector. Value provided was", -1, - true, 0, 0 * time.Second, "", @@ -354,16 +293,6 @@ func TestKubernetesParse(t *testing.T) { t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d zones, instead found %d zones: '%v' for input '%s'", i, test.expectedZoneCount, foundZoneCount, k8sController.Zones, test.input) } - // NameTemplate - if k8sController.NameTemplate == nil { - t.Errorf("Test %d: Expected kubernetes controller to be initialized with a NameTemplate. Instead found '%v' for input '%s'", i, k8sController.NameTemplate, test.input) - } else { - foundNTValid := k8sController.NameTemplate.IsValid() - if foundNTValid != test.expectedNTValid { - t.Errorf("Test %d: Expected NameTemplate validity to be '%v', instead found '%v' for input '%s'", i, test.expectedNTValid, foundNTValid, test.input) - } - } - // Namespaces foundNSCount := len(k8sController.Namespaces) if foundNSCount != test.expectedNSCount { diff --git a/test/kubernetes_test.go b/test/kubernetes_test.go index 824f8eadc..e93bea973 100644 --- a/test/kubernetes_test.go +++ b/test/kubernetes_test.go @@ -105,7 +105,7 @@ var dnsTestCases = []test.Case{ }, //TODO: Fix below to all use test.SRV not test.A! { - Qname: "_*._*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "*._TcP.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local. 303 IN SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."), @@ -113,12 +113,12 @@ var dnsTestCases = []test.Case{ }, }, { - Qname: "_*._*.bogusservice.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "*.*.bogusservice.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeNameError, Answer: []dns.RR{}, }, { - Qname: "_*._*.svc-1-a.*.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "*.any.svc-1-a.*.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local. 303 IN SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."), @@ -126,7 +126,7 @@ var dnsTestCases = []test.Case{ }, }, { - Qname: "_*._*.svc-1-a.any.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "ANY.*.svc-1-a.any.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local. 303 IN SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."), @@ -134,17 +134,17 @@ var dnsTestCases = []test.Case{ }, }, { - Qname: "_*._*.bogusservice.*.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "*.*.bogusservice.*.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeNameError, Answer: []dns.RR{}, }, { - Qname: "_*._*.bogusservice.any.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "*.*.bogusservice.any.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeNameError, Answer: []dns.RR{}, }, { - Qname: "_c-port._*.*.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "_c-port._UDP.*.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ test.SRV("_c-port._udp.svc-c.test-1.svc.cluster.local. 303 IN SRV 10 100 1234 svc-c.test-1.svc.cluster.local."), @@ -153,7 +153,7 @@ var dnsTestCases = []test.Case{ }, }, { - Qname: "_*._tcp.any.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "*._tcp.any.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeSuccess, Answer: []dns.RR{ test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local. 303 IN SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."), @@ -162,12 +162,12 @@ var dnsTestCases = []test.Case{ }, }, { - Qname: "_*._*.any.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "*.*.any.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeNameError, Answer: []dns.RR{}, }, { - Qname: "_*._*.*.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, + Qname: "*.*.*.test-2.svc.cluster.local.", Qtype: dns.TypeSRV, Rcode: dns.RcodeNameError, Answer: []dns.RR{}, }, @@ -180,18 +180,18 @@ var dnsTestCases = []test.Case{ }, }, { - Qname: "_*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeNameError, + Qname: "*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, + Rcode: dns.RcodeServerFailure, Answer: []dns.RR{}, }, { - Qname: "_*._not-udp-or-tcp.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeNameError, + Qname: "*._not-udp-or-tcp.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, + Rcode: dns.RcodeServerFailure, Answer: []dns.RR{}, }, { Qname: "svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV, - Rcode: dns.RcodeNameError, + Rcode: dns.RcodeServerFailure, Answer: []dns.RR{}, }, {