Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): add short-hand '-o' flag to convert #965

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@
validated, which allows for working with incomplete or even invalid files in a pipeline.
The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops).
[#939](https://github.com/Kong/deck/pull/939)
- Added a new command `file render` to render a final decK file. This will result in a file representing
the state as it would be synced online.
[#963](https://github.com/Kong/deck/pull/963)
- Added a new flag `--format` to `file convert` to enable JSON output.
[#963](https://github.com/Kong/deck/pull/963)
- Added shorthand flag `-o` to `convert` to align with other commands
[#965](https://github.com/Kong/deck/pull/965)

### Fixes

Expand Down
2 changes: 1 addition & 1 deletion cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism,
delay int, workspace string,
) error {
// read target file
targetContent, err := file.GetContentFromFiles(filenames)
targetContent, err := file.GetContentFromFiles(filenames, false)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/common_konnect.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func syncKonnect(ctx context.Context,
httpClient := utils.HTTPClient()

// read target file
targetContent, err := file.GetContentFromFiles(filenames)
targetContent, err := file.GetContentFromFiles(filenames, false)
if err != nil {
return err
}
Expand Down
13 changes: 3 additions & 10 deletions cmd/file.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd

import (
"github.com/spf13/cobra"
)

//
//
// Define the CLI data for the file sub-command
//
//

func newAddFileCmd() *cobra.Command {
addFileCmd := &cobra.Command{
Use: "file [sub-command]...",
Use: "file",
Short: "Sub-command to host the decK file manipulation operations",
Long: `Sub-command to host the decK file manipulation operations`,
}

addFileCmd.AddCommand(newFileRenderCmd())

return addFileCmd
}
51 changes: 44 additions & 7 deletions cmd/convert.go → cmd/file_convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@ import (
"fmt"
"log"
"os"
"strings"

"github.com/kong/deck/convert"
"github.com/kong/deck/cprint"
"github.com/kong/deck/file"
"github.com/kong/deck/utils"
"github.com/spf13/cobra"
)

var (
convertCmdSourceFormat string
convertCmdDestinationFormat string
convertCmdDestinationFormat string // konnect/kong-gateway-3.x/etc
convertCmdInputFile string
convertCmdOutputFile string
convertCmdAssumeYes bool
convertCmdStateFormat string // yaml/json output
)

func executeConvert(_ *cobra.Command, _ []string) error {
Expand All @@ -38,7 +41,13 @@ func executeConvert(_ *cobra.Command, _ []string) error {
return nil
}

err = convert.Convert(convertCmdInputFile, convertCmdOutputFile, sourceFormat, destinationFormat)
err = convert.Convert(
[]string{convertCmdInputFile},
convertCmdOutputFile,
file.Format(strings.ToUpper(convertCmdStateFormat)),
sourceFormat,
destinationFormat,
false)
if err != nil {
return fmt.Errorf("converting file: %w", err)
}
Expand All @@ -52,7 +61,13 @@ func executeConvert(_ *cobra.Command, _ []string) error {
return fmt.Errorf("getting files from directory: %w", err)
}
for _, filename := range files {
err = convert.Convert(filename, filename, sourceFormat, destinationFormat)
err = convert.Convert(
[]string{filename},
filename,
file.Format(strings.ToUpper(convertCmdStateFormat)),
sourceFormat,
destinationFormat,
false)
if err != nil {
return fmt.Errorf("converting '%s' file: %w", filename, err)
}
Expand All @@ -70,12 +85,14 @@ func executeConvert(_ *cobra.Command, _ []string) error {
func newConvertCmd(deprecated bool) *cobra.Command {
short := "Convert files from one format into another format"
execute := executeConvert
args := cobra.ArbitraryArgs
if deprecated {
short = "[deprecated] use 'file convert' instead"
execute = func(cmd *cobra.Command, args []string) error {
log.Println("Warning: the 'deck convert' command was deprecated and moved to 'deck file convert'")
return executeConvert(cmd, args)
}
args = validateNoArgs
}

convertCmd := &cobra.Command{
Expand All @@ -84,8 +101,23 @@ func newConvertCmd(deprecated bool) *cobra.Command {
Long: `The convert command changes configuration files from one format
into another compatible format. For example, a configuration for 'kong-gateway-2.x'
can be converted into a 'kong-gateway-3.x' configuration file.`,
Args: validateNoArgs,
Args: args,
RunE: execute,
PreRunE: func(cmd *cobra.Command, args []string) error {
if deprecated {
if len(fileRenderCmdKongStateFile) == 0 {
return fmt.Errorf("a file containing the Kong configuration " +
"must be specified using `--input-file` flag")
}
return preRunSilenceEventsFlag()
}

fileRenderCmdKongStateFile = args
if len(fileRenderCmdKongStateFile) == 0 {
fileRenderCmdKongStateFile = []string{"-"} // default to stdin
}
return preRunSilenceEventsFlag()
},
}

sourceFormats := []convert.Format{convert.FormatKongGateway, convert.FormatKongGateway2x}
Expand All @@ -94,12 +126,17 @@ can be converted into a 'kong-gateway-3.x' configuration file.`,
fmt.Sprintf("format of the source file, allowed formats: %v", sourceFormats))
convertCmd.Flags().StringVar(&convertCmdDestinationFormat, "to", "",
fmt.Sprintf("desired format of the output, allowed formats: %v", destinationFormats))
convertCmd.Flags().StringVar(&convertCmdInputFile, "input-file", "",
"configuration file to be converted. Use `-` to read from stdin.")
convertCmd.Flags().StringVar(&convertCmdOutputFile, "output-file", "kong.yaml",
if deprecated {
convertCmd.Flags().StringVar(&convertCmdInputFile, "input-file", "",
"configuration file to be converted. Use `-` to read from stdin.")
}
convertCmd.Flags().StringVarP(&convertCmdOutputFile, "output-file", "o", "kong.yaml",
"file to write configuration to after conversion. Use `-` to write to stdout.")
convertCmd.Flags().BoolVar(&convertCmdAssumeYes, "yes",
false, "assume `yes` to prompts and run non-interactively.")
convertCmd.Flags().StringVar(&convertCmdStateFormat, "format",
"yaml", "output file format: json or yaml.")

return convertCmd
}

Expand Down
50 changes: 50 additions & 0 deletions cmd/file_render.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cmd

import (
"strings"

"github.com/kong/deck/convert"
"github.com/kong/deck/file"
"github.com/spf13/cobra"
)

var (
fileRenderCmdKongStateFile []string
fileRenderCmdKongFileOutput string
fileRenderCmdStateFormat string
)

func executeFileRenderCmd(_ *cobra.Command, _ []string) error {
return convert.Convert(
fileRenderCmdKongStateFile,
fileRenderCmdKongFileOutput,
file.Format(strings.ToUpper(fileRenderCmdStateFormat)),
convert.FormatDistributed,
convert.FormatKongGateway3x,
true)
}

func newFileRenderCmd() *cobra.Command {
renderCmd := &cobra.Command{
Use: "render",
Short: "Render the configuration as Kong declarative config",
Long: ``,
Args: validateNoArgs,
RunE: executeFileRenderCmd,
PreRunE: func(cmd *cobra.Command, args []string) error {
fileRenderCmdKongStateFile = args
if len(fileRenderCmdKongStateFile) == 0 {
fileRenderCmdKongStateFile = []string{"-"} // default to stdin
}
return preRunSilenceEventsFlag()
},
}

renderCmd.Flags().StringVarP(&fileRenderCmdKongFileOutput, "output-file", "o",
"-", "file to which to write Kong's configuration."+
"Use `-` to write to stdout.")
renderCmd.Flags().StringVar(&fileRenderCmdStateFormat, "format",
"yaml", "output file format: json or yaml.")

return renderCmd
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ It can be used to export, import, or sync entities to Kong.`,
fileCmd.AddCommand(newOpenapi2KongCmd())
fileCmd.AddCommand(newConvertCmd(false))
fileCmd.AddCommand(newValidateCmd()) // alias; since this does both file+online
fileCmd.AddCommand(newFileRenderCmd())
}
return rootCmd
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ this command unless --online flag is used.
_ = sendAnalytics("validate", "", mode)
// read target file
// this does json schema validation as well
targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile)
targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile, false)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions convert/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
output.yaml
81 changes: 72 additions & 9 deletions convert/convert.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package convert

import (
"context"
"fmt"
"strings"

"github.com/blang/semver/v4"
"github.com/kong/deck/cprint"
"github.com/kong/deck/dump"
"github.com/kong/deck/file"
"github.com/kong/deck/state"
"github.com/kong/deck/utils"
"github.com/kong/go-kong/kong"
)

type Format string

const (
// FormatDistributed represents the Deck configuration format.
FormatDistributed Format = "distributed"
// FormatKongGateway represents the Kong gateway format.
FormatKongGateway Format = "kong-gateway"
// FormatKonnect represents the Konnect format.
Expand All @@ -37,38 +43,55 @@ func ParseFormat(key string) (Format, error) {
return FormatKongGateway2x, nil
case FormatKongGateway3x:
return FormatKongGateway3x, nil
case FormatDistributed:
return FormatDistributed, nil
default:
return "", fmt.Errorf("invalid format: '%v'", key)
}
}

func Convert(inputFilename, outputFilename string, from, to Format) error {
func Convert(
inputFilenames []string,
outputFilename string,
outputFormat file.Format,
from Format,
to Format,
mockEnvVars bool,
) error {
var (
outputContent *file.Content
err error
)

inputContent, err := file.GetContentFromFiles([]string{inputFilename})
inputContent, err := file.GetContentFromFiles(inputFilenames, mockEnvVars)
if err != nil {
return err
}

switch {
case from == FormatKongGateway && to == FormatKonnect:
outputContent, err = convertKongGatewayToKonnect(inputContent)
if err != nil {
return err
if len(inputFilenames) > 1 {
return fmt.Errorf("only one input file can be provided when converting from Kong to Konnect format")
}
outputContent, err = convertKongGatewayToKonnect(inputContent)
case from == FormatKongGateway2x && to == FormatKongGateway3x:
outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilename)
if err != nil {
return err
if len(inputFilenames) > 1 {
return fmt.Errorf("only one input file can be provided when converting from Kong 2.x to Kong 3.x format")
}
outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilenames[0])
case from == FormatDistributed && to == FormatKongGateway,
from == FormatDistributed && to == FormatKongGateway2x,
from == FormatDistributed && to == FormatKongGateway3x:
outputContent, err = convertDistributedToKong(inputContent, outputFilename, outputFormat, to)
default:
return fmt.Errorf("cannot convert from '%s' to '%s' format", from, to)
}

err = file.WriteContentToFile(outputContent, outputFilename, file.YAML)
if err != nil {
return err
}

err = file.WriteContentToFile(outputContent, outputFilename, outputFormat)
if err != nil {
return err
}
Expand Down Expand Up @@ -195,3 +218,43 @@ func removeServiceName(service *file.FService) *file.FService {
serviceCopy.ID = kong.String(utils.UUID())
return serviceCopy
}

// convertDistributedToKong is used to convert one or many distributed format
// files to create one Kong Gateway declarative config. It also leverages some
// deck features like the defaults/centralized plugin configurations.
func convertDistributedToKong(
targetContent *file.Content,
outputFilename string,
format file.Format,
kongFormat Format,
) (*file.Content, error) {
var version semver.Version

switch kongFormat { //nolint:exhaustive
case FormatKongGateway,
FormatKongGateway3x:
version = semver.Version{Major: 3, Minor: 0}
case FormatKongGateway2x:
version = semver.Version{Major: 2, Minor: 8}
}

s, _ := state.NewKongState()
rawState, err := file.Get(context.Background(), targetContent, file.RenderConfig{
CurrentState: s,
KongVersion: version,
}, dump.Config{}, nil)
if err != nil {
return nil, err
}
targetState, err := state.Get(rawState)
if err != nil {
return nil, err
}

// file.KongStateToContent calls file.WriteContentToFile
return file.KongStateToContent(targetState, file.WriteConfig{
Filename: outputFilename,
FileFormat: format,
KongVersion: version.String(),
})
}
Loading
Loading