mirror of
https://github.com/notherealmarco/coredns-deployment.git
synced 2025-03-14 14:16:16 +01:00
code moved (#186)
This commit is contained in:
parent
2900b2a671
commit
2f535e77e2
23 changed files with 3 additions and 3493 deletions
3
kubernetes/corefile-tool/.gitignore
vendored
3
kubernetes/corefile-tool/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
Corefile
|
||||
corefile-tool
|
||||
*.yaml
|
|
@ -1,73 +1,3 @@
|
|||
## Corefile-tool
|
||||
|
||||
Corefile-tool is a command line tool which helps you to evaluate and migrate your CoreDNS Corefile Configuration.
|
||||
It uses the [CoreDNS migration tool library](https://github.com/coredns/deployment/tree/master/kubernetes/migration).
|
||||
|
||||
## Usage
|
||||
|
||||
Use the following syntax to run the `corefile-tool` command:
|
||||
|
||||
```
|
||||
Usage:
|
||||
corefile-tool default --corefile <path> [--k8sversion <k8s-ver>]
|
||||
corefile-tool deprecated --from <coredns-ver> --to <coredns-ver> --corefile <path>
|
||||
corefile-tool migrate --from <coredns-ver> --to <coredns-ver> --corefile <path> [--deprecations <true|false>]
|
||||
corefile-tool downgrade --from <coredns-ver> --to <coredns-ver> --corefile <path>
|
||||
corefile-tool released --dockerImageId <id>
|
||||
corefile-tool unsupported --from <coredns-ver> --to <coredns-ver> --corefile <path>
|
||||
corefile-tool validversions
|
||||
```
|
||||
|
||||
|
||||
### Operations
|
||||
|
||||
The following operations are supported:
|
||||
|
||||
- `default`: returns true if the Corefile is the default for the given version of Kubernetes. If `--k8sversion` is not specified, then this will return true if the Corefile is the default for any version of Kubernetes supported by the tool.
|
||||
|
||||
- `deprecated`: returns a list of plugins/options in the Corefile that have been deprecated, removed, ignored or is a new default plugin/option.
|
||||
|
||||
- `migrate`: updates your CoreDNS corefile to be compatible with the `-to` version. Setting the `--deprecations` flag to `true` will migrate plugins/options as soon as they are announced as deprecated. Setting the `--deprecations` flag to `false` will migrate plugins/options only once they are removed (or made a no-op). The default is `false`.
|
||||
|
||||
- `downgrade` : downgrades your CoreDNS corefile to be compatible with the `-to` version. It will not restore plugins/options that might have been removed or altered during an upward migration.
|
||||
|
||||
- `released`: determines if the `--dockerImageID` was an official CoreDNS release or not. Only official releases of CoreDNS are supported by the tool.
|
||||
|
||||
- `unsupported`: returns a list of plugins/options in the Corefile that are not supported by the migration tool (but may still be valid in CoreDNS).
|
||||
|
||||
- `validversions`: Shows the list of CoreDNS versions supported by the this tool.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
The following examples will help you understand the basic usage of the migration tool.
|
||||
|
||||
```bash
|
||||
# See if the Corefile is the default in CoreDNS v1.4.0.
|
||||
corefile-tool default --k8sversion 1.4.0 --corefile /path/to/Corefile
|
||||
```
|
||||
|
||||
```bash
|
||||
# See deprecated plugins CoreDNS from v1.4.0 to v1.5.0.
|
||||
corefile-tool deprecated --from 1.4.0 --to 1.5.0 --corefile /path/to/Corefile
|
||||
```
|
||||
|
||||
```bash
|
||||
# See unsupported plugins CoreDNS from v1.4.0 to v1.5.0.
|
||||
corefile-tool unsupported --from 1.4.0 --to 1.5.0 --corefile /path/to/Corefile
|
||||
```
|
||||
|
||||
```bash
|
||||
# Migrate CoreDNS from v1.4.0 to v1.5.0 and also migrate all the deprecations
|
||||
# that are present in the current Corefile.
|
||||
corefile-tool migrate --from 1.4.0 --to 1.5.0 --corefile /path/to/Corefile --deprecations true
|
||||
|
||||
# Migrate CoreDNS from v1.2.2 to v1.3.1 and do not also migrate all the deprecations
|
||||
# that are present in the current Corefile.
|
||||
corefile-tool migrate --from 1.2.2 --to 1.3.1 --corefile /path/to/Corefile --deprecations false
|
||||
```
|
||||
```bash
|
||||
# Downgrade CoreDNS from v1.5.0 to v1.4.0
|
||||
corefile-tool downgrade --from 1.5.0 --to 1.4.0 --corefile /path/to/Corefile
|
||||
```
|
||||
|
||||
Corefile-tool has moved to https://github.com/coredns/corefile-migration/tree/master/corefile-tool
|
|
@ -1,47 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDefaultCmd represents the default command
|
||||
func NewDefaultCmd() *cobra.Command {
|
||||
defaultCmd := &cobra.Command{
|
||||
Use: "default",
|
||||
Short: "default returns true if the Corefile is the default for a that version of Kubernetes. If the Kubernetes version is omitted, returns true if the Corefile is the default for any version.",
|
||||
Example: `# See if the Corefile is the default in CoreDNS v1.4.0.
|
||||
corefile-tool default --k8sversion 1.4.0 --corefile /path/to/Corefile`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
k8sversion, _ := cmd.Flags().GetString("k8sversion")
|
||||
corefile, _ := cmd.Flags().GetString("corefile")
|
||||
|
||||
isDefault, err := defaultCorefileFromPath(k8sversion, corefile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while checking if the Corefile is the default: %v \n", err)
|
||||
}
|
||||
fmt.Println(isDefault)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
defaultCmd.Flags().String("k8sversion", "", "The Kubernetes version for which you are checking the default.")
|
||||
defaultCmd.Flags().String("corefile", "", "Required: The path where your Corefile is located.")
|
||||
defaultCmd.MarkFlagRequired("corefile")
|
||||
|
||||
return defaultCmd
|
||||
}
|
||||
|
||||
// defaultCorefileFromPath takes the path where the Corefile is located and checks
|
||||
// if the Corefile is the default for that version of Kubernetes.
|
||||
func defaultCorefileFromPath(k8sVersion, corefilePath string) (bool, error) {
|
||||
fileBytes, err := getCorefileFromPath(corefilePath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
corefileStr := string(fileBytes)
|
||||
return migration.Default(k8sVersion, corefileStr), nil
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDeprecatedCmd represents the deprecated command
|
||||
func NewDeprecatedCmd() *cobra.Command {
|
||||
deprecatedCmd := &cobra.Command{
|
||||
Use: "deprecated",
|
||||
Short: "Deprecated returns a list of deprecated, removed, ignored and new default plugins or directives present in the Corefile.",
|
||||
Example: `# See deprecated, removed, ignored and new default plugins CoreDNS from v1.4.0 to v1.5.0.
|
||||
corefile-tool deprecated --from 1.4.0 --to 1.5.0 --corefile /path/to/Corefile`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
from, _ := cmd.Flags().GetString("from")
|
||||
to, _ := cmd.Flags().GetString("to")
|
||||
corefile, _ := cmd.Flags().GetString("corefile")
|
||||
deprecated, err := deprecatedCorefileFromPath(from, to, corefile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while listing deprecated plugins: %v \n", err)
|
||||
}
|
||||
for _, dep := range deprecated {
|
||||
fmt.Println(dep.ToString())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
deprecatedCmd.Flags().String("from", "", "Required: The version you are migrating from. ")
|
||||
deprecatedCmd.MarkFlagRequired("from")
|
||||
deprecatedCmd.Flags().String("to", "", "Required: The version you are migrating to.")
|
||||
deprecatedCmd.MarkFlagRequired("to")
|
||||
deprecatedCmd.Flags().String("corefile", "", "Required: The path where your Corefile is located.")
|
||||
deprecatedCmd.MarkFlagRequired("corefile")
|
||||
|
||||
return deprecatedCmd
|
||||
}
|
||||
|
||||
// deprecatedCorefileFromPath takes the path where the Corefile is located and returns the deprecated plugins or directives
|
||||
// present in the Corefile.
|
||||
func deprecatedCorefileFromPath(fromCoreDNSVersion, toCoreDNSVersion, corefilePath string) ([]migration.Notice, error) {
|
||||
fileBytes, err := getCorefileFromPath(corefilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
corefileStr := string(fileBytes)
|
||||
return migration.Deprecated(fromCoreDNSVersion, toCoreDNSVersion, corefileStr)
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewDowngradeCmd represents the downgrade command
|
||||
func NewDowngradeCmd() *cobra.Command {
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "downgrade",
|
||||
Short: "Downgrade your CoreDNS corefile to a previous version",
|
||||
Example: `# Downgrade CoreDNS from v1.5.0 to v1.4.0.
|
||||
corefile-tool downgrade --from 1.5.0 --to 1.4.0 --corefile /path/to/Corefile`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
from, _ := cmd.Flags().GetString("from")
|
||||
to, _ := cmd.Flags().GetString("to")
|
||||
corefile, _ := cmd.Flags().GetString("corefile")
|
||||
|
||||
migrated, err := downgradeCorefileFromPath(from, to, corefile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while migration: %v \n", err)
|
||||
}
|
||||
fmt.Println(migrated)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
migrateCmd.Flags().String("from", "", "Required: The version you are migrating from. ")
|
||||
migrateCmd.MarkFlagRequired("from")
|
||||
migrateCmd.Flags().String("to", "", "Required: The version you are migrating to.")
|
||||
migrateCmd.MarkFlagRequired("to")
|
||||
migrateCmd.Flags().String("corefile", "", "Required: The path where your Corefile is located.")
|
||||
migrateCmd.MarkFlagRequired("corefile")
|
||||
migrateCmd.Flags().Bool("deprecations", false, "Specify whether you want to handle plugin deprecations. [True | False] ")
|
||||
|
||||
return migrateCmd
|
||||
}
|
||||
|
||||
// downgradeCorefileFromPath takes the path where the Corefile is located and downgrades the Corefile to the
|
||||
// desrired version.
|
||||
func downgradeCorefileFromPath(fromCoreDNSVersion, toCoreDNSVersion, corefilePath string) (string, error) {
|
||||
fileBytes, err := getCorefileFromPath(corefilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
corefileStr := string(fileBytes)
|
||||
return migration.MigrateDown(fromCoreDNSVersion, toCoreDNSVersion, corefileStr)
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewMigrateCmd represents the migrate command
|
||||
func NewMigrateCmd() *cobra.Command {
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Migrate your CoreDNS corefile",
|
||||
Example: `# Migrate CoreDNS from v1.4.0 to v1.5.0 and handle deprecations .
|
||||
corefile-tool migrate --from 1.4.0 --to 1.5.0 --corefile /path/to/Corefile --deprecations true
|
||||
|
||||
# Migrate CoreDNS from v1.2.2 to v1.3.1 and do not handle deprecations .
|
||||
corefile-tool migrate --from 1.2.2 --to 1.3.1 --corefile /path/to/Corefile --deprecations false`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
from, _ := cmd.Flags().GetString("from")
|
||||
to, _ := cmd.Flags().GetString("to")
|
||||
corefile, _ := cmd.Flags().GetString("corefile")
|
||||
deprecations, _ := cmd.Flags().GetBool("deprecations")
|
||||
|
||||
migrated, err := migrateCorefileFromPath(from, to, corefile, deprecations)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while migration: %v \n", err)
|
||||
}
|
||||
fmt.Println(migrated)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
migrateCmd.Flags().String("from", "", "Required: The version you are migrating from. ")
|
||||
migrateCmd.MarkFlagRequired("from")
|
||||
migrateCmd.Flags().String("to", "", "Required: The version you are migrating to.")
|
||||
migrateCmd.MarkFlagRequired("to")
|
||||
migrateCmd.Flags().String("corefile", "", "Required: The path where your Corefile is located.")
|
||||
migrateCmd.MarkFlagRequired("corefile")
|
||||
migrateCmd.Flags().Bool("deprecations", false, "Specify whether you want to handle plugin deprecations. [True | False] ")
|
||||
|
||||
return migrateCmd
|
||||
}
|
||||
|
||||
// migrateCorefileFromPath takes the path where the Corefile is located and migrates the Corefile to the
|
||||
// desrired version.
|
||||
func migrateCorefileFromPath(fromCoreDNSVersion, toCoreDNSVersion, corefilePath string, deprecations bool) (string, error) {
|
||||
fileBytes, err := getCorefileFromPath(corefilePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
corefileStr := string(fileBytes)
|
||||
return migration.Migrate(fromCoreDNSVersion, toCoreDNSVersion, corefileStr, deprecations)
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewReleasedCmd represents the released command
|
||||
func NewReleasedCmd() *cobra.Command {
|
||||
releasedCmd := &cobra.Command{
|
||||
Use: "released",
|
||||
Short: "Determines whether your Docker Image SHA of a CoreDNS release is valid or not",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
image, _ := cmd.Flags().GetString("dockerImageSHA")
|
||||
result := migration.Released(image)
|
||||
|
||||
if result {
|
||||
fmt.Println("The docker image SHA is valid")
|
||||
} else {
|
||||
fmt.Println("The docker image SHA is invalid")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
releasedCmd.Flags().String("dockerImageSHA", "", "Required: The docker image SHA you want to check. ")
|
||||
releasedCmd.MarkFlagRequired("dockerImageSHA")
|
||||
|
||||
return releasedCmd
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/lithammer/dedent"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// CorefileTool represents the base command for the corefile-tool.
|
||||
func CorefileTool() *cobra.Command {
|
||||
rootCmd := &cobra.Command{
|
||||
Use: "corefile-tool",
|
||||
Short: "A brief description of your application",
|
||||
Long: dedent.Dedent(`
|
||||
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ CoreDNS Migration Tool │
|
||||
│ Easily Migrate your Corefile │
|
||||
│ │
|
||||
│ Please give us feedback at: │
|
||||
│ https://github.com/coredns/deployment/issues │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
|
||||
`),
|
||||
}
|
||||
rootCmd.AddCommand(NewMigrateCmd())
|
||||
rootCmd.AddCommand(NewDowngradeCmd())
|
||||
rootCmd.AddCommand(NewDefaultCmd())
|
||||
rootCmd.AddCommand(NewDeprecatedCmd())
|
||||
rootCmd.AddCommand(NewUnsupportedCmd())
|
||||
rootCmd.AddCommand(NewValidVersionsCmd())
|
||||
rootCmd.AddCommand(NewReleasedCmd())
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
func Execute() {
|
||||
if err := CorefileTool().Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getCorefileFromPath(corefilePath string) ([]byte, error) {
|
||||
if _, err := os.Stat(corefilePath); os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileBytes, err := ioutil.ReadFile(corefilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fileBytes, nil
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewUnsupportedCmd represents the unsupported command
|
||||
func NewUnsupportedCmd() *cobra.Command {
|
||||
unsupportedCmd := &cobra.Command{
|
||||
Use: "unsupported",
|
||||
Short: "Unsupported returns a list of plugins that are not recognized/supported by the migration tool (but may still be valid in CoreDNS).",
|
||||
Example: `# See unsupported plugins CoreDNS from v1.4.0 to v1.5.0.
|
||||
corefile-tool unsupported --from 1.4.0 --to 1.5.0 --corefile /path/to/Corefile`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
from, _ := cmd.Flags().GetString("from")
|
||||
to, _ := cmd.Flags().GetString("to")
|
||||
corefile, _ := cmd.Flags().GetString("corefile")
|
||||
unsupported, err := unsupportedCorefileFromPath(from, to, corefile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while listing deprecated plugins: %v \n", err)
|
||||
}
|
||||
for _, unsup := range unsupported {
|
||||
fmt.Println(unsup.ToString())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
unsupportedCmd.Flags().String("from", "", "Required: The version you are migrating from. ")
|
||||
unsupportedCmd.MarkFlagRequired("from")
|
||||
unsupportedCmd.Flags().String("to", "", "Required: The version you are migrating to.")
|
||||
unsupportedCmd.MarkFlagRequired("to")
|
||||
unsupportedCmd.Flags().String("corefile", "", "Required: The path where your Corefile is located.")
|
||||
unsupportedCmd.MarkFlagRequired("corefile")
|
||||
|
||||
return unsupportedCmd
|
||||
}
|
||||
|
||||
// unsupportedCorefileFromPath takes the path where the Corefile is located and returns a list of plugins
|
||||
// that have been removed.
|
||||
func unsupportedCorefileFromPath(fromCoreDNSVersion, toCoreDNSVersion, corefilePath string) ([]migration.Notice, error) {
|
||||
fileBytes, err := getCorefileFromPath(corefilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
corefileStr := string(fileBytes)
|
||||
return migration.Unsupported(fromCoreDNSVersion, toCoreDNSVersion, corefileStr)
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewValidVersionsCmd represents the validversions command
|
||||
func NewValidVersionsCmd() *cobra.Command {
|
||||
validversionsCmd := &cobra.Command{
|
||||
Use: "validversions",
|
||||
Short: "Shows valid versions of CoreDNS",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("The following are valid CoreDNS versions:")
|
||||
fmt.Println(strings.Join(migration.ValidVersions(), ", "))
|
||||
},
|
||||
}
|
||||
return validversionsCmd
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package main
|
||||
|
||||
import "github.com/coredns/deployment/kubernetes/corefile-tool/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
|
@ -1,106 +1,3 @@
|
|||
# K8s/CoreDNS Corefile Migration Tools
|
||||
|
||||
This Go library 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.
|
||||
|
||||
## Notifications
|
||||
|
||||
Several functions in the library return a list of Notices. Each Notice is a warning of a feature deprecation,
|
||||
an unsupported plugin/option, or a new required plugin/option added to the Corefile. A Notice has a `ToString()`
|
||||
For display to an end user. e.g.
|
||||
|
||||
```
|
||||
Plugin "foo" is deprecated in <version>. It is replaced by "bar".
|
||||
Plugin "bar" is removed in <version>. It is replaced by "qux".
|
||||
Option "foo" in plugin "bar" is added as a default in <version>.
|
||||
Plugin "baz" is unsupported by this migration tool in <version>.
|
||||
```
|
||||
|
||||
|
||||
## Functions
|
||||
|
||||
### func Deprecated
|
||||
|
||||
`Deprecated(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string) ([]Notice, error)`
|
||||
|
||||
Deprecated returns a list of deprecation notices affecting the given Corefile. Notices are returned for
|
||||
any deprecated, removed, or ignored plugins/options present in the Corefile. Notices are also returned for
|
||||
any new default plugins that would be added in a migration.
|
||||
|
||||
### func Migrate
|
||||
|
||||
`Migrate(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string, deprecations bool) (string, error)`
|
||||
|
||||
Migrate returns a migrated version of the Corefile, or an error if it cannot. The _to_ version
|
||||
must be >= the _from_ version. It will:
|
||||
* replace/convert any plugins/options that have replacements (e.g. _proxy_ -> _forward_)
|
||||
* return an error if replacable plugins/options cannot be converted (e.g. proxy _options_ not available in _forward_)
|
||||
* remove plugins/options that do not have replacements (e.g. kubernetes `upstream`)
|
||||
* add in any new default plugins where applicable if they are not already present.
|
||||
* If deprecations is true, deprecated plugins/options will be migrated as soon as they are deprecated.
|
||||
* If deprecations is false, deprecated plugins/options will be migrated only once they become removed or ignored.
|
||||
|
||||
### func MigrateDown
|
||||
|
||||
`MigrateDown(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string) (string, error)`
|
||||
|
||||
MigrateDown returns a downward migrated version of the Corefile, or an error if it cannot. The _to_ version
|
||||
must be <= the _from_ version.
|
||||
* It will handle the removal of plugins and options that no longer exist in the destination
|
||||
version when downgrading.
|
||||
* It will not restore plugins/options that might have been removed or altered during an upward migration.
|
||||
|
||||
### func Unsupported
|
||||
|
||||
`Unsupported(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string) ([]Notice, error)`
|
||||
|
||||
Unsupported returns a list Notices for plugins/options that are unhandled by this migration tool,
|
||||
but may still be valid in CoreDNS. Currently, only a subset of plugins included by default in CoreDNS are supported
|
||||
by this tool.
|
||||
|
||||
|
||||
### func Default
|
||||
|
||||
`Default(k8sVersion, corefileStr string) bool`
|
||||
|
||||
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 Released
|
||||
|
||||
`Released(dockerImageSHA string) bool`
|
||||
|
||||
Released returns true if dockerImageSHA matches any released image of CoreDNS.
|
||||
|
||||
|
||||
### func ValidVersions
|
||||
|
||||
`ValidVersions() []string`
|
||||
|
||||
ValidVersions returns a list of all versions supported by this tool.
|
||||
|
||||
|
||||
## Command Line Converter Example
|
||||
|
||||
An example use of this library is provided [here](../corefile-tool/).
|
||||
|
||||
|
||||
## Kubernetes Cluster Managemnt Tool Usage
|
||||
|
||||
This is an example flow of how this library could be used by a Kubernetes cluster management tool to perform a
|
||||
Corefile migration during an upgrade...
|
||||
|
||||
0. Check `Released()` to verify that the installed version of CoreDNS is an official release.
|
||||
1. Check `Default()`, if the Corefile is a default, simply re-deploy the new default over top the old one. No migration needed.
|
||||
If the Corefile is not a default, continue...
|
||||
2. Check `Deprecated()`, if anything is deprecated, warn user, but continue install.
|
||||
3. Check `Unsupported()`, if anything is unsupported, abort and warn user (allow user to override to pass this).
|
||||
4. Call `Migrate()`, if there is an error, abort and warn user.
|
||||
5. If there is no error, pause and ask user if they want to continue with the migration. If the starting Corefile was at defaults,
|
||||
proceed use the migrated Corefile.
|
||||
|
||||
|
||||
# K8s/CoreDNS Corefile Migration
|
||||
|
||||
Corefile Migration has moved to https://github.com/coredns/corefile-migration/tree/master/migration
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package caddy
|
||||
|
||||
import "io"
|
||||
|
||||
// allTokens lexes the entire input, but does not parse it.
|
||||
// It returns all the tokens from the input, unstructured
|
||||
// and in order.
|
||||
func allTokens(input io.Reader) ([]Token, error) {
|
||||
l := new(lexer)
|
||||
err := l.load(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokens []Token
|
||||
for l.next() {
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
|
@ -1,260 +0,0 @@
|
|||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Dispenser is a type that dispenses tokens, similarly to a lexer,
|
||||
// except that it can do so with some notion of structure and has
|
||||
// some really convenient methods.
|
||||
type Dispenser struct {
|
||||
filename string
|
||||
tokens []Token
|
||||
cursor int
|
||||
nesting int
|
||||
}
|
||||
|
||||
// NewDispenser returns a Dispenser, ready to use for parsing the given input.
|
||||
func NewDispenser(filename string, input io.Reader) Dispenser {
|
||||
tokens, _ := allTokens(input) // ignoring error because nothing to do with it
|
||||
return Dispenser{
|
||||
filename: filename,
|
||||
tokens: tokens,
|
||||
cursor: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDispenserTokens returns a Dispenser filled with the given tokens.
|
||||
func NewDispenserTokens(filename string, tokens []Token) Dispenser {
|
||||
return Dispenser{
|
||||
filename: filename,
|
||||
tokens: tokens,
|
||||
cursor: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// Next loads the next token. Returns true if a token
|
||||
// was loaded; false otherwise. If false, all tokens
|
||||
// have been consumed.
|
||||
func (d *Dispenser) Next() bool {
|
||||
if d.cursor < len(d.tokens)-1 {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NextArg loads the next token if it is on the same
|
||||
// line. Returns true if a token was loaded; false
|
||||
// otherwise. If false, all tokens on the line have
|
||||
// been consumed. It handles imported tokens correctly.
|
||||
func (d *Dispenser) NextArg() bool {
|
||||
if d.cursor < 0 {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
if d.cursor >= len(d.tokens) {
|
||||
return false
|
||||
}
|
||||
if d.cursor < len(d.tokens)-1 &&
|
||||
d.tokens[d.cursor].File == d.tokens[d.cursor+1].File &&
|
||||
d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NextLine loads the next token only if it is not on the same
|
||||
// line as the current token, and returns true if a token was
|
||||
// loaded; false otherwise. If false, there is not another token
|
||||
// or it is on the same line. It handles imported tokens correctly.
|
||||
func (d *Dispenser) NextLine() bool {
|
||||
if d.cursor < 0 {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
if d.cursor >= len(d.tokens) {
|
||||
return false
|
||||
}
|
||||
if d.cursor < len(d.tokens)-1 &&
|
||||
(d.tokens[d.cursor].File != d.tokens[d.cursor+1].File ||
|
||||
d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) {
|
||||
d.cursor++
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NextBlock can be used as the condition of a for loop
|
||||
// to load the next token as long as it opens a block or
|
||||
// is already in a block. It returns true if a token was
|
||||
// loaded, or false when the block's closing curly brace
|
||||
// was loaded and thus the block ended. Nested blocks are
|
||||
// not supported.
|
||||
func (d *Dispenser) NextBlock() bool {
|
||||
if d.nesting > 0 {
|
||||
d.Next()
|
||||
if d.Val() == "}" {
|
||||
d.nesting--
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if !d.NextArg() { // block must open on same line
|
||||
return false
|
||||
}
|
||||
if d.Val() != "{" {
|
||||
d.cursor-- // roll back if not opening brace
|
||||
return false
|
||||
}
|
||||
d.Next()
|
||||
if d.Val() == "}" {
|
||||
// Open and then closed right away
|
||||
return false
|
||||
}
|
||||
d.nesting++
|
||||
return true
|
||||
}
|
||||
|
||||
// Val gets the text of the current token. If there is no token
|
||||
// loaded, it returns empty string.
|
||||
func (d *Dispenser) Val() string {
|
||||
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
||||
return ""
|
||||
}
|
||||
return d.tokens[d.cursor].Text
|
||||
}
|
||||
|
||||
// Line gets the line number of the current token. If there is no token
|
||||
// loaded, it returns 0.
|
||||
func (d *Dispenser) Line() int {
|
||||
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
||||
return 0
|
||||
}
|
||||
return d.tokens[d.cursor].Line
|
||||
}
|
||||
|
||||
// File gets the filename of the current token. If there is no token loaded,
|
||||
// it returns the filename originally given when parsing started.
|
||||
func (d *Dispenser) File() string {
|
||||
if d.cursor < 0 || d.cursor >= len(d.tokens) {
|
||||
return d.filename
|
||||
}
|
||||
if tokenFilename := d.tokens[d.cursor].File; tokenFilename != "" {
|
||||
return tokenFilename
|
||||
}
|
||||
return d.filename
|
||||
}
|
||||
|
||||
// Args is a convenience function that loads the next arguments
|
||||
// (tokens on the same line) into an arbitrary number of strings
|
||||
// pointed to in targets. If there are fewer tokens available
|
||||
// than string pointers, the remaining strings will not be changed
|
||||
// and false will be returned. If there were enough tokens available
|
||||
// to fill the arguments, then true will be returned.
|
||||
func (d *Dispenser) Args(targets ...*string) bool {
|
||||
enough := true
|
||||
for i := 0; i < len(targets); i++ {
|
||||
if !d.NextArg() {
|
||||
enough = false
|
||||
break
|
||||
}
|
||||
*targets[i] = d.Val()
|
||||
}
|
||||
return enough
|
||||
}
|
||||
|
||||
// RemainingArgs loads any more arguments (tokens on the same line)
|
||||
// into a slice and returns them. Open curly brace tokens also indicate
|
||||
// the end of arguments, and the curly brace is not included in
|
||||
// the return value nor is it loaded.
|
||||
func (d *Dispenser) RemainingArgs() []string {
|
||||
var args []string
|
||||
|
||||
for d.NextArg() {
|
||||
if d.Val() == "{" {
|
||||
d.cursor--
|
||||
break
|
||||
}
|
||||
args = append(args, d.Val())
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// ArgErr returns an argument error, meaning that another
|
||||
// argument was expected but not found. In other words,
|
||||
// a line break or open curly brace was encountered instead of
|
||||
// an argument.
|
||||
func (d *Dispenser) ArgErr() error {
|
||||
if d.Val() == "{" {
|
||||
return d.Err("Unexpected token '{', expecting argument")
|
||||
}
|
||||
return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val())
|
||||
}
|
||||
|
||||
// SyntaxErr creates a generic syntax error which explains what was
|
||||
// found and what was expected.
|
||||
func (d *Dispenser) SyntaxErr(expected string) error {
|
||||
msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.File(), d.Line(), d.Val(), expected)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
// EOFErr returns an error indicating that the dispenser reached
|
||||
// the end of the input when searching for the next token.
|
||||
func (d *Dispenser) EOFErr() error {
|
||||
return d.Errf("Unexpected EOF")
|
||||
}
|
||||
|
||||
// Err generates a custom parse-time error with a message of msg.
|
||||
func (d *Dispenser) Err(msg string) error {
|
||||
msg = fmt.Sprintf("%s:%d - Error during parsing: %s", d.File(), d.Line(), msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
// Errf is like Err, but for formatted error messages
|
||||
func (d *Dispenser) Errf(format string, args ...interface{}) error {
|
||||
return d.Err(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// numLineBreaks counts how many line breaks are in the token
|
||||
// value given by the token index tknIdx. It returns 0 if the
|
||||
// token does not exist or there are no line breaks.
|
||||
func (d *Dispenser) numLineBreaks(tknIdx int) int {
|
||||
if tknIdx < 0 || tknIdx >= len(d.tokens) {
|
||||
return 0
|
||||
}
|
||||
return strings.Count(d.tokens[tknIdx].Text, "\n")
|
||||
}
|
||||
|
||||
// isNewLine determines whether the current token is on a different
|
||||
// line (higher line number) than the previous token. It handles imported
|
||||
// tokens correctly. If there isn't a previous token, it returns true.
|
||||
func (d *Dispenser) isNewLine() bool {
|
||||
if d.cursor < 1 {
|
||||
return true
|
||||
}
|
||||
if d.cursor > len(d.tokens)-1 {
|
||||
return false
|
||||
}
|
||||
return d.tokens[d.cursor-1].File != d.tokens[d.cursor].File ||
|
||||
d.tokens[d.cursor-1].Line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].Line
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
// Copyright 2015 Light Code Labs, LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type (
|
||||
// lexer is a utility which can get values, token by
|
||||
// token, from a Reader. A token is a word, and tokens
|
||||
// are separated by whitespace. A word can be enclosed
|
||||
// in quotes if it contains whitespace.
|
||||
lexer struct {
|
||||
reader *bufio.Reader
|
||||
token Token
|
||||
line int
|
||||
}
|
||||
|
||||
// Token represents a single parsable unit.
|
||||
Token struct {
|
||||
File string
|
||||
Line int
|
||||
Text string
|
||||
}
|
||||
)
|
||||
|
||||
// load prepares the lexer to scan an input for tokens.
|
||||
// It discards any leading byte order mark.
|
||||
func (l *lexer) load(input io.Reader) error {
|
||||
l.reader = bufio.NewReader(input)
|
||||
l.line = 1
|
||||
|
||||
// discard byte order mark, if present
|
||||
firstCh, _, err := l.reader.ReadRune()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if firstCh != 0xFEFF {
|
||||
err := l.reader.UnreadRune()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// next loads the next token into the lexer.
|
||||
// A token is delimited by whitespace, unless
|
||||
// the token starts with a quotes character (")
|
||||
// in which case the token goes until the closing
|
||||
// quotes (the enclosing quotes are not included).
|
||||
// Inside quoted strings, quotes may be escaped
|
||||
// with a preceding \ character. No other chars
|
||||
// may be escaped. The rest of the line is skipped
|
||||
// if a "#" character is read in. Returns true if
|
||||
// a token was loaded; false otherwise.
|
||||
func (l *lexer) next() bool {
|
||||
var val []rune
|
||||
var comment, quoted, escaped bool
|
||||
|
||||
makeToken := func() bool {
|
||||
l.token.Text = string(val)
|
||||
return true
|
||||
}
|
||||
|
||||
for {
|
||||
ch, _, err := l.reader.ReadRune()
|
||||
if err != nil {
|
||||
if len(val) > 0 {
|
||||
return makeToken()
|
||||
}
|
||||
if err == io.EOF {
|
||||
return false
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if quoted {
|
||||
if !escaped {
|
||||
if ch == '\\' {
|
||||
escaped = true
|
||||
continue
|
||||
} else if ch == '"' {
|
||||
quoted = false
|
||||
return makeToken()
|
||||
}
|
||||
}
|
||||
if ch == '\n' {
|
||||
l.line++
|
||||
}
|
||||
if escaped {
|
||||
// only escape quotes
|
||||
if ch != '"' {
|
||||
val = append(val, '\\')
|
||||
}
|
||||
}
|
||||
val = append(val, ch)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if unicode.IsSpace(ch) {
|
||||
if ch == '\r' {
|
||||
continue
|
||||
}
|
||||
if ch == '\n' {
|
||||
l.line++
|
||||
comment = false
|
||||
}
|
||||
if len(val) > 0 {
|
||||
return makeToken()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ch == '#' {
|
||||
comment = true
|
||||
}
|
||||
|
||||
if comment {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(val) == 0 {
|
||||
l.token = Token{Line: l.line}
|
||||
if ch == '"' {
|
||||
quoted = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
val = append(val, ch)
|
||||
}
|
||||
}
|
|
@ -1,179 +0,0 @@
|
|||
package corefile
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration/caddy"
|
||||
)
|
||||
|
||||
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.NewDispenser("migration", strings.NewReader(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
|
|
@ -1,121 +0,0 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module github.com/coredns/deployment/kubernetes/migration
|
||||
|
||||
go 1.12
|
|
@ -1,445 +0,0 @@
|
|||
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 (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/coredns/deployment/kubernetes/migration/corefile"
|
||||
)
|
||||
|
||||
// Deprecated returns a list of deprecation notifications affecting the guven Corefile. Notifications are returned for
|
||||
// any deprecated, removed, or ignored plugins/directives present in the Corefile. Notifications are also returned for
|
||||
// any new default plugins that would be added in a migration.
|
||||
func Deprecated(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string) ([]Notice, error) {
|
||||
return getStatus(fromCoreDNSVersion, toCoreDNSVersion, corefileStr, all)
|
||||
}
|
||||
|
||||
// Unsupported returns a list notifications of plugins/options that are not handled supported by this 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) {
|
||||
if fromCoreDNSVersion == toCoreDNSVersion {
|
||||
return nil, nil
|
||||
}
|
||||
err := validUpMigration(fromCoreDNSVersion, toCoreDNSVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cf, err := corefile.New(corefileStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
notices := []Notice{}
|
||||
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 != "" && vp.status != newdefault {
|
||||
notices = append(notices, Notice{
|
||||
Plugin: p.Name,
|
||||
Severity: vp.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 != "" && vo.status != newdefault {
|
||||
notices = append(notices, Notice{Plugin: p.Name, Option: o.Name, Severity: vo.status, Version: v})
|
||||
continue
|
||||
}
|
||||
}
|
||||
if status != unsupported {
|
||||
CheckForNewOptions:
|
||||
for name, vo := range Versions[v].plugins[p.Name].options {
|
||||
if vo.status != newdefault {
|
||||
continue
|
||||
}
|
||||
for _, o := range p.Options {
|
||||
if name == o.Name {
|
||||
continue CheckForNewOptions
|
||||
}
|
||||
}
|
||||
notices = append(notices, Notice{Plugin: p.Name, Option: name, Severity: newdefault, Version: v})
|
||||
}
|
||||
}
|
||||
}
|
||||
if status != unsupported {
|
||||
CheckForNewPlugins:
|
||||
for name, vp := range Versions[v].plugins {
|
||||
if vp.status != newdefault {
|
||||
continue
|
||||
}
|
||||
for _, p := range s.Plugins {
|
||||
if name == p.Name {
|
||||
continue CheckForNewPlugins
|
||||
}
|
||||
}
|
||||
notices = append(notices, Notice{Plugin: name, Option: "", Severity: newdefault, Version: v})
|
||||
}
|
||||
}
|
||||
}
|
||||
if v == toCoreDNSVersion {
|
||||
break
|
||||
}
|
||||
}
|
||||
return notices, nil
|
||||
}
|
||||
|
||||
// Migrate returns the Corefile converted to toCoreDNSVersion, or an error if it cannot. This function only accepts
|
||||
// a forward migration, where the destination version is => the start version.
|
||||
// If deprecations is true, deprecated plugins/options will be migrated as soon as they are deprecated.
|
||||
// If deprecations is false, deprecated plugins/options will be migrated only once they become removed or ignored.
|
||||
func Migrate(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string, deprecations bool) (string, error) {
|
||||
if fromCoreDNSVersion == toCoreDNSVersion {
|
||||
return corefileStr, nil
|
||||
}
|
||||
err := validUpMigration(fromCoreDNSVersion, toCoreDNSVersion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
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)
|
||||
}
|
||||
newPlug := &corefile.Plugin{
|
||||
Name: p.Name,
|
||||
Args: p.Args,
|
||||
Options: newOpts,
|
||||
}
|
||||
CheckForNewOptions:
|
||||
for name, vo := range Versions[v].plugins[p.Name].options {
|
||||
if vo.status != newdefault {
|
||||
continue
|
||||
}
|
||||
for _, o := range p.Options {
|
||||
if name == o.Name {
|
||||
continue CheckForNewOptions
|
||||
}
|
||||
}
|
||||
newPlug, err = vo.add(newPlug)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
newPlugs = append(newPlugs, newPlug)
|
||||
}
|
||||
newSrv := &corefile.Server{
|
||||
DomPorts: s.DomPorts,
|
||||
Plugins: newPlugs,
|
||||
}
|
||||
CheckForNewPlugins:
|
||||
for name, vp := range Versions[v].plugins {
|
||||
if vp.status != newdefault {
|
||||
continue
|
||||
}
|
||||
for _, p := range s.Plugins {
|
||||
if name == p.Name {
|
||||
continue CheckForNewPlugins
|
||||
}
|
||||
}
|
||||
newSrv, err = vp.add(newSrv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
newSrvs = append(newSrvs, newSrv)
|
||||
}
|
||||
|
||||
cf = &corefile.Corefile{Servers: newSrvs}
|
||||
|
||||
// apply any global corefile level post processing
|
||||
if Versions[v].postProcess != nil {
|
||||
cf, err = Versions[v].postProcess(cf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
if v == toCoreDNSVersion {
|
||||
break
|
||||
}
|
||||
}
|
||||
return cf.ToString(), nil
|
||||
}
|
||||
|
||||
// MigrateDown returns the Corefile converted to toCoreDNSVersion, or an error if it cannot. This function only accepts
|
||||
// a downward migration, where the destination version is <= the start version.
|
||||
func MigrateDown(fromCoreDNSVersion, toCoreDNSVersion, corefileStr string) (string, error) {
|
||||
if fromCoreDNSVersion == toCoreDNSVersion {
|
||||
return corefileStr, nil
|
||||
}
|
||||
err := validDownMigration(fromCoreDNSVersion, toCoreDNSVersion)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cf, err := corefile.New(corefileStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
v := fromCoreDNSVersion
|
||||
for {
|
||||
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 vp.downAction == nil {
|
||||
newPlugs = append(newPlugs, p)
|
||||
continue
|
||||
}
|
||||
p, err := vp.downAction(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 vo.downAction == nil {
|
||||
newOpts = append(newOpts, o)
|
||||
continue
|
||||
}
|
||||
o, err := vo.downAction(o)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if o == nil {
|
||||
// remove option
|
||||
continue
|
||||
}
|
||||
newOpts = append(newOpts, o)
|
||||
}
|
||||
newPlug := &corefile.Plugin{
|
||||
Name: p.Name,
|
||||
Args: p.Args,
|
||||
Options: newOpts,
|
||||
}
|
||||
newPlugs = append(newPlugs, newPlug)
|
||||
}
|
||||
newSrv := &corefile.Server{
|
||||
DomPorts: s.DomPorts,
|
||||
Plugins: newPlugs,
|
||||
}
|
||||
newSrvs = append(newSrvs, newSrv)
|
||||
}
|
||||
|
||||
cf = &corefile.Corefile{Servers: newSrvs}
|
||||
|
||||
if v == toCoreDNSVersion {
|
||||
break
|
||||
}
|
||||
v = Versions[v].priorVersion
|
||||
}
|
||||
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 {
|
||||
for _, release := range v.k8sReleases {
|
||||
if k8sVersion != "" && k8sVersion != release {
|
||||
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 dockerImageSHA matches any released image of CoreDNS.
|
||||
func Released(dockerImageSHA string) bool {
|
||||
for _, v := range Versions {
|
||||
if v.dockerImageSHA == dockerImageSHA {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidVersions returns a list of all versions defined
|
||||
func ValidVersions() []string {
|
||||
var vStrs []string
|
||||
for vStr := range Versions {
|
||||
vStrs = append(vStrs, vStr)
|
||||
}
|
||||
sort.Strings(vStrs)
|
||||
return vStrs
|
||||
}
|
||||
|
||||
func validateVersion(fromCoreDNSVersion string) error {
|
||||
if _, ok := Versions[fromCoreDNSVersion]; !ok {
|
||||
return fmt.Errorf("start version '%v' not supported", fromCoreDNSVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validUpMigration(fromCoreDNSVersion, toCoreDNSVersion string) error {
|
||||
err := validateVersion(fromCoreDNSVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for next := Versions[fromCoreDNSVersion].nextVersion; next != ""; next = Versions[next].nextVersion {
|
||||
if next != toCoreDNSVersion {
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("cannot migrate up to '%v' from '%v'", toCoreDNSVersion, fromCoreDNSVersion)
|
||||
}
|
||||
|
||||
func validDownMigration(fromCoreDNSVersion, toCoreDNSVersion string) error {
|
||||
err := validateVersion(fromCoreDNSVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for prior := Versions[fromCoreDNSVersion].priorVersion; prior != ""; prior = Versions[prior].priorVersion {
|
||||
if prior != toCoreDNSVersion {
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("cannot migrate down to '%v' from '%v'", toCoreDNSVersion, fromCoreDNSVersion)
|
||||
}
|
|
@ -1,572 +0,0 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMigrate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fromVersion string
|
||||
toVersion string
|
||||
deprecations bool
|
||||
startCorefile string
|
||||
expectedCorefile string
|
||||
}{
|
||||
{
|
||||
name: "Remove invalid proxy option",
|
||||
fromVersion: "1.1.3",
|
||||
toVersion: "1.2.6",
|
||||
deprecations: true,
|
||||
startCorefile: `.:53 {
|
||||
errors
|
||||
health
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
endpoint thing1 thing2
|
||||
pods insecure
|
||||
upstream
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
prometheus :9153
|
||||
proxy example.org 1.2.3.4:53 {
|
||||
protocol https_google
|
||||
}
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
`,
|
||||
expectedCorefile: `.:53 {
|
||||
errors
|
||||
health
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
endpoint thing1 thing2
|
||||
pods insecure
|
||||
upstream
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
prometheus :9153
|
||||
proxy example.org 1.2.3.4:53
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "Migrate from proxy to forward and handle Kubernetes deprecations",
|
||||
fromVersion: "1.3.1",
|
||||
toVersion: "1.5.0",
|
||||
deprecations: true,
|
||||
startCorefile: `.:53 {
|
||||
errors
|
||||
health
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
endpoint thing1 thing2
|
||||
pods insecure
|
||||
upstream
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
prometheus :9153
|
||||
proxy . /etc/resolv.conf
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
`,
|
||||
expectedCorefile: `.:53 {
|
||||
errors
|
||||
health
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
endpoint thing1
|
||||
pods insecure
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
prometheus :9153
|
||||
forward . /etc/resolv.conf
|
||||
cache 30
|
||||
loop
|
||||
reload
|
||||
loadbalance
|
||||
ready
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "add missing loop and ready plugins",
|
||||
fromVersion: "1.1.3",
|
||||
toVersion: "1.5.0",
|
||||
deprecations: true,
|
||||
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
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
`,
|
||||
expectedCorefile: `.: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
|
||||
reload
|
||||
loadbalance
|
||||
loop
|
||||
ready
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "handle multiple proxy plugins",
|
||||
fromVersion: "1.1.3",
|
||||
toVersion: "1.5.0",
|
||||
deprecations: true,
|
||||
startCorefile: `.:53 {
|
||||
errors
|
||||
health
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
upstream
|
||||
fallthrough in-addr.arpa ip6.arpa
|
||||
}
|
||||
prometheus :9153
|
||||
proxy mystub-1.example.org 1.2.3.4
|
||||
proxy mystub-2.example.org 5.6.7.8
|
||||
proxy . /etc/resolv.conf
|
||||
cache 30
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
`,
|
||||
expectedCorefile: `.: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
|
||||
reload
|
||||
loadbalance
|
||||
loop
|
||||
ready
|
||||
}
|
||||
|
||||
mystub-1.example.org {
|
||||
forward . 1.2.3.4
|
||||
loop
|
||||
errors
|
||||
cache 30
|
||||
}
|
||||
|
||||
mystub-2.example.org {
|
||||
forward . 5.6.7.8
|
||||
loop
|
||||
errors
|
||||
cache 30
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no-op same version migration",
|
||||
fromVersion: "1.3.1",
|
||||
toVersion: "1.3.1",
|
||||
deprecations: true,
|
||||
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
|
||||
}
|
||||
`,
|
||||
expectedCorefile: `.: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
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
|
||||
result, err := Migrate(testCase.fromVersion, testCase.toVersion, testCase.startCorefile, testCase.deprecations)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
if result != testCase.expectedCorefile {
|
||||
t.Errorf("expected != result\n%v\n%v", testCase.expectedCorefile, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateDown(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
fromVersion string
|
||||
toVersion string
|
||||
deprecations bool
|
||||
startCorefile string
|
||||
expectedCorefile string
|
||||
}{
|
||||
{
|
||||
name: "from 1.5.0 to 1.1.3",
|
||||
fromVersion: "1.5.0",
|
||||
toVersion: "1.1.3",
|
||||
startCorefile: `.:53 {
|
||||
errors
|
||||
health
|
||||
ready
|
||||
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
|
||||
}
|
||||
`,
|
||||
expectedCorefile: `.: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
|
||||
reload
|
||||
loadbalance
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no-op same version migration",
|
||||
fromVersion: "1.3.1",
|
||||
toVersion: "1.3.1",
|
||||
deprecations: true,
|
||||
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
|
||||
}
|
||||
`,
|
||||
expectedCorefile: `.: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
|
||||
}
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
|
||||
result, err := MigrateDown(testCase.fromVersion, testCase.toVersion, testCase.startCorefile)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
|
||||
if result != testCase.expectedCorefile {
|
||||
t.Errorf("expected != result:\n%v\n%v", testCase.expectedCorefile, 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"},
|
||||
{Option: "upstream", Plugin: "kubernetes", Severity: ignored, Version: "1.5.0"},
|
||||
{Plugin: "proxy", Severity: removed, ReplacedBy: "forward", Version: "1.5.0"},
|
||||
{Plugin: "ready", Severity: newdefault, Version: "1.5.0"},
|
||||
}
|
||||
|
||||
result, err := Deprecated("1.2.0", "1.5.0", startCorefile)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("expected to find %v notifications; 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())
|
||||
}
|
||||
}
|
||||
|
||||
result, err = Deprecated("1.3.1", "1.3.1", startCorefile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected = []Notice{}
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("expected to find %v notifications in no-op upgrade; got %v", len(expected), len(result))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidUpMigration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
from string
|
||||
to string
|
||||
shouldErr bool
|
||||
}{
|
||||
{"1.3.1", "1.3.1", true},
|
||||
{"1.3.1", "1.5.0", false},
|
||||
{"1.5.0", "1.3.1", true},
|
||||
{"banana", "1.5.0", true},
|
||||
{"1.3.1", "apple", true},
|
||||
{"banana", "apple", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := validUpMigration(tc.from, tc.to)
|
||||
|
||||
if !tc.shouldErr && err != nil {
|
||||
t.Errorf("expected '%v' to '%v' to be valid versions.", tc.from, tc.to)
|
||||
}
|
||||
if tc.shouldErr && err == nil {
|
||||
t.Errorf("expected '%v' to '%v' to be invalid versions.", tc.from, tc.to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidDownMigration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
from string
|
||||
to string
|
||||
shouldErr bool
|
||||
}{
|
||||
{"1.3.1", "1.3.1", true},
|
||||
{"1.3.1", "1.5.0", true},
|
||||
{"1.5.0", "1.3.1", false},
|
||||
{"banana", "1.5.0", true},
|
||||
{"1.3.1", "apple", true},
|
||||
{"banana", "apple", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := validDownMigration(tc.from, tc.to)
|
||||
|
||||
if !tc.shouldErr && err != nil {
|
||||
t.Errorf("expected '%v' to '%v' to be valid versions.", tc.from, tc.to)
|
||||
}
|
||||
if tc.shouldErr && err == nil {
|
||||
t.Errorf("expected '%v' to '%v' to be invalid versions.", tc.from, tc.to)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
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 if n.Severity == newdefault {
|
||||
s += "is added as a default in " + n.Version + "."
|
||||
} else {
|
||||
s += "is " + 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 (
|
||||
// The following statuses are used to indicate the state of support/deprecation in a given release.
|
||||
deprecated = "deprecated" // deprecated, but still completely functional
|
||||
ignored = "ignored" // if included in the corefile, it will be ignored by CoreDNS
|
||||
removed = "removed" // completely removed from CoreDNS, and would cause CoreDNS to exit if present in the Corefile
|
||||
newdefault = "newdefault" // added to the default corefile. CoreDNS may not function properly if it is not present in the corefile.
|
||||
unsupported = "unsupported" // the plugin/option is not supported by the migration tool
|
||||
|
||||
// The following statuses are used for selecting/filtering notifications
|
||||
all = "all" // show all statuses
|
||||
)
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue