Skip to content

Commit

Permalink
feat: add capability to convert from 2.x to 3.x format
Browse files Browse the repository at this point in the history
  • Loading branch information
GGabriele committed Sep 26, 2022
1 parent 444cbc7 commit 42250af
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 39 deletions.
43 changes: 33 additions & 10 deletions cmd/convert.go
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"os"

"github.com/kong/deck/convert"
"github.com/kong/deck/cprint"
Expand Down Expand Up @@ -35,15 +36,32 @@ can be converted into a 'konnect' configuration file.`,
return err
}

if yes, err := utils.ConfirmFileOverwrite(convertCmdOutputFile, "", false); err != nil {
return err
} else if !yes {
return nil
}
if convertCmdInputFile != "" {
if yes, err := utils.ConfirmFileOverwrite(convertCmdOutputFile, "", false); err != nil {
return err
} else if !yes {
return nil
}

err = convert.Convert(convertCmdInputFile, convertCmdOutputFile, sourceFormat, destinationFormat)
if err != nil {
return fmt.Errorf("converting file: %v", err)
err = convert.Convert(convertCmdInputFile, convertCmdOutputFile, sourceFormat, destinationFormat)
if err != nil {
return fmt.Errorf("converting file: %v", err)
}
} else if is2xTo3xConversion() {
path, err := os.Getwd()
if err != nil {
return fmt.Errorf("getting current working directory: %w", err)
}
files, err := utils.ConfigFilesInDir(path)
if err != nil {
return fmt.Errorf("getting files from directory: %w", err)
}
for _, filename := range files {
err = convert.Convert(filename, filename, sourceFormat, destinationFormat)
if err != nil {
return fmt.Errorf("converting '%s' file: %v", filename, err)
}
}
}
if convertCmdDestinationFormat == "konnect" {
cprint.UpdatePrintf("Warning: konnect format type was deprecated in v1.12 and it will be removed\n" +
Expand All @@ -54,8 +72,8 @@ can be converted into a 'konnect' configuration file.`,
},
}

