diff --git a/cmd/dump.go b/cmd/dump.go deleted file mode 100644 index b6b7bdfbc..000000000 --- a/cmd/dump.go +++ /dev/null @@ -1,179 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "strings" - - "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" - "github.com/spf13/cobra" -) - -var ( - dumpCmdKongStateFile string - dumpCmdStateFormat string - dumpWorkspace string - dumpAllWorkspaces bool - dumpWithID bool -) - -func listWorkspaces(ctx context.Context, client *kong.Client) ([]string, error) { - workspaces, err := client.Workspaces.ListAll(ctx) - if err != nil { - return nil, fmt.Errorf("fetching workspaces from Kong: %w", err) - } - var res []string - for _, workspace := range workspaces { - res = append(res, *workspace.Name) - } - - return res, nil -} - -// newDumpCmd represents the dump command -func newDumpCmd() *cobra.Command { - dumpCmd := &cobra.Command{ - Use: "dump", - Short: "Export Kong configuration to a file", - Long: `The dump command reads all entities present in Kong -and writes them to a local file. - -The file can then be read using the sync command or diff command to -configure Kong.`, - Args: validateNoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - - if yes, err := utils.ConfirmFileOverwrite(dumpCmdKongStateFile, dumpCmdStateFormat, assumeYes); err != nil { - return err - } else if !yes { - return nil - } - - if inKonnectMode(nil) { - _ = sendAnalytics("dump", "", modeKonnect) - return dumpKonnectV2(ctx) - } - - wsClient, err := utils.GetKongClient(rootConfig) - if err != nil { - return err - } - - format := file.Format(strings.ToUpper(dumpCmdStateFormat)) - - kongVersion, err := fetchKongVersion(ctx, rootConfig.ForWorkspace(dumpWorkspace)) - if err != nil { - return fmt.Errorf("reading Kong version: %w", err) - } - _ = sendAnalytics("dump", kongVersion, modeKong) - - // Kong Enterprise dump all workspace - if dumpAllWorkspaces { - if dumpWorkspace != "" { - return fmt.Errorf("workspace cannot be specified with --all-workspace flag") - } - if dumpCmdKongStateFile != "kong" { - return fmt.Errorf("output-file cannot be specified with --all-workspace flag") - } - workspaces, err := listWorkspaces(ctx, wsClient) - if err != nil { - return err - } - - for _, workspace := range workspaces { - wsClient, err := utils.GetKongClient(rootConfig.ForWorkspace(workspace)) - if err != nil { - return err - } - - rawState, err := dump.Get(ctx, wsClient, dumpConfig) - if err != nil { - return fmt.Errorf("reading configuration from Kong: %w", err) - } - ks, err := state.Get(rawState) - if err != nil { - return fmt.Errorf("building state: %w", err) - } - - if err := file.KongStateToFile(ks, file.WriteConfig{ - SelectTags: dumpConfig.SelectorTags, - Workspace: workspace, - Filename: workspace, - FileFormat: format, - WithID: dumpWithID, - KongVersion: kongVersion, - }); err != nil { - return err - } - } - return nil - } - - // Kong OSS - // or Kong Enterprise single workspace - if dumpWorkspace != "" { - wsConfig := rootConfig.ForWorkspace(dumpWorkspace) - exists, err := workspaceExists(ctx, rootConfig, dumpWorkspace) - if err != nil { - return err - } - if !exists { - return fmt.Errorf("workspace '%v' does not exist in Kong", dumpWorkspace) - } - wsClient, err = utils.GetKongClient(wsConfig) - if err != nil { - return err - } - } - - rawState, err := dump.Get(ctx, wsClient, dumpConfig) - if err != nil { - return fmt.Errorf("reading configuration from Kong: %w", err) - } - ks, err := state.Get(rawState) - if err != nil { - return fmt.Errorf("building state: %w", err) - } - return file.KongStateToFile(ks, file.WriteConfig{ - SelectTags: dumpConfig.SelectorTags, - Workspace: dumpWorkspace, - Filename: dumpCmdKongStateFile, - FileFormat: format, - WithID: dumpWithID, - KongVersion: kongVersion, - }) - }, - } - - dumpCmd.Flags().StringVarP(&dumpCmdKongStateFile, "output-file", "o", - "kong", "file to which to write Kong's configuration."+ - "Use `-` to write to stdout.") - dumpCmd.Flags().StringVar(&dumpCmdStateFormat, "format", - "yaml", "output file format: json or yaml.") - dumpCmd.Flags().BoolVar(&dumpWithID, "with-id", - false, "write ID of all entities in the output") - dumpCmd.Flags().StringVarP(&dumpWorkspace, "workspace", "w", - "", "dump configuration of a specific Workspace"+ - "(Kong Enterprise only).") - dumpCmd.Flags().BoolVar(&dumpAllWorkspaces, "all-workspaces", - false, "dump configuration of all Workspaces (Kong Enterprise only).") - dumpCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", - false, "skip exporting consumers, consumer-groups and any plugins associated "+ - "with them.") - dumpCmd.Flags().StringSliceVar(&dumpConfig.SelectorTags, - "select-tag", []string{}, - "only entities matching tags specified with this flag are exported.\n"+ - "When this setting has multiple tag values, entities must match every tag.") - dumpCmd.Flags().BoolVar(&dumpConfig.RBACResourcesOnly, "rbac-resources-only", - false, "export only the RBAC resources (Kong Enterprise only).") - dumpCmd.Flags().BoolVar(&assumeYes, "yes", - false, "assume `yes` to prompts and run non-interactively.") - dumpCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", - false, "do not dump CA certificates.") - return dumpCmd -} diff --git a/cmd/file.go b/cmd/file.go index 71b767f4a..40d98721a 100644 --- a/cmd/file.go +++ b/cmd/file.go @@ -4,12 +4,12 @@ import ( "github.com/spf13/cobra" ) -func newAddFileCmd() *cobra.Command { - addFileCmd := &cobra.Command{ +func newFileSubCmd() *cobra.Command { + fileCmd := &cobra.Command{ Use: "file [sub-command]...", - Short: "Subcommand to host the decK file manipulation operations", - Long: `Subcommand to host the decK file manipulation operations.`, + Short: "Subcommand to host the decK file operations", + Long: `Subcommand to host the decK file operations.`, } - return addFileCmd + return fileCmd } diff --git a/cmd/file_convert.go b/cmd/file_convert.go index 60ec957db..6c6897404 100644 --- a/cmd/file_convert.go +++ b/cmd/file_convert.go @@ -81,9 +81,21 @@ func executeConvert(_ *cobra.Command, _ []string) error { } // newConvertCmd represents the convert command -func newConvertCmd() *cobra.Command { +func newConvertCmd(deprecated bool) *cobra.Command { short := "Convert files from one format into another format" execute := executeConvert + fileInDefault := "-" + fileOutDefault := "-" + if deprecated { + short = "[deprecated] use 'file convert' instead" + execute = func(cmd *cobra.Command, args []string) error { + cprint.UpdatePrintf("Warning: 'deck convert' is DEPRECATED and will be removed in a future version. " + + "Use 'deck file convert' instead.\n") + return executeConvert(cmd, args) + } + fileInDefault = "" + fileOutDefault = "kong.yaml" + } convertCmd := &cobra.Command{ Use: "convert", @@ -101,9 +113,9 @@ 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", "", + convertCmd.Flags().StringVar(&convertCmdInputFile, "input-file", fileInDefault, "configuration file to be converted. Use `-` to read from stdin.") - convertCmd.Flags().StringVar(&convertCmdOutputFile, "output-file", "kong.yaml", + convertCmd.Flags().StringVarP(&convertCmdOutputFile, "output-file", "o", fileOutDefault, "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.") diff --git a/cmd/gateway.go b/cmd/gateway.go new file mode 100644 index 000000000..1a0bc4cc1 --- /dev/null +++ b/cmd/gateway.go @@ -0,0 +1,15 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +func newKongSubCmd() *cobra.Command { + kongSubCmd := &cobra.Command{ + Use: "gateway [sub-command]...", + Short: "Subcommand to host the decK network operations", + Long: `Subcommand to host the decK network operations.`, + } + + return kongSubCmd +} diff --git a/cmd/diff.go b/cmd/gateway_diff.go similarity index 58% rename from cmd/diff.go rename to cmd/gateway_diff.go index b98df6692..c3a1be28c 100644 --- a/cmd/diff.go +++ b/cmd/gateway_diff.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" + "github.com/kong/deck/cprint" "github.com/spf13/cobra" ) @@ -14,35 +15,63 @@ var ( diffJSONOutput bool ) +func executeDiff(cmd *cobra.Command, _ []string) error { + return syncMain(cmd.Context(), diffCmdKongStateFile, true, + diffCmdParallelism, 0, diffWorkspace, diffJSONOutput) +} + // newDiffCmd represents the diff command -func newDiffCmd() *cobra.Command { - diffCmd := &cobra.Command{ - Use: "diff", - Short: "Diff the current entities in Kong with the one on disks", - Long: `The diff command is similar to a dry run of the 'decK sync' command. +func newDiffCmd(deprecated bool) *cobra.Command { + use := "diff [flags] [kong-state-files...]" + short := "Diff the current entities in Kong with the one on disks" + execute := executeDiff + argsValidator := cobra.MinimumNArgs(0) + preRun := func(cmd *cobra.Command, args []string) error { + diffCmdKongStateFile = args + if len(diffCmdKongStateFile) == 0 { + diffCmdKongStateFile = []string{"-"} + } + return preRunSilenceEventsFlag() + } -It loads entities from Kong and performs a diff with -the entities in local files. This allows you to see the entities -that will be created, updated, or deleted. -`, - Args: validateNoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return syncMain(cmd.Context(), diffCmdKongStateFile, true, - diffCmdParallelism, 0, diffWorkspace, diffJSONOutput) - }, - PreRunE: func(cmd *cobra.Command, args []string) error { + if deprecated { + use = "diff" + short = "[deprecated] use 'gateway diff' instead" + execute = func(cmd *cobra.Command, args []string) error { + cprint.UpdatePrintf("Warning: 'deck diff' is DEPRECATED and will be removed in a future version. " + + "Use 'deck gateway diff' instead.\n") + return executeDiff(cmd, args) + } + argsValidator = validateNoArgs + preRun = func(cmd *cobra.Command, args []string) error { if len(diffCmdKongStateFile) == 0 { return fmt.Errorf("a state file with Kong's configuration " + "must be specified using `-s`/`--state` flag") } return preRunSilenceEventsFlag() - }, + } } - diffCmd.Flags().StringSliceVarP(&diffCmdKongStateFile, - "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ - "This flag can be specified multiple times for multiple files.\n"+ - "Use `-` to read from stdin.") + diffCmd := &cobra.Command{ + Use: use, + Short: short, + Long: `The diff command is similar to a dry run of the 'decK kong sync' command. + +It loads entities from Kong and performs a diff with +the entities in local files. This allows you to see the entities +that will be created, updated, or deleted. +`, + Args: argsValidator, + RunE: execute, + PreRunE: preRun, + } + + if deprecated { + diffCmd.Flags().StringSliceVarP(&diffCmdKongStateFile, + "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ + "This flag can be specified multiple times for multiple files.\n"+ + "Use `-` to read from stdin.") + } diffCmd.Flags().StringVarP(&diffWorkspace, "workspace", "w", "", "Diff configuration with a specific workspace "+ "(Kong Enterprise only).\n"+ diff --git a/cmd/gateway_dump.go b/cmd/gateway_dump.go new file mode 100644 index 000000000..95637646a --- /dev/null +++ b/cmd/gateway_dump.go @@ -0,0 +1,197 @@ +package cmd + +import ( + "context" + "fmt" + "strings" + + "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" + "github.com/spf13/cobra" +) + +const defaultFileOutName = "kong" + +var ( + dumpCmdKongStateFile string + dumpCmdStateFormat string + dumpWorkspace string + dumpAllWorkspaces bool + dumpWithID bool +) + +func listWorkspaces(ctx context.Context, client *kong.Client) ([]string, error) { + workspaces, err := client.Workspaces.ListAll(ctx) + if err != nil { + return nil, fmt.Errorf("fetching workspaces from Kong: %w", err) + } + var res []string + for _, workspace := range workspaces { + res = append(res, *workspace.Name) + } + + return res, nil +} + +func executeDump(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + + if yes, err := utils.ConfirmFileOverwrite(dumpCmdKongStateFile, dumpCmdStateFormat, assumeYes); err != nil { + return err + } else if !yes { + return nil + } + + if inKonnectMode(nil) { + _ = sendAnalytics("dump", "", modeKonnect) + return dumpKonnectV2(ctx) + } + + wsClient, err := utils.GetKongClient(rootConfig) + if err != nil { + return err + } + + format := file.Format(strings.ToUpper(dumpCmdStateFormat)) + + kongVersion, err := fetchKongVersion(ctx, rootConfig.ForWorkspace(dumpWorkspace)) + if err != nil { + return fmt.Errorf("reading Kong version: %w", err) + } + _ = sendAnalytics("dump", kongVersion, modeKong) + + // Kong Enterprise dump all workspace + if dumpAllWorkspaces { + if dumpWorkspace != "" { + return fmt.Errorf("workspace cannot be specified with --all-workspace flag") + } + if dumpCmdKongStateFile != defaultFileOutName { + return fmt.Errorf("output-file cannot be specified with --all-workspace flag") + } + workspaces, err := listWorkspaces(ctx, wsClient) + if err != nil { + return err + } + + for _, workspace := range workspaces { + wsClient, err := utils.GetKongClient(rootConfig.ForWorkspace(workspace)) + if err != nil { + return err + } + + rawState, err := dump.Get(ctx, wsClient, dumpConfig) + if err != nil { + return fmt.Errorf("reading configuration from Kong: %w", err) + } + ks, err := state.Get(rawState) + if err != nil { + return fmt.Errorf("building state: %w", err) + } + + if err := file.KongStateToFile(ks, file.WriteConfig{ + SelectTags: dumpConfig.SelectorTags, + Workspace: workspace, + Filename: workspace, + FileFormat: format, + WithID: dumpWithID, + KongVersion: kongVersion, + }); err != nil { + return err + } + } + return nil + } + + // Kong OSS + // or Kong Enterprise single workspace + if dumpWorkspace != "" { + wsConfig := rootConfig.ForWorkspace(dumpWorkspace) + exists, err := workspaceExists(ctx, rootConfig, dumpWorkspace) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("workspace '%v' does not exist in Kong", dumpWorkspace) + } + wsClient, err = utils.GetKongClient(wsConfig) + if err != nil { + return err + } + } + + rawState, err := dump.Get(ctx, wsClient, dumpConfig) + if err != nil { + return fmt.Errorf("reading configuration from Kong: %w", err) + } + ks, err := state.Get(rawState) + if err != nil { + return fmt.Errorf("building state: %w", err) + } + return file.KongStateToFile(ks, file.WriteConfig{ + SelectTags: dumpConfig.SelectorTags, + Workspace: dumpWorkspace, + Filename: dumpCmdKongStateFile, + FileFormat: format, + WithID: dumpWithID, + KongVersion: kongVersion, + }) +} + +// newDumpCmd represents the dump command +func newDumpCmd(deprecated bool) *cobra.Command { + short := "Export Kong configuration to a file" + execute := executeDump + fileOutDefault := "-" + if deprecated { + short = "[deprecated] use 'gateway dump' instead" + execute = func(cmd *cobra.Command, args []string) error { + cprint.UpdatePrintf("Warning: 'deck dump' is DEPRECATED and will be removed in a future version. " + + "Use 'deck gateway dump' instead.\n") + return executeDump(cmd, args) + } + fileOutDefault = defaultFileOutName + } + + dumpCmd := &cobra.Command{ + Use: "dump", + Short: short, + Long: `The dump command reads all entities present in Kong +and writes them to a local file. + +The file can then be read using the sync command or diff command to +configure Kong.`, + Args: validateNoArgs, + RunE: execute, + } + + dumpCmd.Flags().StringVarP(&dumpCmdKongStateFile, "output-file", "o", + fileOutDefault, "file to which to write Kong's configuration."+ + "Use `-` to write to stdout.") + dumpCmd.Flags().StringVar(&dumpCmdStateFormat, "format", + "yaml", "output file format: json or yaml.") + dumpCmd.Flags().BoolVar(&dumpWithID, "with-id", + false, "write ID of all entities in the output") + dumpCmd.Flags().StringVarP(&dumpWorkspace, "workspace", "w", + "", "dump configuration of a specific Workspace"+ + "(Kong Enterprise only).") + dumpCmd.Flags().BoolVar(&dumpAllWorkspaces, "all-workspaces", + false, "dump configuration of all Workspaces (Kong Enterprise only).") + dumpCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", + false, "skip exporting consumers, consumer-groups and any plugins associated "+ + "with them.") + dumpCmd.Flags().StringSliceVar(&dumpConfig.SelectorTags, + "select-tag", []string{}, + "only entities matching tags specified with this flag are exported.\n"+ + "When this setting has multiple tag values, entities must match every tag.") + dumpCmd.Flags().BoolVar(&dumpConfig.RBACResourcesOnly, "rbac-resources-only", + false, "export only the RBAC resources (Kong Enterprise only).") + dumpCmd.Flags().BoolVar(&assumeYes, "yes", + false, "assume `yes` to prompts and run non-interactively.") + dumpCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", + false, "do not dump CA certificates.") + return dumpCmd +} diff --git a/cmd/ping.go b/cmd/gateway_ping.go similarity index 70% rename from cmd/ping.go rename to cmd/gateway_ping.go index 4a59e59dd..71ee20dea 100644 --- a/cmd/ping.go +++ b/cmd/gateway_ping.go @@ -4,28 +4,42 @@ import ( "context" "fmt" + "github.com/kong/deck/cprint" "github.com/kong/deck/utils" "github.com/spf13/cobra" ) var pingWorkspace string +func executePing(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + mode := getMode(nil) + if mode == modeKonnect { + return pingKonnect(ctx) + } + return pingKong(ctx) +} + // newPingCmd represents the ping command -func newPingCmd() *cobra.Command { +func newPingCmd(deprecated bool) *cobra.Command { + short := "Verify connectivity with Kong" + execute := executePing + if deprecated { + short = "[deprecated] use 'gateway ping' instead" + execute = func(cmd *cobra.Command, args []string) error { + cprint.UpdatePrintf("Warning: 'deck ping' is DEPRECATED and will be removed in a future version. " + + "Use 'deck gateway ping' instead.\n") + return executePing(cmd, args) + } + } + pingCmd := &cobra.Command{ Use: "ping", - Short: "Verify connectivity with Kong", + Short: short, Long: `The ping command can be used to verify if decK can connect to Kong's Admin API.`, Args: validateNoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - mode := getMode(nil) - if mode == modeKonnect { - return pingKonnect(ctx) - } - return pingKong(ctx) - }, + RunE: execute, } pingCmd.Flags().StringVarP(&pingWorkspace, "workspace", "w", diff --git a/cmd/gateway_reset.go b/cmd/gateway_reset.go new file mode 100644 index 000000000..faf8ac00a --- /dev/null +++ b/cmd/gateway_reset.go @@ -0,0 +1,158 @@ +package cmd + +import ( + "fmt" + + "github.com/kong/deck/cprint" + "github.com/kong/deck/state" + "github.com/kong/deck/utils" + "github.com/spf13/cobra" +) + +var ( + resetCmdForce bool + resetWorkspace string + resetAllWorkspaces bool + resetJSONOutput bool +) + +func executeReset(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + + if resetAllWorkspaces && resetWorkspace != "" { + return fmt.Errorf("workspace cannot be specified with --all-workspace flag") + } + + if !resetCmdForce { + ok, err := utils.Confirm("This will delete all configuration from Kong's database." + + "\n> Are you sure? ") + if err != nil { + return err + } + if !ok { + return nil + } + } + + mode := getMode(nil) + if mode == modeKonnect { + _ = sendAnalytics("reset", "", mode) + return resetKonnectV2(ctx) + } + + rootClient, err := utils.GetKongClient(rootConfig) + if err != nil { + return err + } + + kongVersion, err := fetchKongVersion(ctx, rootConfig.ForWorkspace(resetWorkspace)) + if err != nil { + return fmt.Errorf("reading Kong version: %w", err) + } + parsedKongVersion, err := utils.ParseKongVersion(kongVersion) + if err != nil { + return fmt.Errorf("parsing Kong version: %w", err) + } + _ = sendAnalytics("reset", kongVersion, mode) + + if utils.Kong340Version.LTE(parsedKongVersion) { + dumpConfig.IsConsumerGroupScopedPluginSupported = true + } + + var workspaces []string + // Kong OSS or default workspace + if !resetAllWorkspaces && resetWorkspace == "" { + workspaces = append(workspaces, "") + } + + // Kong Enterprise + if resetAllWorkspaces { + workspaces, err = listWorkspaces(ctx, rootClient) + if err != nil { + return err + } + } + if resetWorkspace != "" { + exists, err := workspaceExists(ctx, rootConfig, resetWorkspace) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("workspace '%v' does not exist in Kong", resetWorkspace) + } + + workspaces = append(workspaces, resetWorkspace) + } + + for _, workspace := range workspaces { + wsClient, err := utils.GetKongClient(rootConfig.ForWorkspace(workspace)) + if err != nil { + return err + } + currentState, err := fetchCurrentState(ctx, wsClient, dumpConfig) + if err != nil { + return err + } + targetState, err := state.NewKongState() + if err != nil { + return err + } + _, err = performDiff(ctx, currentState, targetState, false, 10, 0, wsClient, false, resetJSONOutput) + if err != nil { + return err + } + } + return nil +} + +// newResetCmd represents the reset command +func newResetCmd(deprecated bool) *cobra.Command { + short := "Reset deletes all entities in Kong" + execute := executeReset + if deprecated { + short = "[deprecated] use 'gateway reset' instead" + execute = func(cmd *cobra.Command, args []string) error { + cprint.UpdatePrintf("Warning: 'deck reset' is DEPRECATED and will be removed in a future version. " + + "Use 'deck gateway reset' instead.\n") + return executeReset(cmd, args) + } + } + + resetCmd := &cobra.Command{ + Use: "reset", + Short: short, + Long: `The reset command deletes all entities in Kong's database.string. + +Use this command with extreme care as it's equivalent to running +"kong migrations reset" on your Kong instance. + +By default, this command will ask for confirmation.`, + Args: validateNoArgs, + RunE: execute, + } + + resetCmd.Flags().BoolVarP(&resetCmdForce, "force", "f", + false, "Skip interactive confirmation prompt before reset.") + resetCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", + false, "do not reset consumers, consumer-groups or "+ + "any plugins associated with consumers.") + resetCmd.Flags().StringVarP(&resetWorkspace, "workspace", "w", + "", "reset configuration of a specific workspace"+ + "(Kong Enterprise only).") + resetCmd.Flags().BoolVar(&resetAllWorkspaces, "all-workspaces", + false, "reset configuration of all workspaces (Kong Enterprise only).") + resetCmd.Flags().BoolVar(&noMaskValues, "no-mask-deck-env-vars-value", + false, "do not mask DECK_ environment variable values at diff output.") + resetCmd.Flags().StringSliceVar(&dumpConfig.SelectorTags, + "select-tag", []string{}, + "only entities matching tags specified via this flag are deleted.\n"+ + "When this setting has multiple tag values, entities must match every tag.") + resetCmd.Flags().BoolVar(&dumpConfig.RBACResourcesOnly, "rbac-resources-only", + false, "reset only the RBAC resources (Kong Enterprise only).") + resetCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", + false, "do not reset CA certificates.") + resetCmd.Flags().BoolVar(&resetJSONOutput, "json-output", + false, "generate command execution report in a JSON format") + + return resetCmd +} diff --git a/cmd/sync.go b/cmd/gateway_sync.go similarity index 58% rename from cmd/sync.go rename to cmd/gateway_sync.go index e8d6d5e1b..f207756bd 100644 --- a/cmd/sync.go +++ b/cmd/gateway_sync.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" + "github.com/kong/deck/cprint" "github.com/spf13/cobra" ) @@ -13,33 +14,61 @@ var ( syncJSONOutput bool ) +var syncCmdKongStateFile []string + +func executeSync(cmd *cobra.Command, _ []string) error { + return syncMain(cmd.Context(), syncCmdKongStateFile, false, + syncCmdParallelism, syncCmdDBUpdateDelay, syncWorkspace, syncJSONOutput) +} + // newSyncCmd represents the sync command -func newSyncCmd() *cobra.Command { - var syncCmdKongStateFile []string - syncCmd := &cobra.Command{ - Use: "sync", - Short: "Sync performs operations to get Kong's configuration " + - "to match the state file", - Long: `The sync command reads the state file and performs operation on Kong -to get Kong's state in sync with the input state.`, - Args: validateNoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return syncMain(cmd.Context(), syncCmdKongStateFile, false, - syncCmdParallelism, syncCmdDBUpdateDelay, syncWorkspace, syncJSONOutput) - }, - PreRunE: func(cmd *cobra.Command, args []string) error { +func newSyncCmd(deprecated bool) *cobra.Command { + use := "sync [flags] [kong-state-files...]" + short := "Sync performs operations to get Kong's configuration to match the state file" + execute := executeSync + argsValidator := cobra.MinimumNArgs(0) + preRun := func(cmd *cobra.Command, args []string) error { + syncCmdKongStateFile = args + if len(syncCmdKongStateFile) == 0 { + syncCmdKongStateFile = []string{"-"} + } + return preRunSilenceEventsFlag() + } + + if deprecated { + use = "sync" + short = "[deprecated] use 'gateway sync' instead" + execute = func(cmd *cobra.Command, args []string) error { + cprint.UpdatePrintf("Warning: 'deck sync' is DEPRECATED and will be removed in a future version. " + + "Use 'deck gateway sync' instead.\n") + return executeSync(cmd, args) + } + argsValidator = validateNoArgs + preRun = func(cmd *cobra.Command, args []string) error { if len(syncCmdKongStateFile) == 0 { return fmt.Errorf("a state file with Kong's configuration " + "must be specified using `-s`/`--state` flag") } return preRunSilenceEventsFlag() - }, + } } - syncCmd.Flags().StringSliceVarP(&syncCmdKongStateFile, - "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ - "This flag can be specified multiple times for multiple files.\n"+ - "Use `-` to read from stdin.") + syncCmd := &cobra.Command{ + Use: use, + Short: short, + Long: `The sync command reads the state file and performs operation on Kong +to get Kong's state in sync with the input state.`, + Args: argsValidator, + RunE: execute, + PreRunE: preRun, + } + + if deprecated { + syncCmd.Flags().StringSliceVarP(&syncCmdKongStateFile, + "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ + "This flag can be specified multiple times for multiple files.\n"+ + "Use `-` to read from stdin.") + } syncCmd.Flags().StringVarP(&syncWorkspace, "workspace", "w", "", "Sync configuration to a specific workspace "+ "(Kong Enterprise only).\n"+ diff --git a/cmd/validate.go b/cmd/gateway_validate.go similarity index 56% rename from cmd/validate.go rename to cmd/gateway_validate.go index d44f4d000..30e478570 100644 --- a/cmd/validate.go +++ b/cmd/gateway_validate.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/kong/deck/cprint" "github.com/kong/deck/dump" "github.com/kong/deck/file" "github.com/kong/deck/state" @@ -22,86 +23,136 @@ var ( validateJSONOutput bool ) +func executeValidate(cmd *cobra.Command, _ []string) error { + mode := getMode(nil) + if validateOnline && mode == modeKonnect { + return fmt.Errorf("online validation not yet supported in konnect mode") + } + _ = sendAnalytics("validate", "", mode) + // read target file + // this does json schema validation as well + targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile, false) + if err != nil { + return err + } + + dummyEmptyState, err := state.NewKongState() + if err != nil { + return err + } + ctx := cmd.Context() + var kongClient *kong.Client + if validateOnline { + kongClient, err = getKongClient(ctx, targetContent) + if err != nil { + return err + } + } + + rawState, err := file.Get(ctx, targetContent, file.RenderConfig{ + CurrentState: dummyEmptyState, + }, dump.Config{}, kongClient) + if err != nil { + return err + } + if err := checkForRBACResources(*rawState, validateCmdRBACResourcesOnly); err != nil { + return err + } + // this catches foreign relation errors + ks, err := state.Get(rawState) + if err != nil { + return err + } + + if validateOnline { + if errs := validateWithKong(ctx, kongClient, ks); len(errs) != 0 { + return validate.ErrorsWrapper{Errors: errs} + } + } + return nil +} + // newValidateCmd represents the diff command -func newValidateCmd() *cobra.Command { - validateCmd := &cobra.Command{ - Use: "validate", - Short: "Validate the state file", - Long: `The validate command reads the state file and ensures validity. +func newValidateCmd(deprecated bool, online bool) *cobra.Command { + use := "validate [flags] [kong-state-files...]" + short := "Validate the state file" + long := `The validate command reads the state file and ensures validity. It reads all the specified state files and reports YAML/JSON parsing issues. It also checks for foreign relationships and alerts if there are broken relationships, or missing links present. -No communication takes places between decK and Kong during the execution of -this command unless --online flag is used. -`, - Args: validateNoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - mode := getMode(nil) - if validateOnline && mode == modeKonnect { - return fmt.Errorf("online validation not yet supported in konnect mode") - } - _ = sendAnalytics("validate", "", mode) - // read target file - // this does json schema validation as well - targetContent, err := file.GetContentFromFiles(validateCmdKongStateFile, false) - if err != nil { - return err - } +` + execute := executeValidate + argsValidator := cobra.MinimumNArgs(0) + preRun := func(cmd *cobra.Command, args []string) error { + diffCmdKongStateFile = args + if len(diffCmdKongStateFile) == 0 { + diffCmdKongStateFile = []string{"-"} + } + return preRunSilenceEventsFlag() + } - dummyEmptyState, err := state.NewKongState() - if err != nil { - return err - } - ctx := cmd.Context() - var kongClient *kong.Client - if validateOnline { - kongClient, err = getKongClient(ctx, targetContent) - if err != nil { - return err - } - } + if deprecated { + use = "validate" + short = "[deprecated] use 'gateway validate' or 'file validate' instead" + long = `The validate command reads the state file and ensures validity. +It reads all the specified state files and reports YAML/JSON +parsing issues. It also checks for foreign relationships +and alerts if there are broken relationships, or missing links present. - rawState, err := file.Get(ctx, targetContent, file.RenderConfig{ - CurrentState: dummyEmptyState, - }, dump.Config{}, kongClient) - if err != nil { - return err - } - if err := checkForRBACResources(*rawState, validateCmdRBACResourcesOnly); err != nil { - return err - } - // this catches foreign relation errors - ks, err := state.Get(rawState) - if err != nil { - return err - } +No communication takes places between decK and Kong during the execution of +this command unless --online flag is used. +` - if validateOnline { - if errs := validateWithKong(ctx, kongClient, ks); len(errs) != 0 { - return validate.ErrorsWrapper{Errors: errs} - } - } - return nil - }, - PreRunE: func(cmd *cobra.Command, args []string) error { - if len(validateCmdKongStateFile) == 0 { + execute = func(cmd *cobra.Command, args []string) error { + cprint.UpdatePrintf("Warning: 'deck validate' is DEPRECATED and will be removed in a future version. " + + "Use 'deck gateway validate' instead.\n") + return executeValidate(cmd, args) + } + argsValidator = validateNoArgs + preRun = func(cmd *cobra.Command, args []string) error { + if len(diffCmdKongStateFile) == 0 { return fmt.Errorf("a state file with Kong's configuration " + - "must be specified using -s/--state flag") + "must be specified using `-s`/`--state` flag") } - return nil - }, + return preRunSilenceEventsFlag() + } + } else { + validateOnline = online + if validateOnline { + short = short + " (online)" + long = long + "Validates against the Kong API, via communication with Kong. This increases the\n" + + "time for validation but catches significant errors. No resource is created in Kong.\n" + + "For offline validation see 'deck file validate'.\n" + } else { + short = short + " (locally)" + long = long + "No communication takes places between decK and Kong during the execution of\n" + + "this command. This is faster than the online validation, but catches fewer errors.\n" + + "For online validation see 'deck gateway validate'.\n" + } + } + + validateCmd := &cobra.Command{ + Use: use, + Short: short, + Long: long, + Args: argsValidator, + RunE: execute, + PreRunE: preRun, } + validateCmd.Flags().BoolVar(&validateCmdRBACResourcesOnly, "rbac-resources-only", false, "indicate that the state file(s) contains RBAC resources only (Kong Enterprise only).") - validateCmd.Flags().StringSliceVarP(&validateCmdKongStateFile, - "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ - "This flag can be specified multiple times for multiple files.\n"+ - "Use '-' to read from stdin.") - validateCmd.Flags().BoolVar(&validateOnline, "online", - false, "perform validations against Kong API. When this flag is used, validation is done\n"+ - "via communication with Kong. This increases the time for validation but catches \n"+ - "significant errors. No resource is created in Kong.") + if deprecated { + validateCmd.Flags().StringSliceVarP(&validateCmdKongStateFile, + "state", "s", []string{"kong.yaml"}, "file(s) containing Kong's configuration.\n"+ + "This flag can be specified multiple times for multiple files.\n"+ + "Use '-' to read from stdin.") + validateCmd.Flags().BoolVar(&validateOnline, "online", + false, "perform validations against Kong API. When this flag is used, validation is done\n"+ + "via communication with Kong. This increases the time for validation but catches \n"+ + "significant errors. No resource is created in Kong.") + } validateCmd.Flags().StringVarP(&validateWorkspace, "workspace", "w", "", "validate configuration of a specific workspace "+ "(Kong Enterprise only).\n"+ diff --git a/cmd/konnect.go b/cmd/konnect.go index a5196c560..a9f857590 100644 --- a/cmd/konnect.go +++ b/cmd/konnect.go @@ -7,14 +7,14 @@ import ( var konnectAlphaState = ` -WARNING: This command is currently in alpha state. This command -might have breaking changes in future releases.` +WARNING: This command was in alpha state and has been deprecated. +This command will be removed in a future release.` // newKonnectCmd represents the konnect command func newKonnectCmd() *cobra.Command { konnectCmd := &cobra.Command{ Use: "konnect", - Short: "Configuration tool for Konnect (in alpha)", + Short: "[deprecated] Configuration tool for Konnect", Long: `The konnect command prints subcommands that can be used to configure Konnect.` + konnectAlphaState, PersistentPreRun: func(cmd *cobra.Command, args []string) { diff --git a/cmd/reset.go b/cmd/reset.go deleted file mode 100644 index d0a68780e..000000000 --- a/cmd/reset.go +++ /dev/null @@ -1,144 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/kong/deck/state" - "github.com/kong/deck/utils" - "github.com/spf13/cobra" -) - -var ( - resetCmdForce bool - resetWorkspace string - resetAllWorkspaces bool - resetJSONOutput bool -) - -// newResetCmd represents the reset command -func newResetCmd() *cobra.Command { - resetCmd := &cobra.Command{ - Use: "reset", - Short: "Reset deletes all entities in Kong", - Long: `The reset command deletes all entities in Kong's database.string. - -Use this command with extreme care as it's equivalent to running -"kong migrations reset" on your Kong instance. - -By default, this command will ask for confirmation.`, - Args: validateNoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - - if resetAllWorkspaces && resetWorkspace != "" { - return fmt.Errorf("workspace cannot be specified with --all-workspace flag") - } - - if !resetCmdForce { - ok, err := utils.Confirm("This will delete all configuration from Kong's database." + - "\n> Are you sure? ") - if err != nil { - return err - } - if !ok { - return nil - } - } - - mode := getMode(nil) - if mode == modeKonnect { - _ = sendAnalytics("reset", "", mode) - return resetKonnectV2(ctx) - } - - rootClient, err := utils.GetKongClient(rootConfig) - if err != nil { - return err - } - - kongVersion, err := fetchKongVersion(ctx, rootConfig.ForWorkspace(resetWorkspace)) - if err != nil { - return fmt.Errorf("reading Kong version: %w", err) - } - parsedKongVersion, err := utils.ParseKongVersion(kongVersion) - if err != nil { - return fmt.Errorf("parsing Kong version: %w", err) - } - _ = sendAnalytics("reset", kongVersion, mode) - - if utils.Kong340Version.LTE(parsedKongVersion) { - dumpConfig.IsConsumerGroupScopedPluginSupported = true - } - - var workspaces []string - // Kong OSS or default workspace - if !resetAllWorkspaces && resetWorkspace == "" { - workspaces = append(workspaces, "") - } - - // Kong Enterprise - if resetAllWorkspaces { - workspaces, err = listWorkspaces(ctx, rootClient) - if err != nil { - return err - } - } - if resetWorkspace != "" { - exists, err := workspaceExists(ctx, rootConfig, resetWorkspace) - if err != nil { - return err - } - if !exists { - return fmt.Errorf("workspace '%v' does not exist in Kong", resetWorkspace) - } - - workspaces = append(workspaces, resetWorkspace) - } - - for _, workspace := range workspaces { - wsClient, err := utils.GetKongClient(rootConfig.ForWorkspace(workspace)) - if err != nil { - return err - } - currentState, err := fetchCurrentState(ctx, wsClient, dumpConfig) - if err != nil { - return err - } - targetState, err := state.NewKongState() - if err != nil { - return err - } - _, err = performDiff(ctx, currentState, targetState, false, 10, 0, wsClient, false, resetJSONOutput) - if err != nil { - return err - } - } - return nil - }, - } - - resetCmd.Flags().BoolVarP(&resetCmdForce, "force", "f", - false, "Skip interactive confirmation prompt before reset.") - resetCmd.Flags().BoolVar(&dumpConfig.SkipConsumers, "skip-consumers", - false, "do not reset consumers, consumer-groups or "+ - "any plugins associated with consumers.") - resetCmd.Flags().StringVarP(&resetWorkspace, "workspace", "w", - "", "reset configuration of a specific workspace"+ - "(Kong Enterprise only).") - resetCmd.Flags().BoolVar(&resetAllWorkspaces, "all-workspaces", - false, "reset configuration of all workspaces (Kong Enterprise only).") - resetCmd.Flags().BoolVar(&noMaskValues, "no-mask-deck-env-vars-value", - false, "do not mask DECK_ environment variable values at diff output.") - resetCmd.Flags().StringSliceVar(&dumpConfig.SelectorTags, - "select-tag", []string{}, - "only entities matching tags specified via this flag are deleted.\n"+ - "When this setting has multiple tag values, entities must match every tag.") - resetCmd.Flags().BoolVar(&dumpConfig.RBACResourcesOnly, "rbac-resources-only", - false, "reset only the RBAC resources (Kong Enterprise only).") - resetCmd.Flags().BoolVar(&dumpConfig.SkipCACerts, "skip-ca-certificates", - false, "do not reset CA certificates.") - resetCmd.Flags().BoolVar(&resetJSONOutput, "json-output", - false, "generate command execution report in a JSON format") - - return resetCmd -} diff --git a/cmd/root.go b/cmd/root.go index 6397c3031..49292c325 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -54,9 +54,30 @@ It can be used to export, import, or sync entities to Kong.`, } cobra.OnInitialize(initConfig) + // global flags rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "Config file (default is $HOME/.deck.yaml).") + rootCmd.PersistentFlags().Int("verbose", 0, + "Enable verbose logging levels\n"+ + "Sets the verbosity level of log output (higher is more verbose).") + viper.BindPFlag("verbose", + rootCmd.PersistentFlags().Lookup("verbose")) + + rootCmd.PersistentFlags().Bool("no-color", false, + "Disable colorized output") + viper.BindPFlag("no-color", + rootCmd.PersistentFlags().Lookup("no-color")) + + rootCmd.PersistentFlags().Bool("analytics", true, + "Share anonymized data to help improve decK.\n"+ + "Use `--analytics=false` to disable this.") + viper.BindPFlag("analytics", + rootCmd.PersistentFlags().Lookup("analytics")) + + // TODO: everything below are online flags to be moved to the "gateway" subcommand + // moving them now would break to top-level commands (sync, diff, etc) we still + // need for backward compatibility. rootCmd.PersistentFlags().String("kong-addr", defaultKongURL, "HTTP address of Kong's Admin API.\n"+ "This value can also be set using the environment variable DECK_KONG_ADDR\n"+ @@ -102,17 +123,6 @@ It can be used to export, import, or sync entities to Kong.`, viper.BindPFlag("ca-cert-file", rootCmd.PersistentFlags().Lookup("ca-cert-file")) - rootCmd.PersistentFlags().Int("verbose", 0, - "Enable verbose logging levels\n"+ - "Sets the verbosity level of log output (higher is more verbose).") - viper.BindPFlag("verbose", - rootCmd.PersistentFlags().Lookup("verbose")) - - rootCmd.PersistentFlags().Bool("no-color", false, - "Disable colorized output") - viper.BindPFlag("no-color", - rootCmd.PersistentFlags().Lookup("no-color")) - rootCmd.PersistentFlags().Bool("skip-workspace-crud", false, "Skip API calls related to Workspaces (Kong Enterprise only).") viper.BindPFlag("skip-workspace-crud", @@ -125,12 +135,6 @@ It can be used to export, import, or sync entities to Kong.`, viper.BindPFlag("kong-cookie-jar-path", rootCmd.PersistentFlags().Lookup("kong-cookie-jar-path")) - rootCmd.PersistentFlags().Bool("analytics", true, - "Share anonymized data to help improve decK.\n"+ - "Use `--analytics=false` to disable this.") - viper.BindPFlag("analytics", - rootCmd.PersistentFlags().Lookup("analytics")) - rootCmd.PersistentFlags().Int("timeout", 10, "Set a request timeout for the client to connect with Kong (in seconds).") viper.BindPFlag("timeout", @@ -214,18 +218,28 @@ It can be used to export, import, or sync entities to Kong.`, rootCmd.MarkFlagsMutuallyExclusive("konnect-runtime-group-name", "konnect-control-plane-name") - rootCmd.AddCommand(newSyncCmd()) rootCmd.AddCommand(newVersionCmd()) - rootCmd.AddCommand(newValidateCmd()) - rootCmd.AddCommand(newResetCmd()) - rootCmd.AddCommand(newPingCmd()) - rootCmd.AddCommand(newDumpCmd()) - rootCmd.AddCommand(newDiffCmd()) - rootCmd.AddCommand(newConvertCmd()) rootCmd.AddCommand(newCompletionCmd()) - rootCmd.AddCommand(newKonnectCmd()) + rootCmd.AddCommand(newSyncCmd(true)) // deprecated, to exist under the `kong` subcommand only + rootCmd.AddCommand(newValidateCmd(true, false)) // deprecated, to exist under both `kong` and `file` subcommands + rootCmd.AddCommand(newResetCmd(true)) // deprecated, to exist under the `kong` subcommand only + rootCmd.AddCommand(newPingCmd(true)) // deprecated, to exist under the `kong` subcommand only + rootCmd.AddCommand(newDumpCmd(true)) // deprecated, to exist under the `kong` subcommand only + rootCmd.AddCommand(newDiffCmd(true)) // deprecated, to exist under the `kong` subcommand only + rootCmd.AddCommand(newConvertCmd(true)) // deprecated, to exist under the `file` subcommand only + rootCmd.AddCommand(newKonnectCmd()) // deprecated, to be removed + { + kongCmd := newKongSubCmd() + rootCmd.AddCommand(kongCmd) + kongCmd.AddCommand(newSyncCmd(false)) + kongCmd.AddCommand(newValidateCmd(false, true)) // online validation + kongCmd.AddCommand(newResetCmd(false)) + kongCmd.AddCommand(newPingCmd(false)) + kongCmd.AddCommand(newDumpCmd(false)) + kongCmd.AddCommand(newDiffCmd(false)) + } { - fileCmd := newAddFileCmd() + fileCmd := newFileSubCmd() rootCmd.AddCommand(fileCmd) fileCmd.AddCommand(newAddPluginsCmd()) fileCmd.AddCommand(newAddTagsCmd()) @@ -235,6 +249,8 @@ It can be used to export, import, or sync entities to Kong.`, fileCmd.AddCommand(newPatchCmd()) fileCmd.AddCommand(newOpenapi2KongCmd()) fileCmd.AddCommand(newFileRenderCmd()) + fileCmd.AddCommand(newConvertCmd(false)) + fileCmd.AddCommand(newValidateCmd(false, false)) // file-based validation } return rootCmd } diff --git a/tests/integration/test_utils.go b/tests/integration/test_utils.go index 754c11d5b..ca09a5368 100644 --- a/tests/integration/test_utils.go +++ b/tests/integration/test_utils.go @@ -215,7 +215,7 @@ func testKongState(t *testing.T, client *kong.Client, isKonnect bool, func reset(t *testing.T, opts ...string) { deckCmd := cmd.NewRootCmd() - args := []string{"reset", "--force"} + args := []string{"gateway", "reset", "--force"} if len(opts) > 0 { args = append(args, opts...) } @@ -248,7 +248,7 @@ func setup(t *testing.T) { func sync(kongFile string, opts ...string) error { deckCmd := cmd.NewRootCmd() - args := []string{"sync", "-s", kongFile} + args := []string{"gateway", "sync", kongFile} if len(opts) > 0 { args = append(args, opts...) } @@ -258,7 +258,7 @@ func sync(kongFile string, opts ...string) error { func diff(kongFile string, opts ...string) (string, error) { deckCmd := cmd.NewRootCmd() - args := []string{"diff", "-s", kongFile} + args := []string{"gateway", "diff", kongFile} if len(opts) > 0 { args = append(args, opts...) } @@ -280,7 +280,7 @@ func diff(kongFile string, opts ...string) (string, error) { func dump(opts ...string) (string, error) { deckCmd := cmd.NewRootCmd() - args := []string{"dump"} + args := []string{"gateway", "dump", "-o=kong"} if len(opts) > 0 { args = append(args, opts...) } @@ -302,7 +302,7 @@ func dump(opts ...string) (string, error) { func ping(opts ...string) error { deckCmd := cmd.NewRootCmd() - args := []string{"ping"} + args := []string{"gateway", "ping"} if len(opts) > 0 { args = append(args, opts...) }