mirror of
https://github.com/notherealmarco/coredns-deployment.git
synced 2025-03-14 14:16:16 +01:00
groundwork (#140)
This commit is contained in:
parent
e6c1d12235
commit
ac020ac1bc
6 changed files with 1082 additions and 0 deletions
178
kubernetes/migration/corefile/corefile.go
Normal file
178
kubernetes/migration/corefile/corefile.go
Normal 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
|
121
kubernetes/migration/corefile/corefile_test.go
Normal file
121
kubernetes/migration/corefile/corefile_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
231
kubernetes/migration/migrate.go
Normal file
231
kubernetes/migration/migrate.go
Normal 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
|
||||
}
|
254
kubernetes/migration/migrate_test.go
Normal file
254
kubernetes/migration/migrate_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
40
kubernetes/migration/notice.go
Normal file
40
kubernetes/migration/notice.go
Normal 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
|
||||
)
|
258
kubernetes/migration/versions.go
Normal file
258
kubernetes/migration/versions.go
Normal 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") }
|
Loading…
Reference in a new issue