* 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
166 lines
4.7 KiB
Go
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
|
|
}
|