groundwork (#140)

This commit is contained in:
Chris O'Haver 2019-04-08 16:21:40 -04:00 committed by GitHub
parent e6c1d12235
commit ac020ac1bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 1082 additions and 0 deletions

View file

@ -0,0 +1,178 @@
package corefile
import (
"github.com/mholt/caddy"
"strings"
)
type Corefile struct {
Servers []*Server
}
type Server struct {
DomPorts []string
Plugins []*Plugin
}
type Plugin struct {
Name string
Args []string
Options []*Option
}
type Option struct {
Name string
Args []string
}
func New(s string) (Corefile, error) {
c := Corefile{}
cc := caddy.NewTestController("migration", s)
depth := 0
var cSvr *Server
var cPlg *Plugin
for cc.Next() {
if cc.Val() == "{" {
depth += 1
continue
} else if cc.Val() == "}" {
depth -= 1
continue
}
val := cc.Val()
args := cc.RemainingArgs()
switch depth {
case 0:
c.Servers = append(c.Servers,
&Server{
DomPorts: append([]string{val}, args...),
})
cSvr = c.Servers[len(c.Servers)-1]
case 1:
cSvr.Plugins = append(cSvr.Plugins,
&Plugin{
Name: val,
Args: args,
})
cPlg = cSvr.Plugins[len(cSvr.Plugins)-1]
case 2:
cPlg.Options = append(cPlg.Options,
&Option{
Name: val,
Args: args,
})
}
}
return c, nil
}
func (c *Corefile) ToString() (out string) {
strs := []string{}
for _, s := range c.Servers {
strs = append(strs, s.ToString())
}
return strings.Join(strs, "\n")
}
func (s *Server) ToString() (out string) {
str := strings.Join(s.DomPorts, " ")
strs := []string{}
for _, p := range s.Plugins {
strs = append(strs, strings.Repeat(" ", indent)+p.ToString())
}
if len(strs) > 0 {
str += " {\n" + strings.Join(strs, "\n") + "\n}\n"
}
return str
}
func (p *Plugin) ToString() (out string) {
str := strings.Join(append([]string{p.Name}, p.Args...), " ")
strs := []string{}
for _, o := range p.Options {
strs = append(strs, strings.Repeat(" ", indent*2)+o.ToString())
}
if len(strs) > 0 {
str += " {\n" + strings.Join(strs, "\n") + "\n" + strings.Repeat(" ", indent*1) + "}"
}
return str
}
func (o *Option) ToString() (out string) {
str := strings.Join(append([]string{o.Name}, o.Args...), " ")
return str
}
func (s *Server) FindMatch(def []*Server) (*Server, bool) {
NextServer:
for _, sDef := range def {
for i, dp := range sDef.DomPorts {
if dp == "*" {
continue
}
if dp == "***" {
return sDef, true
}
if i >= len(s.DomPorts) || dp != s.DomPorts[i] {
continue NextServer
}
}
if len(sDef.DomPorts) != len(s.DomPorts) {
continue
}
return sDef, true
}
return nil, false
}
func (p *Plugin) FindMatch(def []*Plugin) (*Plugin, bool) {
NextPlugin:
for _, pDef := range def {
if pDef.Name != p.Name {
continue
}
for i, arg := range pDef.Args {
if arg == "*" {
continue
}
if arg == "***" {
return pDef, true
}
if i >= len(p.Args) || arg != p.Args[i] {
continue NextPlugin
}
}
if len(pDef.Args) != len(p.Args) {
continue
}
return pDef, true
}
return nil, false
}
func (o *Option) FindMatch(def []*Option) (*Option, bool) {
NextPlugin:
for _, oDef := range def {
if oDef.Name != o.Name {
continue
}
for i, arg := range oDef.Args {
if arg == "*" {
continue
}
if arg == "***" {
return oDef, true
}
if i >= len(o.Args) || arg != o.Args[i] {
continue NextPlugin
}
}
if len(oDef.Args) != len(o.Args) {
continue
}
return oDef, true
}
return nil, false
}
const indent = 4

View file

@ -0,0 +1,121 @@
package corefile
import (
"testing"
)
func TestCorefile(t *testing.T) {
startCorefile := `.:53 {
error
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
proxy . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
.:5353 {
proxy . /etc/resolv.conf
}
`
c, err := New(startCorefile)
if err != nil {
t.Error(err)
}
got := c.ToString()
if got != startCorefile {
t.Errorf("Corefile did not match expected.\nExpected:\n%v\nGot:\n%v", startCorefile, got)
}
}
func TestServer_FindMatch(t *testing.T) {
tests := []struct{
server *Server
match bool
}{
{server: &Server{DomPorts: []string{".:53"}}, match: true},
{server: &Server{DomPorts: []string{".:54"}}, match: false},
{server: &Server{DomPorts: []string{"abc:53"}}, match: false},
{server: &Server{DomPorts: []string{"abc:53", "blah"}}, match: true},
{server: &Server{DomPorts: []string{"abc:53", "blah", "blah"}}, match: false},
{server: &Server{DomPorts: []string{"xyz:53"}}, match: true},
{server: &Server{DomPorts: []string{"xyz:53", "blah", "blah"}}, match: true},
}
def := []*Server{
{DomPorts: []string{".:53"}},
{DomPorts: []string{"abc:53", "*"}},
{DomPorts: []string{"xyz:53", "***"}},
}
for i, test := range tests {
_, match := test.server.FindMatch(def)
if match != test.match {
t.Errorf("In test #%v, expected match to be %v but got %v.", i, test.match, match)
}
}
}
func TestPlugin_FindMatch(t *testing.T) {
tests := []struct{
plugin *Plugin
match bool
}{
{plugin: &Plugin{Name: "plugin1", Args: []string{}}, match: true},
{plugin: &Plugin{Name: "plugin2", Args: []string{"1","1.5","2"}}, match: true},
{plugin: &Plugin{Name: "plugin3", Args: []string{"1","2","3","4"}}, match: true},
{plugin: &Plugin{Name: "plugin1", Args: []string{"a"}}, match: false},
{plugin: &Plugin{Name: "plugin2", Args: []string{"1","1.5","b"}}, match: false},
{plugin: &Plugin{Name: "plugin3", Args: []string{"a","2","3","4"}}, match: false},
{plugin: &Plugin{Name: "plugin4", Args: []string{}}, match: false},
}
def := []*Plugin{
{Name: "plugin1", Args: []string{}},
{Name: "plugin2", Args: []string{"1", "*", "2"}},
{Name: "plugin3", Args: []string{"1", "***"}},
}
for i, test := range tests {
_, match := test.plugin.FindMatch(def)
if match != test.match {
t.Errorf("In test #%v, expected match to be %v but got %v.", i, test.match, match)
}
}
}
func TestOption_FindMatch(t *testing.T) {
tests := []struct{
option *Plugin
match bool
}{
{option: &Plugin{Name: "option1", Args: []string{}}, match: true},
{option: &Plugin{Name: "option2", Args: []string{"1","1.5","2"}}, match: true},
{option: &Plugin{Name: "option3", Args: []string{"1","2","3","4"}}, match: true},
{option: &Plugin{Name: "option1", Args: []string{"a"}}, match: false},
{option: &Plugin{Name: "option2", Args: []string{"1","1.5","b"}}, match: false},
{option: &Plugin{Name: "option3", Args: []string{"a","2","3","4"}}, match: false},
{option: &Plugin{Name: "option4", Args: []string{}}, match: false},
}
def := []*Plugin{
{Name: "option1", Args: []string{}},
{Name: "option2", Args: []string{"1", "*", "2"}},
{Name: "option3", Args: []string{"1", "***"}},
}
for i, test := range tests {
_, match := test.option.FindMatch(def)
if match != test.match {
t.Errorf("In test #%v, expected match to be %v but got %v.", i, test.match, match)
}
}
}

View file

@ -0,0 +1,231 @@
package migration
// This package provides a set of functions to help handle migrations of CoreDNS Corefiles to be compatible with new
// versions of CoreDNS. The task of upgrading CoreDNS is the responsibility of a variety of Kubernetes management tools
// (e.g. kubeadm and others), and the precise behavior may be different for each one. This library abstracts some basic
// helper functions that make this easier to implement.
import (
"github.com/coredns/deployment/kubernetes/migration/corefile"
)
// Deprecated returns a list of deprecated plugins or directives present in the Corefile. Each Notice returned is a
// warning, e.g. "plugin 'foo' is deprecated." An empty list returned means there are no deprecated plugins/options
// present in the Corefile.
func Deprecated(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string) ([]Notice, error) {
return getStatus(fromCoreDNSVersion, toCoreDNSVersion, corefileStr, deprecated)
}
// Removed returns a list of removed plugins or directives present in the Corefile. Each Notice returned is a warning,
// e.g. "plugin 'foo' is no longer supported." An empty list returned means there are no removed plugins/options
// present in the Corefile.
func Removed(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string) ([]Notice, error) {
return getStatus(fromCoreDNSVersion, toCoreDNSVersion, corefileStr, removed)
}
// Unsupported returns a list of plugins that are not recognized/supported by the migration tool (but may still be valid in CoreDNS).
func Unsupported(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string) ([]Notice, error) {
return getStatus(fromCoreDNSVersion, toCoreDNSVersion, corefileStr, unsupported)
}
func getStatus(fromCoreDNSVersion, toCoreDNSVersion, corefileStr, status string) ([]Notice, error) {
notices := []Notice{}
cf, err := corefile.New(corefileStr)
if err != nil {
return notices, err
}
v := fromCoreDNSVersion
for {
v = Versions[v].nextVersion
for _, s := range cf.Servers {
for _, p := range s.Plugins {
vp, present := Versions[v].plugins[p.Name]
if status == unsupported {
if present {
continue
}
notices = append(notices, Notice{Plugin: p.Name, Severity: status, Version: v})
continue
}
if !present {
continue
}
if vp.status == status {
notices = append(notices, Notice{
Plugin: p.Name,
Severity: status,
Version: v,
ReplacedBy: vp.replacedBy,
Additional: vp.additional,
})
continue
}
for _, o := range p.Options {
vo, present := Versions[v].plugins[p.Name].options[o.Name]
if status == unsupported {
if present {
continue
}
notices = append(notices, Notice{
Plugin: p.Name,
Option: o.Name,
Severity: status,
Version: v,
ReplacedBy: vo.replacedBy,
Additional: vo.additional,
})
continue
}
if !present {
continue
}
if vo.status == status {
notices = append(notices, Notice{Plugin: p.Name, Option: o.Name, Severity: status, Version: v})
continue
}
}
}
}
if v == toCoreDNSVersion {
break
}
}
return notices, nil
}
// Migrate returns version of the Corefile migrated to toCoreDNSVersion, or an error if it cannot.
func Migrate(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string, deprecations bool) (string, error) {
cf, err := corefile.New(corefileStr)
if err != nil {
return "", err
}
v := fromCoreDNSVersion
for {
v = Versions[v].nextVersion
newSrvs := []*corefile.Server{}
for _, s := range cf.Servers {
newPlugs := []*corefile.Plugin{}
for _, p := range s.Plugins {
vp, present := Versions[v].plugins[p.Name]
if !present {
newPlugs = append(newPlugs, p)
continue
}
if !deprecations && vp.status == deprecated {
newPlugs = append(newPlugs, p)
continue
}
if vp.action != nil {
p, err := vp.action(p)
if err != nil {
return "", err
}
if p == nil {
// remove plugin, skip options processing
continue
}
}
newOpts := []*corefile.Option{}
for _, o := range p.Options {
vo, present := Versions[v].plugins[p.Name].options[o.Name]
if !present {
newOpts = append(newOpts, o)
continue
}
if !deprecations && vo.status == deprecated {
newOpts = append(newOpts, o)
continue
}
if vo.action == nil {
newOpts = append(newOpts, o)
continue
}
o, err := vo.action(o)
if err != nil {
return "", err
}
if o == nil {
// remove option
continue
}
newOpts = append(newOpts, o)
}
newPlugs = append(newPlugs,
&corefile.Plugin{
Name: p.Name,
Args: p.Args,
Options: newOpts,
})
}
newSrvs = append(newSrvs,
&corefile.Server{
DomPorts: s.DomPorts,
Plugins: newPlugs,
},
)
}
cf = corefile.Corefile{Servers: newSrvs}
if v == toCoreDNSVersion {
break
}
}
return cf.ToString(), nil
}
// Default returns true if the Corefile is the default for a given version of Kubernetes.
// Or, if k8sVersion is empty, Default returns true if the Corefile is the default for any version of Kubernetes.
func Default(k8sVersion, corefileStr string) bool {
cf, err := corefile.New(corefileStr)
if err != nil {
return false
}
NextVersion:
for _, v := range Versions {
if k8sVersion != "" && k8sVersion != v.k8sRelease {
continue
}
defCf, err := corefile.New(v.defaultConf)
if err != nil {
continue
}
// check corefile against k8s release default
if len(cf.Servers) != len(defCf.Servers) {
continue NextVersion
}
for _, s := range cf.Servers {
defS, found := s.FindMatch(defCf.Servers)
if !found {
continue NextVersion
}
if len(s.Plugins) != len(defS.Plugins) {
continue NextVersion
}
for _, p := range s.Plugins {
defP, found := p.FindMatch(defS.Plugins)
if !found {
continue NextVersion
}
if len(p.Options) != len(defP.Options) {
continue NextVersion
}
for _, o := range p.Options {
_, found := o.FindMatch(defP.Options)
if !found {
continue NextVersion
}
}
}
}
return true
}
return false
}
// Released returns true if dockerImageID matches any released image of CoreDNS.
func Released(dockerImageID string) bool {
for _, v := range Versions {
if v.dockerImageID == dockerImageID {
return true
}
}
return false
}

View file

@ -0,0 +1,254 @@
package migration
import (
"testing"
)
func TestMigrate(t *testing.T) {
startCorefile := `.:53 {
#mycomment
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
proxy . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
`
expected := `.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
`
result, err := Migrate("1.3.1", "1.5.0", startCorefile, true)
if err != nil {
t.Errorf("%v", err)
}
if result != expected {
t.Errorf("expected %v; got %v", expected, result)
}
}
func TestDeprecated(t *testing.T) {
startCorefile := `.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
proxy . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
`
expected := []Notice{
{Plugin: "kubernetes", Option: "upstream", Severity: deprecated, Version: "1.4.0"},
{Plugin: "proxy", Severity: deprecated, ReplacedBy: "forward", Version: "1.4.0"},
}
result, err := Deprecated("1.3.1", "1.5.0", startCorefile)
if err != nil {
t.Fatal(err)
}
if len(result) != len(expected) {
t.Fatalf("expected to find %v deprecations; got %v", len(expected), len(result))
}
for i, dep := range expected {
if result[i].ToString() != dep.ToString() {
t.Errorf("expected to get '%v'; got '%v'", dep.ToString(), result[i].ToString())
}
}
}
func TestRemoved(t *testing.T) {
startCorefile := `.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
proxy . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
`
expected := []Notice{
{Plugin: "proxy", Severity: removed, ReplacedBy: "forward", Version: "1.5.0"},
}
result, err := Removed("1.3.1", "1.5.0", startCorefile)
if err != nil {
t.Fatal(err)
}
if len(result) != len(expected) {
t.Fatalf("expected to find %v deprecations; got %v", len(expected), len(result))
}
for i, dep := range expected {
if result[i].ToString() != dep.ToString() {
t.Errorf("expected to get '%v'; got '%v'", dep.ToString(), result[i].ToString())
}
}
}
func TestUnsupported(t *testing.T) {
startCorefile := `.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
route53 example.org.:Z1Z2Z3Z4DZ5Z6Z7
prometheus :9153
proxy . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
`
expected := []Notice{
{Plugin: "route53", Severity: unsupported, Version: "1.4.0"},
{Plugin: "route53", Severity: unsupported, Version: "1.5.0"},
}
result, err := Unsupported("1.3.1", "1.5.0", startCorefile)
if err != nil {
t.Fatal(err)
}
if len(result) != len(expected) {
t.Fatalf("expected to find %v deprecations; got %v", len(expected), len(result))
}
for i, dep := range expected {
if result[i].ToString() != dep.ToString() {
t.Errorf("expected to get '%v'; got '%v'", dep.ToString(), result[i].ToString())
}
}
}
func TestDefault(t *testing.T) {
defaultCorefiles := []string{`.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
`,
`.:53 {
errors
health
kubernetes myzone.org in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
`}
nonDefaultCorefiles := []string{`.:53 {
errors
health
rewrite name suffix myzone.org cluster.local
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
`,
`.:53 {
errors
health
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . /etc/resolv.conf
cache 30
loop
reload
loadbalance
}
stubzone.org:53 {
forward . 1.2.3.4
}
`}
for _, d := range defaultCorefiles {
if !Default("", d) {
t.Errorf("expected config to be identified as a default: %v", d)
}
}
for _, d := range nonDefaultCorefiles {
if Default("", d) {
t.Errorf("expected config to NOT be identified as a default: %v", d)
}
}
}

View file

@ -0,0 +1,40 @@
package migration
import "fmt"
// Notice is a migration warning
type Notice struct {
Plugin string
Option string
Severity string // 'deprecated', 'removed', or 'unsupported'
ReplacedBy string
Additional string
Version string
}
func (n *Notice) ToString() string {
s := ""
if n.Option == "" {
s += fmt.Sprintf(`Plugin "%v" `, n.Plugin)
} else {
s += fmt.Sprintf(`Option "%v" in plugin "%v" `, n.Option, n.Plugin)
}
if n.Severity == unsupported {
s += "is unsupported by this migration tool in " + n.Version + "."
} else {
s += "was " + n.Severity + " in " + n.Version + "."
}
if n.ReplacedBy != "" {
s += fmt.Sprintf(` It is replaced by "%v".`, n.ReplacedBy)
}
if n.Additional != "" {
s += " " + n.Additional
}
return s
}
const (
deprecated = "deprecated" // plugin/option is deprecated
removed = "removed" // plugin/option has been removed
unsupported = "unsupported" // plugin/option is not supported by the migration tool
)

View file

@ -0,0 +1,258 @@
package migration
import (
"github.com/coredns/deployment/kubernetes/migration/corefile"
)
type plugin struct {
status string
replacedBy string
additional string
action pluginActionFn
options map[string]option
}
type option struct {
status string
replacedBy string
additional string
action optionActionFn
}
type release struct {
k8sRelease string
nextVersion string
dockerImageID string
plugins map[string]plugin
// defaultConf hold the default Corefile template packaged with the corresponding k8sRelease.
// Wildcards are used for fuzzy matching:
// "*" matches exactly one token
// "***" matches 0 all remaining tokens on the line
// Order of server blocks, plugins, and options does not matter.
// Order of arguments does matter.
defaultConf string
}
type pluginActionFn func(*corefile.Plugin) (*corefile.Plugin, error)
type optionActionFn func(*corefile.Option) (*corefile.Option, error)
func removePlugin(*corefile.Plugin) (*corefile.Plugin, error) { return nil, nil }
func removeOption(*corefile.Option) (*corefile.Option, error) { return nil, nil }
func renamePlugin(p *corefile.Plugin, to string) (*corefile.Plugin, error) {
p.Name = to
return p, nil
}
var Versions = map[string]release{
"1.5.0": {
dockerImageID: "7987f0908caf",
plugins: map[string]plugin{
"errors": {
options: map[string]option{
"consolidate": {},
},
},
"health": {},
"autopath": {},
"kubernetes": {
options: map[string]option{
"resyncperiod": {
status: deprecated,
action: removeOption,
},
"endpoint": {},
"tls": {},
"kubeconfig": {},
"namespaces": {},
"labels": {},
"pods": {},
"endpoint_pod_names": {},
"ttl": {},
"noendpoints": {},
"transfer": {},
"fallthrough": {},
"ignore": {},
},
},
"k8s_external": {
options: map[string]option{
"apex": {},
"ttl": {},
},
},
"prometheus": {},
"proxy": {
status: removed,
replacedBy: "forward",
action: proxyToForwardPluginAction,
options: proxyToForwardOptionsMigrations,
},
"forward": {},
"cache": {
options: map[string]option{
"success": {},
"denial": {},
"prefetch": {},
},
},
"loop": {},
"reload": {},
"loadbalance": {},
},
},
"1.4.0": {
nextVersion: "1.5.0",
dockerImageID: "a9e015907f63",
plugins: map[string]plugin{
"errors": {
options: map[string]option{
"consolidate": {},
},
},
"health": {},
"autopath": {},
"kubernetes": {
options: map[string]option{
"resyncperiod": {},
"endpoint": {}, // TODO: Multiple endpoint deprecation
"tls": {},
"kubeconfig": {},
"namespaces": {},
"labels": {},
"pods": {},
"endpoint_pod_names": {},
"upstream": {
status: deprecated,
action: removeOption,
},
"ttl": {},
"noendpoints": {},
"transfer": {},
"fallthrough": {},
"ignore": {},
},
},
"k8s_external": {
options: map[string]option{
"apex": {},
"ttl": {},
},
},
"prometheus": {},
"proxy": {
status: deprecated,
replacedBy: "forward",
action: proxyToForwardPluginAction,
options: proxyToForwardOptionsMigrations,
},
"forward": {},
"cache": {
options: map[string]option{
"success": {},
"denial": {},
"prefetch": {},
},
},
"loop": {},
"reload": {},
"loadbalance": {},
},
},
"1.3.1": {
nextVersion: "1.4.0",
k8sRelease: "1.14",
dockerImageID: "eb516548c180",
defaultConf: `.:53 {
errors
health
kubernetes * *** {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
forward . *
cache 30
loop
reload
loadbalance
}`,
plugins: map[string]plugin{
"errors": {
options: map[string]option{
"consolidate": {},
},
},
"health": {},
"autopath": {},
"kubernetes": {
options: map[string]option{
"resyncperiod": {},
"endpoint": {}, // TODO: Multiple endpoint deprecation
"tls": {},
"kubeconfig": {},
"namespaces": {},
"labels": {},
"pods": {},
"endpoint_pod_names": {},
"upstream": {},
"ttl": {},
"noendpoints": {},
"transfer": {},
"fallthrough": {},
"ignore": {},
},
},
"k8s_external": {
options: map[string]option{
"apex": {},
"ttl": {},
},
},
"prometheus": {},
"proxy": {},
"forward": {},
"cache": {
options: map[string]option{
"success": {},
"denial": {},
"prefetch": {},
},
},
"loop": {},
"reload": {},
"loadbalance": {},
},
},
}
var proxyToForwardOptionsMigrations = map[string]option{
"policy": {
action: func(o *corefile.Option) (*corefile.Option, error) {
if len(o.Args) == 2 && o.Args[1] == "least_conn" {
o.Name = "force_tcp"
o.Args = nil
}
return o, nil
},
},
"except": {},
"fail_timeout": {action: removeOption},
"max_fails": {action: removeOption},
"health_check": {action: removeOption},
"spray": {action: removeOption},
"protocol": {
action: func(o *corefile.Option) (*corefile.Option, error) {
if len(o.Args) >= 2 && o.Args[1] == "force_tcp" {
o.Name = "force_tcp"
o.Args = nil
return o, nil
}
return nil, nil
},
},
}
var proxyToForwardPluginAction = func(p *corefile.Plugin) (*corefile.Plugin, error) { return renamePlugin(p, "forward") }