code moved (#186)

This commit is contained in:
Chris O'Haver 2019-07-29 13:25:50 -04:00 committed by GitHub
parent 2900b2a671
commit 2f535e77e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 3 additions and 3493 deletions

View file

@ -1,3 +0,0 @@
Corefile
corefile-tool
*.yaml

View file

@ -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

View file

@ -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
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -1,7 +0,0 @@
package main
import "github.com/coredns/deployment/kubernetes/corefile-tool/cmd"
func main() {
cmd.Execute()
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -1,3 +0,0 @@
module github.com/coredns/deployment/kubernetes/migration
go 1.12

View file

@ -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)
}

View file

@ -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)
}
}
}

View file

@ -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