diff --git a/commands/cluster_command_launcher.go b/commands/cluster_command_launcher.go index bca77d3..a124fef 100644 --- a/commands/cluster_command_launcher.go +++ b/commands/cluster_command_launcher.go @@ -175,6 +175,8 @@ const ( scrutinizeSubCmd = "scrutinize" showRestorePointsSubCmd = "show_restore_points" installPkgSubCmd = "install_packages" + // hidden Cmds (for internal testing only) + promoteSandboxSubCmd = "promote_sandbox" ) // cmdGlobals holds global variables shared by multiple @@ -535,6 +537,8 @@ func constructCmds() []*cobra.Command { makeCmdManageConfig(), makeCmdReplication(), makeCmdCreateConnection(), + // hidden cmds (for internal testing only) + makeCmdPromoteSandbox(), } } diff --git a/commands/cmd_promote_sandbox.go b/commands/cmd_promote_sandbox.go new file mode 100644 index 0000000..4f723cc --- /dev/null +++ b/commands/cmd_promote_sandbox.go @@ -0,0 +1,130 @@ +/* + (c) Copyright [2023-2024] Open Text. + 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. + + DISCLAIMER: + The subcommand (promote_sandbox) within this file is intended solely for internal testing purposes. + It is not designed, intended, or authorized for use in production environments. The behavior of this + subcommand may change without prior notice and is not guaranteed to be maintained in future releases. + + Use of this function in any production code or reliance upon its behavior is strongly discouraged and + undertaken at your own risk. Open Text assumes no responsibility for any consequences arising from the + use of this function outside of its intended testing context. +*/ + +package commands + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/vertica/vcluster/vclusterops" + "github.com/vertica/vcluster/vclusterops/vlog" +) + +/* cmdPromoteSandbox + * + * Parses arguments to promote a sandbox to main cluster. + * This should not be used by VCluster CLI users. See the disclaimer above. + * + * Implements ClusterCommand interface + */ +type cmdPromoteSandbox struct { + promoteSandboxOpts *vclusterops.VPromoteSandboxToMainOptions + CmdBase +} + +func makeCmdPromoteSandbox() *cobra.Command { + newCmd := &cmdPromoteSandbox{} + opt := vclusterops.VPromoteSandboxToMainFactory() + newCmd.promoteSandboxOpts = &opt + + cmd := makeBasicCobraCmd( + newCmd, + promoteSandboxSubCmd, + "", + "", + []string{dbNameFlag, hostsFlag, ipv6Flag, eonModeFlag, configFlag, passwordFlag}, + ) + + // local flags + newCmd.setLocalFlags(cmd) + + // required flags + markFlagsRequired(cmd, sandboxFlag) + + // hide this subcommand + cmd.Hidden = true + + return cmd +} + +// setLocalFlags will set the local flags the command has +func (c *cmdPromoteSandbox) setLocalFlags(cmd *cobra.Command) { + cmd.Flags().StringVar( + &c.promoteSandboxOpts.SandboxName, + sandboxFlag, + "", + "The name of the sandbox", + ) +} + +func (c *cmdPromoteSandbox) Parse(inputArgv []string, logger vlog.Printer) error { + c.argv = inputArgv + logger.LogArgParse(&c.argv) + + // reset some options that are not included in user input + c.ResetUserInputOptions(&c.promoteSandboxOpts.DatabaseOptions) + + // promote_sandbox only works for an Eon db so we assume the user always runs this subcommand + // on an Eon db. When Eon mode cannot be found in config file, we set its value to true. + if !viper.IsSet(eonModeKey) { + c.promoteSandboxOpts.IsEon = true + } + + return c.validateParse(logger) +} + +func (c *cmdPromoteSandbox) validateParse(logger vlog.Printer) error { + logger.Info("Called validateParse()") + if !c.usePassword() { + err := c.getCertFilesFromCertPaths(&c.promoteSandboxOpts.DatabaseOptions) + if err != nil { + return err + } + } + + err := c.ValidateParseBaseOptions(&c.promoteSandboxOpts.DatabaseOptions) + if err != nil { + return err + } + return c.setDBPassword(&c.promoteSandboxOpts.DatabaseOptions) +} + +func (c *cmdPromoteSandbox) Run(vcc vclusterops.ClusterCommands) error { + vcc.LogInfo("Called method Run()") + + options := c.promoteSandboxOpts + + err := vcc.VPromoteSandboxToMain(options) + if err != nil { + vcc.LogError(err, "fail to promote sandbox to main", "sandbox", c.promoteSandboxOpts.SandboxName) + return err + } + vcc.DisplayInfo("Successfully promoted sandbox %q to main", c.promoteSandboxOpts.SandboxName) + return nil +} + +// SetDatabaseOptions will assign a vclusterops.DatabaseOptions instance to the one in CmdPromoteSandbox +func (c *cmdPromoteSandbox) SetDatabaseOptions(opt *vclusterops.DatabaseOptions) { + c.promoteSandboxOpts.DatabaseOptions = *opt +} diff --git a/go.mod b/go.mod index f94d3e6..50cc059 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( github.com/deckarep/golang-set/v2 v2.3.1 + github.com/fatih/color v1.14.1 github.com/go-logr/logr v1.2.4 github.com/go-logr/zapr v1.2.4 github.com/spf13/cobra v1.8.0 @@ -16,6 +17,7 @@ require ( go.uber.org/zap v1.25.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/sys v0.15.0 + golang.org/x/term v0.15.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.26.2 ) @@ -28,7 +30,6 @@ require ( github.com/aws/aws-sdk-go v1.49.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/fatih/color v1.14.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect @@ -71,7 +72,6 @@ require ( golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.15.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/api v0.153.0 // indirect diff --git a/vclusterops/cluster_op.go b/vclusterops/cluster_op.go index 7182a2d..f573366 100644 --- a/vclusterops/cluster_op.go +++ b/vclusterops/cluster_op.go @@ -227,7 +227,7 @@ func (op *opBase) setLogger(logger vlog.Printer) { func (op *opBase) parseAndCheckResponse(host, responseContent string, responseObj any) error { err := util.GetJSONLogErrors(responseContent, &responseObj, op.name, op.logger) if err != nil { - op.logger.Error(err, "fail to parse response on host, detail", "host", host) + op.logger.Error(err, "fail to parse response on host, detail", "host", host, "original responseContent", responseContent) return err } op.logger.Info("JSON response", "host", host, "responseObj", responseObj) @@ -248,6 +248,13 @@ func (op *opBase) parseAndCheckStringResponse(host, responseContent string) (str return responseStr, err } +func (op *opBase) parseAndCheckGenericJSONResponse(host, responseContent string) (nmaGenericJSONResponse, error) { + var genericResponse nmaGenericJSONResponse + err := op.parseAndCheckResponse(host, responseContent, &genericResponse) + + return genericResponse, err +} + func (op *opBase) setClusterHTTPRequestName() { op.clusterHTTPRequest.Name = op.name } diff --git a/vclusterops/helpers.go b/vclusterops/helpers.go index 0ff4851..b73550f 100644 --- a/vclusterops/helpers.go +++ b/vclusterops/helpers.go @@ -468,3 +468,9 @@ func (vcc *VClusterCommands) doReIP(options *DatabaseOptions, scName string, return nil } + +// An nmaGenericJSONResponse is the default response that is generated, +// the response value is of type "string" in JSON format. +type nmaGenericJSONResponse struct { + RespStr string +} diff --git a/vclusterops/nma_get_config_parameter_op.go b/vclusterops/nma_get_config_parameter_op.go index 8fecbdd..4ed8311 100644 --- a/vclusterops/nma_get_config_parameter_op.go +++ b/vclusterops/nma_get_config_parameter_op.go @@ -121,10 +121,11 @@ func (op *nmaGetConfigurationParameterOp) processResult(_ *opEngineExecContext) op.logResponse(host, result) if result.isPassing() { - err := op.parseAndCheckResponse(host, result.content, op.retrievedParamValue) + genericResponse, err := op.parseAndCheckGenericJSONResponse(host, result.content) if err != nil { allErrs = errors.Join(allErrs, err) } + *op.retrievedParamValue = genericResponse.RespStr } else { allErrs = errors.Join(allErrs, result.err) } diff --git a/vclusterops/promote_sandbox_to_main.go b/vclusterops/promote_sandbox_to_main.go index 5cc046f..cf76c6e 100644 --- a/vclusterops/promote_sandbox_to_main.go +++ b/vclusterops/promote_sandbox_to_main.go @@ -40,6 +40,9 @@ func (opt *VPromoteSandboxToMainOptions) validateEonOptions(_ vlog.Printer) erro if !opt.IsEon { return fmt.Errorf("promote a sandbox to main is only supported in Eon mode") } + if opt.SandboxName == "" { + return fmt.Errorf("must specify a sandbox name") + } return nil } @@ -49,12 +52,12 @@ func (opt *VPromoteSandboxToMainOptions) validateParseOptions(logger vlog.Printe return err } - // need to provide a password or certs in source database - if opt.Password == nil && (opt.Cert == "" || opt.Key == "") { - return fmt.Errorf("must provide a password or a key-certificate pair") + err = opt.validateBaseOptions(PromoteSandboxToMainCmd, logger) + if err != nil { + return err } - return opt.validateBaseOptions(PromoteSandboxToMainCmd, logger) + return opt.validateAuthOptions("", logger) } // analyzeOptions will modify some options based on what is chosen diff --git a/vclusterops/promote_sandbox_to_main_test.go b/vclusterops/promote_sandbox_to_main_test.go index ce54335..d3a2f19 100644 --- a/vclusterops/promote_sandbox_to_main_test.go +++ b/vclusterops/promote_sandbox_to_main_test.go @@ -35,6 +35,10 @@ func TestPromoteSandboxToMainOptions_validateParseOptions(t *testing.T) { opt.Password = &testPassword err := opt.validateParseOptions(logger) + assert.ErrorContains(t, err, "must specify a sandbox name") + + opt.SandboxName = "sand1" + err = opt.validateParseOptions(logger) assert.NoError(t, err) opt.UserName = ""