sourceFormats := []convert.Format{convert.FormatKongGateway}
destinationFormats := []convert.Format{convert.FormatKonnect}
sourceFormats := []convert.Format{convert.FormatKongGateway, convert.FormatKongGateway2x}
destinationFormats := []convert.Format{convert.FormatKonnect, convert.FormatKongGateway3x}
convertCmd.Flags().StringVar(&convertCmdSourceFormat, "from", "",
fmt.Sprintf("format of the source file, allowed formats: %v", sourceFormats))
convertCmd.Flags().StringVar(&convertCmdDestinationFormat, "to", "",
Expand All @@ -66,3 +84,8 @@ can be converted into a 'konnect' configuration file.`,
"file to write configuration to after conversion. Use `-` to write to stdout.")
return convertCmd
}

func is2xTo3xConversion() bool {
return convertCmdSourceFormat == string(convert.FormatKongGateway2x) &&
convertCmdDestinationFormat == string(convert.FormatKongGateway3x)
}
68 changes: 68 additions & 0 deletions convert/convert.go
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strings"

"github.com/kong/deck/cprint"
"github.com/kong/deck/file"
"github.com/kong/deck/utils"
"github.com/kong/go-kong/kong"
Expand All @@ -16,6 +17,10 @@ const (
FormatKongGateway Format = "kong-gateway"
// FormatKonnect represents the Konnect format.
FormatKonnect Format = "konnect"
// FormatKongGateway2x represents the Kong gateway 2.x format.
FormatKongGateway2x Format = "kong-gateway-2.x"
// FormatKongGateway3x represents the Kong gateway 3.x format.
FormatKongGateway3x Format = "kong-gateway-3.x"
)

// AllFormats contains all available formats.
Expand All @@ -28,6 +33,10 @@ func ParseFormat(key string) (Format, error) {
return FormatKongGateway, nil
case FormatKonnect:
return FormatKonnect, nil
case FormatKongGateway2x:
return FormatKongGateway2x, nil
case FormatKongGateway3x:
return FormatKongGateway3x, nil
default:
return "", fmt.Errorf("invalid format: '%v'", key)
}
Expand All @@ -50,6 +59,11 @@ func Convert(inputFilename, outputFilename string, from, to Format) error {
if err != nil {
return err
}
case from == FormatKongGateway2x && to == FormatKongGateway3x:
outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilename)
if err != nil {
return err
}
default:
return fmt.Errorf("cannot convert from '%s' to '%s' format", from, to)
}
Expand All @@ -61,6 +75,60 @@ func Convert(inputFilename, outputFilename string, from, to Format) error {
return nil
}

func convertKongGateway2xTo3x(input *file.Content, filename string) (*file.Content, error) {
if input == nil {
return nil, fmt.Errorf("input content is nil")
}
outputContent := input.DeepCopy()

convertedRoutes := []*file.FRoute{}
for _, service := range outputContent.Services {
for _, route := range service.Routes {
convertedRoutes = append(convertedRoutes, migrateRoutesPathFieldPre300(route, filename))
}
service.Routes = convertedRoutes
}

cprint.UpdatePrintf(
"From the '%s' config file,\n"+
"the _format_version field has been migrated from '%s' to '%s'.\n"+
"These automatic changes may not be correct or exhaustive enough, please\n"+
"perform a manual audit of the config file.\n\n"+
"For related information, please visit:\n"+
"https://docs.konghq.com/deck/latest/3.0-upgrade\n\n",
filename, outputContent.FormatVersion, "3.0")
outputContent.FormatVersion = "3.0"

return outputContent, nil
}

func migrateRoutesPathFieldPre300(route *file.FRoute, filename string) *file.FRoute {
changedPaths := []string{}
for _, path := range route.Paths {
if !strings.HasPrefix(*path, "~/") && utils.IsPathRegexLike(*path) {
changedPaths = append(changedPaths, *path)
*path = "~" + *path
}
}
if len(changedPaths) > 0 {
changedPathsLen := len(changedPaths)
// do not consider more than 3 sample routes to print out.
if changedPathsLen > 3 {
changedPaths = changedPaths[:3]
}
cprint.UpdatePrintf(
"From the '%s' config file,\n"+
"%d routes paths matching an unsupported regex pattern usage\n"+
"with Kong version 3.0 or above were detected\n"+
"(e.g. %s).\n\n"+
"Kong gateway versions 3.0 and above require that regular expressions\n"+
"start with a '~' character to distinguish from simple prefix match.\n"+
"In order to make these paths compatible with 3.x, a '~' prefix has been added.\n\n",
filename, changedPathsLen, strings.Join(changedPaths, ", "))
}
return route
}

func convertKongGatewayToKonnect(input *file.Content) (*file.Content, error) {
if input == nil {
return nil, fmt.Errorf("input content is nil")
Expand Down
47 changes: 47 additions & 0 deletions convert/convert_test.go
Expand Up @@ -28,6 +28,22 @@ func TestParseFormat(t *testing.T) {
want: FormatKongGateway,
wantErr: false,
},
{
name: "parses valid values",
args: args{
key: "kong-gateway-2.x",
},
want: FormatKongGateway2x,
wantErr: false,
},
{
name: "parses valid values",
args: args{
key: "kong-gateway-3.x",
},
want: FormatKongGateway3x,
wantErr: false,
},
{
name: "parses values in a case-insensitive manner",
args: args{
Expand Down Expand Up @@ -161,6 +177,15 @@ func Test_Convert(t *testing.T) {
},
wantErr: true,
},
{
name: "errors out due to invalid conversion",
args: args{
inputFilename: "testdata/3/input.yaml",
fromFormat: FormatKongGateway3x,
toFormat: FormatKongGateway2x,
},
wantErr: true,
},
{
name: "errors out when a nameless service is present in the input",
args: args{
Expand Down Expand Up @@ -190,6 +215,28 @@ func Test_Convert(t *testing.T) {
},
wantErr: false,
},
{
name: "converts from Kong Gateway 2.x to Kong Gateway 3.x format",
args: args{
inputFilename: "testdata/3/input.yaml",
outputFilename: "testdata/3/output.yaml",
expectedOutputFilename: "testdata/3/output-expected.yaml",
fromFormat: FormatKongGateway2x,
toFormat: FormatKongGateway3x,
},
wantErr: false,
},
{
name: "converts from Kong Gateway 2.x to Kong Gateway 3.x format (no _format_version input)",
args: args{
inputFilename: "testdata/4/input.yaml",
outputFilename: "testdata/4/output.yaml",
expectedOutputFilename: "testdata/4/output-expected.yaml",
fromFormat: FormatKongGateway2x,
toFormat: FormatKongGateway3x,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions convert/testdata/3/input.yaml
@@ -0,0 +1,9 @@
services:
- name: svc1
host: mockbin.org
path: /status/200
routes:
- name: r1
paths:
- /status/\d+
- /code/\d+
10 changes: 10 additions & 0 deletions convert/testdata/3/output-expected.yaml
@@ -0,0 +1,10 @@
_format_version: "3.0"
services:
- name: svc1
host: mockbin.org
path: /status/200
routes:
- name: r1
paths:
- ~/status/\d+
- ~/code/\d+
10 changes: 10 additions & 0 deletions convert/testdata/3/output.yaml
@@ -0,0 +1,10 @@
_format_version: "3.0"
services:
- host: mockbin.org
name: svc1
path: /status/200
routes:
- name: r1
paths:
- ~/status/\d+
- ~/code/\d+
10 changes: 10 additions & 0 deletions convert/testdata/4/input.yaml
@@ -0,0 +1,10 @@
_format_version: "1.1"
services:
- name: svc1
host: mockbin.org
path: /status/200
routes:
- name: r1
paths:
- /status/\d+
- /code/\d+
10 changes: 10 additions & 0 deletions convert/testdata/4/output-expected.yaml
@@ -0,0 +1,10 @@
_format_version: "3.0"
services:
- name: svc1
host: mockbin.org
path: /status/200
routes:
- name: r1
paths:
- ~/status/\d+
- ~/code/\d+
10 changes: 10 additions & 0 deletions convert/testdata/4/output.yaml
@@ -0,0 +1,10 @@
_format_version: "3.0"
services:
- host: mockbin.org
name: svc1
path: /status/200
routes:
- name: r1
paths:
- ~/status/\d+
- ~/code/\d+
30 changes: 2 additions & 28 deletions file/readfile.go
Expand Up @@ -7,12 +7,12 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"

ghodss "github.com/ghodss/yaml"
"github.com/imdario/mergo"
"github.com/kong/deck/utils"
)

// getContent reads all the YAML and JSON files in the directory or the
Expand Down Expand Up @@ -64,7 +64,7 @@ func getReaders(fileOrDir string) ([]io.Reader, error) {

var files []string
if finfo.IsDir() {
files, err = configFilesInDir(fileOrDir)
files, err = utils.ConfigFilesInDir(fileOrDir)
if err != nil {
return nil, fmt.Errorf("getting files from directory: %w", err)
}
Expand Down Expand Up @@ -130,32 +130,6 @@ func yamlUnmarshal(bytes []byte, v interface{}) error {
return ghodss.Unmarshal(bytes, v)
}

// configFilesInDir traverses the directory rooted at dir and
// returns all the files with a case-insensitive extension of `yml` or `yaml`.
func configFilesInDir(dir string) ([]string, error) {
var res []string
err := filepath.Walk(
dir,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
switch strings.ToLower(filepath.Ext(path)) {
case ".yaml", ".yml", ".json":
res = append(res, path)
}
return nil
},
)
if err != nil {
return nil, fmt.Errorf("reading state directory: %w", err)
}
return res, nil
}

func getPrefixedEnvVar(key string) (string, error) {
const envVarPrefix = "DECK_"
if !strings.HasPrefix(key, envVarPrefix) {
Expand Down
3 changes: 2 additions & 1 deletion file/readfile_test.go
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"testing"

"github.com/kong/deck/utils"
"github.com/kong/go-kong/kong"
)

Expand Down Expand Up @@ -45,7 +46,7 @@ func Test_configFilesInDir(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := configFilesInDir(tt.args.dir)
got, err := utils.ConfigFilesInDir(tt.args.dir)
if (err != nil) != tt.wantErr {
t.Errorf("configFilesInDir() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down

0 comments on commit 42250af

Please sign in to comment.