mirror of
https://github.com/notherealmarco/coredns-deployment.git
synced 2025-05-05 12:32:34 +02: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…
Add table
Add a link
Reference in a new issue