Skip to content

Commit 1e23d9a

Browse files
zekthTieske
authored andcommitted
feat(render): add new command to render final file
1 parent 95bc95f commit 1e23d9a

29 files changed

+671
-79
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@
8989
validated, which allows for working with incomplete or even invalid files in a pipeline.
9090
The functionality is imported from the [go-apiops library](https://github.com/Kong/go-apiops).
9191
[#939](https://github.com/Kong/deck/pull/939)
92+
- Added a new command `file render` to render a final decK file. This will result in a file representing
93+
the state as it would be synced online.
94+
[#939](https://github.com/Kong/deck/pull/939)
9295

9396
### Fixes
9497

cmd/common.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func syncMain(ctx context.Context, filenames []string, dry bool, parallelism,
8484
delay int, workspace string,
8585
) error {
8686
// read target file
87-
targetContent, err := file.GetContentFromFiles(filenames)
87+
targetContent, err := file.GetContentFromFiles(filenames, false)
8888
if err != nil {
8989
return err
9090
}

cmd/common_konnect.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func syncKonnect(ctx context.Context,
183183
httpClient := utils.HTTPClient()
184184

185185
// read target file
186-
targetContent, err := file.GetContentFromFiles(filenames)
186+
targetContent, err := file.GetContentFromFiles(filenames, false)
187187
if err != nil {
188188
return err
189189
}

cmd/file.go

+3-10
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
1-
/*
2-
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
3-
*/
41
package cmd
52

63
import (
74
"github.com/spf13/cobra"
85
)
96

10-
//
11-
//
12-
// Define the CLI data for the file sub-command
13-
//
14-
//
15-
167
func newAddFileCmd() *cobra.Command {
178
addFileCmd := &cobra.Command{
18-
Use: "file [sub-command]...",
9+
Use: "file",
1910
Short: "Sub-command to host the decK file manipulation operations",
2011
Long: `Sub-command to host the decK file manipulation operations`,
2112
}
2213

14+
addFileCmd.AddCommand(newFileRenderCmd())
15+
2316
return addFileCmd
2417
}

cmd/convert.go renamed to cmd/file_convert.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func executeConvert(_ *cobra.Command, _ []string) error {
3838
return nil
3939
}
4040

41-
err = convert.Convert(convertCmdInputFile, convertCmdOutputFile, sourceFormat, destinationFormat)
41+
err = convert.Convert([]string{convertCmdInputFile}, convertCmdOutputFile, sourceFormat, destinationFormat, false)
4242
if err != nil {
4343
return fmt.Errorf("converting file: %w", err)
4444
}
@@ -52,7 +52,7 @@ func executeConvert(_ *cobra.Command, _ []string) error {
5252
return fmt.Errorf("getting files from directory: %w", err)
5353
}
5454
for _, filename := range files {
55-
err = convert.Convert(filename, filename, sourceFormat, destinationFormat)
55+
err = convert.Convert([]string{filename}, filename, sourceFormat, destinationFormat, false)
5656
if err != nil {
5757
return fmt.Errorf("converting '%s' file: %w", filename, err)
5858
}

cmd/file_render.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/blang/semver/v4"
9+
"github.com/kong/deck/file"
10+
"github.com/kong/deck/state"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
var (
15+
fileRenderCmdKongStateFile []string
16+
fileRenderCmdKongFileOutput string
17+
fileRenderCmdStateFormat string
18+
)
19+
20+
func newFileRenderCmd() *cobra.Command {
21+
renderCmd := &cobra.Command{
22+
Use: "render",
23+
Short: "Render the configuration as Kong declarative config",
24+
Long: ``,
25+
Args: validateNoArgs,
26+
RunE: func(cmd *cobra.Command, args []string) error {
27+
return render(cmd.Context(), fileRenderCmdKongStateFile, fileRenderCmdStateFormat)
28+
},
29+
PreRunE: func(cmd *cobra.Command, args []string) error {
30+
if len(fileRenderCmdStateFormat) == 0 {
31+
return fmt.Errorf("a state file with Kong's configuration " +
32+
"must be specified using `-s`/`--state` flag")
33+
}
34+
return preRunSilenceEventsFlag()
35+
},
36+
}
37+
38+
renderCmd.Flags().StringSliceVarP(&fileRenderCmdKongStateFile,
39+
"state", "s", []string{"-"}, "file(s) containing Kong's configuration.\n"+
40+
"This flag can be specified multiple times for multiple files.\n"+
41+
"Use `-` to read from stdin.")
42+
renderCmd.Flags().StringVarP(&fileRenderCmdKongFileOutput, "output-file", "o",
43+
"-", "file to which to write Kong's configuration."+
44+
"Use `-` to write to stdout.")
45+
renderCmd.Flags().StringVar(&fileRenderCmdStateFormat, "format",
46+
"yaml", "output file format: json or yaml.")
47+
48+
return renderCmd
49+
}
50+
51+
func render(ctx context.Context, filenames []string, format string) error {
52+
targetContent, err := file.GetContentFromFiles(filenames, true)
53+
if err != nil {
54+
return err
55+
}
56+
s, _ := state.NewKongState()
57+
rawState, err := file.Get(ctx, targetContent, file.RenderConfig{
58+
CurrentState: s,
59+
KongVersion: semver.Version{Major: 3, Minor: 0},
60+
}, dumpConfig, nil)
61+
if err != nil {
62+
return err
63+
}
64+
targetState, err := state.Get(rawState)
65+
if err != nil {
66+
return err
67+
}
68+
69+
return file.KongStateToFile(targetState, file.WriteConfig{
70+
Filename: fileRenderCmdKongFileOutput,
71+
FileFormat: file.Format(strings.ToUpper(format)),
72+
KongVersion: "3.0.0",
73+
})
74+
}

cmd/root.go

+1
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ It can be used to export, import, or sync entities to Kong.`,
226226
fileCmd.AddCommand(newOpenapi2KongCmd())
227227
fileCmd.AddCommand(newConvertCmd(false))
228228
fileCmd.AddCommand(newValidateCmd()) // alias; since this does both file+online
229+
fileCmd.AddCommand(newFileRenderCmd())
229230
}
230231
return rootCmd
231232
}

cmd/validate.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ this command unless --online flag is used.
4343
_ = sendAnalytics("validate", "", mode)
4444
// read target file
4545
// this does json schema validation as well
46-
targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile)
46+
targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile, false)
4747
if err != nil {
4848
return err
4949
}

convert/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
output.yaml

convert/convert.go

+66-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
package convert
22

33
import (
4+
"context"
45
"fmt"
56
"strings"
67

8+
"github.com/blang/semver/v4"
79
"github.com/kong/deck/cprint"
10+
"github.com/kong/deck/dump"
811
"github.com/kong/deck/file"
12+
"github.com/kong/deck/state"
913
"github.com/kong/deck/utils"
1014
"github.com/kong/go-kong/kong"
1115
)
1216

1317
type Format string
1418

1519
const (
20+
// FormatDistributed represents the Deck configuration format.
21+
FormatDistributed Format = "distributed"
1622
// FormatKongGateway represents the Kong gateway format.
1723
FormatKongGateway Format = "kong-gateway"
1824
// FormatKonnect represents the Konnect format.
@@ -37,38 +43,49 @@ func ParseFormat(key string) (Format, error) {
3743
return FormatKongGateway2x, nil
3844
case FormatKongGateway3x:
3945
return FormatKongGateway3x, nil
46+
case FormatDistributed:
47+
return FormatDistributed, nil
4048
default:
4149
return "", fmt.Errorf("invalid format: '%v'", key)
4250
}
4351
}
4452

45-
func Convert(inputFilename, outputFilename string, from, to Format) error {
53+
func Convert(inputFilenames []string, outputFilename string, from, to Format, mockEnvVars bool) error {
54+
const outputFormat = file.YAML
4655
var (
4756
outputContent *file.Content
4857
err error
4958
)
5059

51-
inputContent, err := file.GetContentFromFiles([]string{inputFilename})
60+
inputContent, err := file.GetContentFromFiles(inputFilenames, mockEnvVars)
5261
if err != nil {
5362
return err
5463
}
5564

5665
switch {
5766
case from == FormatKongGateway && to == FormatKonnect:
58-
outputContent, err = convertKongGatewayToKonnect(inputContent)
59-
if err != nil {
60-
return err
67+
if len(inputFilenames) > 1 {
68+
return fmt.Errorf("only one input file can be provided when converting from Kong to Konnect format")
6169
}
70+
outputContent, err = convertKongGatewayToKonnect(inputContent)
6271
case from == FormatKongGateway2x && to == FormatKongGateway3x:
63-
outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilename)
64-
if err != nil {
65-
return err
72+
if len(inputFilenames) > 1 {
73+
return fmt.Errorf("only one input file can be provided when converting from Kong 2.x to Kong 3.x format")
6674
}
75+
outputContent, err = convertKongGateway2xTo3x(inputContent, inputFilenames[0])
76+
case from == FormatDistributed && to == FormatKongGateway,
77+
from == FormatDistributed && to == FormatKongGateway2x,
78+
from == FormatDistributed && to == FormatKongGateway3x:
79+
outputContent, err = convertDistributedToKong(inputContent, outputFilename, outputFormat, to)
6780
default:
6881
return fmt.Errorf("cannot convert from '%s' to '%s' format", from, to)
6982
}
7083

71-
err = file.WriteContentToFile(outputContent, outputFilename, file.YAML)
84+
if err != nil {
85+
return err
86+
}
87+
88+
err = file.WriteContentToFile(outputContent, outputFilename, outputFormat)
7289
if err != nil {
7390
return err
7491
}
@@ -195,3 +212,43 @@ func removeServiceName(service *file.FService) *file.FService {
195212
serviceCopy.ID = kong.String(utils.UUID())
196213
return serviceCopy
197214
}
215+
216+
// convertDistributedToKong is used to convert one or many distributed format
217+
// files to create one Kong Gateway declarative config. It also leverages some
218+
// deck features like the defaults/centralized plugin configurations.
219+
func convertDistributedToKong(
220+
targetContent *file.Content,
221+
outputFilename string,
222+
format file.Format,
223+
kongFormat Format,
224+
) (*file.Content, error) {
225+
var version semver.Version
226+
227+
switch kongFormat { //nolint:exhaustive
228+
case FormatKongGateway,
229+
FormatKongGateway3x:
230+
version = semver.Version{Major: 3, Minor: 0}
231+
case FormatKongGateway2x:
232+
version = semver.Version{Major: 2, Minor: 8}
233+
}
234+
235+
s, _ := state.NewKongState()
236+
rawState, err := file.Get(context.Background(), targetContent, file.RenderConfig{
237+
CurrentState: s,
238+
KongVersion: version,
239+
}, dump.Config{}, nil)
240+
if err != nil {
241+
return nil, err
242+
}
243+
targetState, err := state.Get(rawState)
244+
if err != nil {
245+
return nil, err
246+
}
247+
248+
// file.KongStateToContent calls file.WriteContentToFile
249+
return file.KongStateToContent(targetState, file.WriteConfig{
250+
Filename: outputFilename,
251+
FileFormat: format,
252+
KongVersion: version.String(),
253+
})
254+
}

0 commit comments

Comments
 (0)