coredns/middleware/kubernetes/nametemplate/nametemplate.go
Michael Richmond 289f53d386 k8s middleware cleanup, testcases, basic SRV (#181)
* Removing unnecessary gitignore pattern

* Updating Makefile to run unittests for subpackages

* Adding Corefile validation to ignore overlapping zones

* Fixing SRV query handling

* Updating README.md now that SRV works

* Fixing debug message, adding code comment

* Clarifying implementation of zone normalization

* "Overlapping zones" is ill-defined. Reimplemented zone overlap/subzone
  checking to contain these functions in k8s middleware and provide
  better code comments explaining the normalization.

* Separate build verbosity from test verbosity

* Cleaning up comments to match repo code style

* Merging warning messages into single message
* Moving function docs to before function declaration

* Adding test cases for k8sclient connector

* Tests cover connector create and setting base url
* Fixed bugs in connector create and setting base url functions

* Updaing README to group and order development work

* Priority focused on achieving functional parity with SkyDNS.

* Adding work items to README and cleaning up formatting

* More README format cleaning

* List formating

* Refactoring k8s API call to allow dependency injection

* Add test cases for data parsing from k8s into dataobject structures

* URL is dependency-injected to allow replacement with a mock http
  server during test execution

* Adding more data validation for JSON parsing tests

* Adding test case for GetResourceList()

* Adding notes about SkyDNS embedded IP and port record names

* Marked test case implemented.

* Fixing formatting for example command.

* Fixing formatting

* Adding notes about Docker image building.

* Adding SkyDNS work item

* Updating TODO list

* Adding name template to Corefile to specify how k8s record names are assembled

* Adding template support for multi-segment zones

* Updating example CoreFile for k8s with template comment

* Misc whitespace cleanup

* Adding SkyDNS naming notes

* Adding namespace filtering to CoreFile config

* Updating example k8sCoreFile to specify namespaces

* Removing unused codepath

* Adding check for valid namespace

* More README TODO restructuring to focus effort

* Adding template validation while parsing CoreFile

* Record name template is considered invalid if it contains a symbol of the form ${bar} where the symbol
  "${bar}" is not an accepted template symbol.

* Refactoring generation of answer records

* Parse typeName out of query string
* Refactor answer record creation as operation over list of ServiceItems

* Moving k8s API caching into SkyDNS equivalency segment

* Adding function to assemble record names from template

* Warning: This commit may be broken. Syncing to get laptop code over to dev machine.

* More todo notes

* Adding comment describing sample test data.

* Update k8sCorefile

* Adding comment

* Adding filtering support for kubernetes "type"

* Required refactoring to support reuse of the StringInSlice function.

* Cleaning up formatting

* Adding note about SkyDNS supporting word "any".

* baseUrl -> baseURL

* Also removed debug statement from core/setup/kubernetes.go

* Fixing test breaking from Url -> URL naming changes

* Changing record name template language ${...} -> {...}

* Fix formatting with go fmt

* Updating all k8sclient data getters to return error value

* Adding error message to k8sclient data accessors

* Cleaning up setup for kubernetes

* Removed verbose nils in initial k8s middleware instance
* Set reasonable defaults if CoreFile has no parameters in the
kubernetes block. (k8s endpoint, and name template)

* Formatting cleanup -- go fmt
2016-07-07 09:40:58 +01:00

166 lines
4.7 KiB
Go

package nametemplate
import (
"errors"
"fmt"
"strings"
"github.com/miekg/coredns/middleware/kubernetes/util"
)
// 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",
}
// 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.
type NameTemplate 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
}
func (t *NameTemplate) SetTemplate(s string) error {
var err error
fmt.Println()
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 + "'")
fmt.Printf("[debug] %v\n", err)
return err
} else {
fmt.Printf("[debug] Template string has static element '%v'\n", v)
}
}
}
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.
func (t *NameTemplate) GetZoneFromSegmentArray(segments []string) string {
if index, ok := t.Element["zone"]; !ok {
return ""
} else {
return strings.Join(segments[index:len(segments)], ".")
}
}
func (t *NameTemplate) GetNamespaceFromSegmentArray(segments []string) string {
return t.GetSymbolFromSegmentArray("namespace", segments)
}
func (t *NameTemplate) GetServiceFromSegmentArray(segments []string) string {
return t.GetSymbolFromSegmentArray("service", segments)
}
func (t *NameTemplate) GetTypeFromSegmentArray(segments []string) string {
typeSegment := t.GetSymbolFromSegmentArray("type", segments)
// Limit type to known types symbols
if util.StringInSlice(typeSegment, types) {
return ""
}
return typeSegment
}
func (t *NameTemplate) GetSymbolFromSegmentArray(symbol string, segments []string) string {
if index, ok := t.Element[symbol]; !ok {
return ""
} else {
return segments[index]
}
}
// GetRecordNameFromNameValues returns the string produced by applying the
// values to the NameTemplate format string.
func (t *NameTemplate) GetRecordNameFromNameValues(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, ".")
}
type NameValues struct {
ServiceName string
Namespace string
TypeName string
Zone string
}