diff --git a/commands/cluster_command.go b/commands/cluster_command.go deleted file mode 100644 index 05f78fb..0000000 --- a/commands/cluster_command.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - (c) Copyright [2023] 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. -*/ - -package commands - -import ( - "github.com/vertica/vcluster/vclusterops" - "github.com/vertica/vcluster/vclusterops/vlog" -) - -type ClusterCommand interface { - CommandType() string - Parse(argv []string, logger vlog.Printer) error - - /* TODO: Analyze information about the state of - * the cluster. The information could be - * cached in a config file or constructed through - * cluster discovery. - */ - Analyze(logger vlog.Printer) error - Run(vcc vclusterops.ClusterCommands) error - PrintUsage(string) -} diff --git a/commands/cluster_command_launcher.go b/commands/cluster_command_launcher.go index 4403337..a506baf 100644 --- a/commands/cluster_command_launcher.go +++ b/commands/cluster_command_launcher.go @@ -28,41 +28,13 @@ import ( "github.com/vertica/vcluster/vclusterops/vlog" ) -/* ClusterCommandLauncher - * A class that encapsulates the data and steps - * of a CLI program for run cluster operations. Similar - * to commandLineCtrl.py from Admintools. - * - * Main programs instaniate ClusterCommandLauncher with - * makeClusterCommandLauncher. The main program calls the Run() method - * with command line arguments. Run() returns errors. - * - */ -type ClusterCommandLauncher struct { - argv []string - commands map[string]ClusterCommand -} - -/* minArgs - the minimum number of arguments - * that the program expects. - * - * The command lines that we expect to parse - * look like: - * vcluster [options] [args] - * - * Examples: - * vcluster help - * vcluster create_db --db-name db1 - * - * - */ -const minArgs = 2 -const helpString = "help" const defaultLogPath = "/opt/vertica/log/vcluster.log" const defaultExecutablePath = "/opt/vertica/bin/vcluster" const CLIVersion = "1.2.0" const vclusterLogPathEnv = "VCLUSTER_LOG_PATH" +const vclusterKeyPathEnv = "VCLUSTER_KEY_PATH" +const vclusterCertPathEnv = "VCLUSTER_CERT_PATH" // *Flag is for the flag name, *Key is for viper key name // They are bound together @@ -83,8 +55,14 @@ const ( ipv6Key = "ipv6" eonModeFlag = "eon-mode" eonModeKey = "eonMode" + configParamFlag = "config-param" + configParamKey = "configParam" logPathFlag = "log-path" logPathKey = "logPath" + keyPathFlag = "key-path" + keyPathKey = "keyPath" + certPathFlag = "cert-path" + certPathKey = "certPath" passwordFlag = "password" passwordKey = "password" passwordFileFlag = "password-file" @@ -109,7 +87,10 @@ var flagKeyMap = map[string]string{ communalStorageLocationFlag: communalStorageLocationKey, ipv6Flag: ipv6Key, eonModeFlag: eonModeKey, + configParamFlag: configParamKey, logPathFlag: logPathKey, + keyPathFlag: keyPathKey, + certPathFlag: certPathKey, passwordFlag: passwordKey, passwordFileFlag: passwordFileKey, readPasswordFromPromptFlag: readPasswordFromPromptKey, @@ -119,16 +100,33 @@ var flagKeyMap = map[string]string{ } const ( - createDBSubCmd = "create_db" - stopDBSubCmd = "stop_db" - reviveDBSubCmd = "revive_db" + createDBSubCmd = "create_db" + stopDBSubCmd = "stop_db" + startDBSubCmd = "start_db" + dropDBSubCmd = "drop_db" + reviveDBSubCmd = "revive_db" + addSCSubCmd = "db_add_subcluster" + removeSCSubCmd = "db_remove_subcluster" + addNodeSubCmd = "db_add_node" + removeNodeSubCmd = "db_remove_node" + restartNodeSubCmd = "restart_node" + reIPSubCmd = "re_ip" + listAllNodesSubCmd = "list_allnodes" + sandboxSubCmd = "sandbox_subcluster" + unsandboxSubCmd = "unsandbox_subcluster" + scrutinizeSubCmd = "scrutinize" + showRestorePointsSubCmd = "show_restore_points" + installPkgSubCmd = "install_packages" + configSubCmd = "config" ) // cmdGlobals holds global variables shared by multiple // commands type cmdGlobals struct { - verbose bool - file *os.File + verbose bool + file *os.File + keyPath string + certPath string } var ( @@ -216,8 +214,14 @@ func setDBOptionsUsingViper(flag string) error { dbOptions.IPv6 = viper.GetBool(ipv6Key) case eonModeFlag: dbOptions.IsEon = viper.GetBool(eonModeKey) + case configParamFlag: + dbOptions.ConfigurationParameters = viper.GetStringMapString(configParamKey) case logPathFlag: *dbOptions.LogPath = viper.GetString(logPathKey) + case keyPathFlag: + globals.keyPath = viper.GetString(keyPathKey) + case certPathFlag: + globals.certPath = viper.GetString(certPathKey) case verboseFlag: globals.verbose = viper.GetBool(verboseKey) default: @@ -235,7 +239,10 @@ func configViper(cmd *cobra.Command, flagsInConfig []string) error { // log-path is a flag that all the subcommands need flagsInConfig = append(flagsInConfig, logPathFlag) - + // cert-path and key-path are not available for config subcmd + if cmd.CalledAs() != configSubCmd { + flagsInConfig = append(flagsInConfig, certPathFlag, keyPathFlag) + } // bind viper keys to cobra flags for _, flag := range flagsInConfig { if _, ok := flagKeyMap[flag]; !ok { @@ -252,6 +259,14 @@ func configViper(cmd *cobra.Command, flagsInConfig []string) error { if err != nil { return fmt.Errorf("fail to bind viper key %q to environment variable %q: %w", logPathKey, vclusterLogPathEnv, err) } + err = viper.BindEnv(keyPathKey, vclusterKeyPathEnv) + if err != nil { + return fmt.Errorf("fail to bind viper key %q to environment variable %q: %w", keyPathKey, vclusterKeyPathEnv, err) + } + err = viper.BindEnv(certPathKey, vclusterCertPathEnv) + if err != nil { + return fmt.Errorf("fail to bind viper key %q to environment variable %q: %w", certPathKey, vclusterCertPathEnv, err) + } // load db options from config file to viper // note: config file is not available for create_db and revive_db @@ -480,148 +495,6 @@ func setLogPathImpl(opsys operatingSystem) string { return filepath.Join(path, "vcluster.log") } -// remove this function in VER-92222 -/* ClusterCommandLauncherFactory() - * Returns a new instance of a ClusterCommandLauncher - * with some reasonable defaults. - */ -func MakeClusterCommandLauncher() (ClusterCommandLauncher, vclusterops.VClusterCommands) { - // setup logs for command launcher initialization - userCommandString := os.Args[1] - logger := vlog.Printer{ForCli: true} - oldLogPath := parseLogPathArg(os.Args, defaultLogPath) - logger.SetupOrDie(oldLogPath) - vcc := vclusterops.VClusterCommands{ - VClusterCommandsLogger: vclusterops.VClusterCommandsLogger{ - Log: logger.WithName(userCommandString), - }, - } - vcc.LogInfo("New vcluster command initialization") - newLauncher := ClusterCommandLauncher{} - allCommands := constructOldCmds(vcc.GetLog()) - - newLauncher.commands = map[string]ClusterCommand{} - for _, c := range allCommands { - _, existsInMap := newLauncher.commands[c.CommandType()] - if existsInMap { - // shout loud if there's a programmer error - vcc.PrintError("Programmer Error: tried to add command %s to the commands index twice. Check cluster_command_launcher.go", - c.CommandType()) - os.Exit(1) - } - newLauncher.commands[c.CommandType()] = c - } - - return newLauncher, vcc -} - -// remove this function in VER-92222 -// constructCmds returns a list of commands that will be executed -// by the cluster command launcher. -func constructOldCmds(_ vlog.Printer) []ClusterCommand { - return []ClusterCommand{ - // db-scope cmds - // sc-scope cmds - // node-scope cmds - // others - makeCmdHelp(), - } -} - -// remove this function in VER-92222 -/* Run is expected be called by a CLI program - * Run executes the following algorithm: - * + Identifies the appropriate sub-command - * + Asks the sub-command to parse the command line - * + Loads the configuration file using the vconfig library. (not implemented) - * + Asks the sub-command to analyze the command line given the - * context from the config file. - * + Calls Run() for the sub-command - * + Returns any errors to the caller after writing the error to the log - */ -func (c ClusterCommandLauncher) Run(inputArgv []string, vcc vclusterops.VClusterCommands) error { - userCommandString := os.Args[1] - c.argv = inputArgv - minArgsError := checkMinimumInput(c.argv) - - if minArgsError != nil { - vcc.LogError(minArgsError, "fail to check minimum argument") - return minArgsError - } - - subCommand, idError := identifySubcommand(c.commands, userCommandString, vcc.GetLog()) - - if idError != nil { - vcc.LogError(idError, "fail to recognize command") - return idError - } - - parseError := subCommand.Parse(inputArgv[2:], vcc.GetLog()) - if parseError != nil { - vcc.LogError(parseError, "fail to parse command") - return parseError - } - - /* Special case: help - * If the user asked for help, handle that now - * and then exit. Avoid doing anything else that - * might fail and prevent access to help. - */ - if subCommand.CommandType() == helpString { - subCommand.PrintUsage(subCommand.CommandType()) - return nil - } - - /* TODO: this is where we would read a - * configuration file. Not currently implemented. - */ - analyzeError := subCommand.Analyze(vcc.GetLog()) - - if analyzeError != nil { - vcc.LogError(analyzeError, "fail to analyze command") - return analyzeError - } - - runError := subCommand.Run(vcc) - if runError != nil { - vcc.LogError(runError, "fail to run command") - } - return runError -} - -// remove this function in VER-92222 -func identifySubcommand(commands map[string]ClusterCommand, userCommandString string, - logger vlog.Printer) (ClusterCommand, error) { - command, ok := commands[userCommandString] - if !ok { - return nil, fmt.Errorf("unrecognized command '%s'", - userCommandString) - } - - logger.Log.Info("Recognized command", "cmd", userCommandString) - return command, nil -} - -// remove this function in VER-92222 -func checkMinimumInput(inputArgv []string) error { - if len(inputArgv) >= minArgs { - return nil - } - return fmt.Errorf("expected at least %d arguments but found only %d", - minArgs, - len(inputArgv)) -} - -// remove this function in VER-92222 -func parseLogPathArg(argInput []string, defaultPath string) (logPath string) { - for idx, arg := range argInput { - if arg == "--log-path" { - return argInput[idx+1] - } - } - return defaultPath -} - func closeFile(f *os.File) { if f != nil && f != os.Stdout { if err := f.Close(); err != nil { diff --git a/commands/cmd_add_node.go b/commands/cmd_add_node.go index 16fe1db..12da041 100644 --- a/commands/cmd_add_node.go +++ b/commands/cmd_add_node.go @@ -32,8 +32,6 @@ import ( */ type CmdAddNode struct { addNodeOptions *vclusterops.VAddNodeOptions - // Comma-separated list of hosts to add - newHostListStr string // Comma-separated list of node names, which exist in the cluster nodeNameListStr string @@ -49,7 +47,7 @@ func makeCmdAddNode() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "db_add_node", + addNodeSubCmd, "Add host(s) to an existing database", `This subcommand adds one or more hosts to an existing database. @@ -90,10 +88,10 @@ Examples: // setLocalFlags will set the local flags the command has func (c *CmdAddNode) setLocalFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - &c.newHostListStr, + cmd.Flags().StringSliceVar( + &c.addNodeOptions.NewHosts, "add", - "", + []string{}, "Comma-separated list of host(s) to add to the database", ) cmd.Flags().BoolVar( @@ -129,10 +127,6 @@ func (c *CmdAddNode) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdAddNode) CommandType() string { - return "db_add_node" -} - func (c *CmdAddNode) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) @@ -147,7 +141,12 @@ func (c *CmdAddNode) Parse(inputArgv []string, logger vlog.Printer) error { func (c *CmdAddNode) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") - err := c.parseNewHostList() + err := c.getCertFilesFromCertPaths(&c.addNodeOptions.DatabaseOptions) + if err != nil { + return err + } + + err = c.parseNewHostList() if err != nil { return err } @@ -164,15 +163,16 @@ func (c *CmdAddNode) validateParse(logger vlog.Printer) error { return c.setDBPassword(&c.addNodeOptions.DatabaseOptions) } -// ParseNewHostList converts the string list of hosts, to add, into a slice of strings. -// The hosts should be separated by comma, and will be converted to lower case. +// parseNewHostList trims and lowercases the hosts in --add func (c *CmdAddNode) parseNewHostList() error { - inputHostList, err := util.SplitHosts(c.newHostListStr) - if err != nil { - return fmt.Errorf("must specify at least one host to add: %w", err) + if len(c.addNodeOptions.NewHosts) > 0 { + err := util.ParseHostList(&c.addNodeOptions.NewHosts) + if err != nil { + // the err from util.ParseHostList will be "must specify a host or host list" + // we overwrite the error here to provide more details + return fmt.Errorf("must specify at least one host to add") + } } - - c.addNodeOptions.NewHosts = inputHostList return nil } @@ -211,7 +211,7 @@ func (c *CmdAddNode) Run(vcc vclusterops.ClusterCommands) error { if err != nil { vcc.PrintWarning("fail to write config file, details: %s", err) } - vcc.PrintInfo("Added nodes %s to database %s", c.newHostListStr, *options.DBName) + vcc.PrintInfo("Added nodes %v to database %s", c.addNodeOptions.NewHosts, *options.DBName) return nil } diff --git a/commands/cmd_add_subcluster.go b/commands/cmd_add_subcluster.go index 7a976d1..3b89a8f 100644 --- a/commands/cmd_add_subcluster.go +++ b/commands/cmd_add_subcluster.go @@ -44,7 +44,7 @@ func makeCmdAddSubcluster() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "db_add_subcluster", + addSCSubCmd, "Add a subcluster", `This subcommand adds a new subcluster to an existing Eon Mode database. @@ -120,10 +120,6 @@ func (c *CmdAddSubcluster) setHiddenFlags(cmd *cobra.Command) { hideLocalFlags(cmd, []string{"sc-hosts", "like"}) } -func (c *CmdAddSubcluster) CommandType() string { - return "db_add_subcluster" -} - func (c *CmdAddSubcluster) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) @@ -133,7 +129,12 @@ func (c *CmdAddSubcluster) Parse(inputArgv []string, logger vlog.Printer) error // all validations of the arguments should go in here func (c *CmdAddSubcluster) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") - err := c.ValidateParseBaseOptions(&c.addSubclusterOptions.DatabaseOptions) + err := c.getCertFilesFromCertPaths(&c.addSubclusterOptions.DatabaseOptions) + if err != nil { + return err + } + + err = c.ValidateParseBaseOptions(&c.addSubclusterOptions.DatabaseOptions) if err != nil { return err } diff --git a/commands/cmd_base.go b/commands/cmd_base.go index e11f25d..d455664 100644 --- a/commands/cmd_base.go +++ b/commands/cmd_base.go @@ -16,7 +16,6 @@ package commands import ( - "flag" "fmt" "os" "strings" @@ -37,11 +36,9 @@ const ( * Basic/common fields of vcluster commands */ type CmdBase struct { - argv []string - parser *pflag.FlagSet - oldParser *flag.FlagSet // remove this variable in VER-92222 + argv []string + parser *pflag.FlagSet - hostListStr *string // raw string from user input, need further processing // remove below two fields in VER-92369 isEon *bool // need further processing to see if the user inputted this flag or not ipv6 *bool // need further processing to see if the user inputted this flag or not @@ -53,47 +50,6 @@ type CmdBase struct { readPasswordFromPrompt bool } -// print usage of a command -func (c *CmdBase) PrintUsage(commandType string) { - fmt.Fprintf(os.Stderr, - "Please refer the usage of \"vcluster %s\" using \"vcluster %s --help\"\n", - commandType, commandType) -} - -// parse argv -func (c *CmdBase) ParseArgv() error { - parserError := c.oldParser.Parse(c.argv) - if parserError != nil { - return parserError - } - - return nil -} - -// validate and parse argv -func (c *CmdBase) ValidateParseArgv(commandType string, logger vlog.Printer) error { - logger.LogArgParse(&c.argv) - return c.ValidateParseArgvHelper(commandType) -} - -// validate and parse masked argv -// Some database actions, such as createDB and reviveDB, need to mask sensitive parameters in the log -func (c *CmdBase) ValidateParseMaskedArgv(commandType string, logger vlog.Printer) error { - logger.LogMaskedArgParse(c.argv) - return c.ValidateParseArgvHelper(commandType) -} - -func (c *CmdBase) ValidateParseArgvHelper(commandType string) error { - if c.oldParser == nil { - return fmt.Errorf("unexpected nil - the parser was nil") - } - if len(c.argv) == 0 { - c.PrintUsage(commandType) - return fmt.Errorf("zero args found, at least one argument expected") - } - return c.ParseArgv() -} - // ValidateParseBaseOptions will validate and parse the required base options in each command func (c *CmdBase) ValidateParseBaseOptions(opt *vclusterops.DatabaseOptions) error { // parse raw hosts @@ -123,7 +79,7 @@ func (c *CmdBase) SetParser(parser *pflag.FlagSet) { func (c *CmdBase) SetIPv6(cmd *cobra.Command) { cmd.Flags().BoolVar( c.ipv6, - "ipv6", + ipv6Flag, false, "Whether the hosts are using IPv6 addresses", ) @@ -152,6 +108,25 @@ func (c *CmdBase) setCommonFlags(cmd *cobra.Command, flags []string) { false, "Show the details of VCluster run in the console", ) + // keyPath and certPath are flags that all subcommands require, except for the config subcommand + if cmd.Name() != configSubCmd { + cmd.Flags().StringVar( + &globals.keyPath, + keyPathFlag, + "", + "Path to the key file", + ) + markFlagsFileName(cmd, map[string][]string{keyPathFlag: {"key"}}) + + cmd.Flags().StringVar( + &globals.certPath, + certPathFlag, + "", + "Path to the cert file", + ) + markFlagsFileName(cmd, map[string][]string{certPathFlag: {"pem", "crt"}}) + cmd.MarkFlagsRequiredTogether(keyPathFlag, certPathFlag) + } if util.StringInArray(outputFileFlag, flags) { cmd.Flags().StringVarP( &c.output, @@ -236,6 +211,13 @@ func setConfigFlags(cmd *cobra.Command, flags []string) { util.GetEonFlagMsg("indicate if the database is an Eon db."+ " Use it when you do not trust "+vclusterops.ConfigFileName)) } + if util.StringInArray(configParamFlag, flags) { + cmd.Flags().StringToStringVar( + &dbOptions.ConfigurationParameters, + configParamFlag, + map[string]string{}, + "Comma-separated list of NAME=VALUE pairs of existing configuration parameters") + } } // setPasswordFlags sets all the password flags @@ -267,7 +249,7 @@ func (c *CmdBase) setPasswordFlags(cmd *cobra.Command) { // ResetUserInputOptions reset password option to nil in each command // if it is not provided in cli func (c *CmdBase) ResetUserInputOptions(opt *vclusterops.DatabaseOptions) { - if !c.parser.Changed("password") { + if !c.parser.Changed(passwordFlag) { opt.Password = nil } } @@ -276,7 +258,7 @@ func (c *CmdBase) ResetUserInputOptions(opt *vclusterops.DatabaseOptions) { // OldResetUserInputOptions reset password and ipv6 options to nil in each command // if they are not provided in cli func (c *CmdBase) OldResetUserInputOptions() { - if !c.parser.Changed("ipv6") { + if !c.parser.Changed(ipv6Flag) { c.ipv6 = nil } } @@ -330,38 +312,6 @@ func (c *CmdBase) setDBPassword(opt *vclusterops.DatabaseOptions) error { return nil } -// remove this function in VER-92222 -// ValidateParseBaseOptions will validate and parse the required base options in each command -func (c *CmdBase) OldValidateParseBaseOptions(opt *vclusterops.DatabaseOptions) error { - // parse raw host str input into a []string - err := c.parseHostList(opt) - if err != nil { - return err - } - // parse IsEon - opt.OldIsEon.FromBoolPointer(c.isEon) - // parse Ipv6 - opt.OldIpv6.FromBoolPointer(c.ipv6) - - return nil -} - -// remove this function in VER-92222 -// convert a host string into a list of hosts, -// save the list into options.RawHosts; -// the hosts should be separated by comma, and will be converted to lower case -func (c *CmdBase) parseHostList(options *vclusterops.DatabaseOptions) error { - if *c.hostListStr != "" { - inputHostList, err := util.SplitHosts(*c.hostListStr) - if err != nil { - return err - } - options.RawHosts = inputHostList - } - - return nil -} - // usePassword returns true if at least one of the password // flags is passed in the cli func (c *CmdBase) usePassword() bool { @@ -394,3 +344,22 @@ func (c *CmdBase) initCmdOutputFile() (*os.File, error) { } return os.OpenFile(c.output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outputFilePerm) } + +// getCertFilesFromPaths will update cert and key file from cert path options +func (c *CmdBase) getCertFilesFromCertPaths(opt *vclusterops.DatabaseOptions) error { + if globals.certPath != "" { + certData, err := os.ReadFile(globals.certPath) + if err != nil { + return fmt.Errorf("failed to read certificate file, details %w", err) + } + opt.Cert = string(certData) + } + if globals.keyPath != "" { + keyData, err := os.ReadFile(globals.keyPath) + if err != nil { + return fmt.Errorf("failed to read private key file, details %w", err) + } + opt.Key = string(keyData) + } + return nil +} diff --git a/commands/cmd_config.go b/commands/cmd_config.go index 4040345..9c36841 100644 --- a/commands/cmd_config.go +++ b/commands/cmd_config.go @@ -41,7 +41,7 @@ func makeCmdConfig() *cobra.Command { newCmd := &CmdConfig{} cmd := makeBasicCobraCmd( newCmd, - "config", + configSubCmd, "Show the content of the config file", `This subcommand is used to print the content of the config file. @@ -52,7 +52,7 @@ Examples: # show the contents of the config file at /tmp/vertica_cluster.yaml vcluster config --show --config /tmp/vertica_cluster.yaml `, - []string{"config"}, + []string{configFlag}, ) // local flags @@ -73,10 +73,6 @@ func (c *CmdConfig) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdConfig) CommandType() string { - return "config" -} - func (c *CmdConfig) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogArgParse(&c.argv) diff --git a/commands/cmd_create_db.go b/commands/cmd_create_db.go index c22649a..a7e6ccc 100644 --- a/commands/cmd_create_db.go +++ b/commands/cmd_create_db.go @@ -31,15 +31,12 @@ import ( */ type CmdCreateDB struct { - createDBOptions *vclusterops.VCreateDatabaseOptions - configParamListStr *string // raw input from user, need further processing - + createDBOptions *vclusterops.VCreateDatabaseOptions CmdBase } func makeCmdCreateDB() *cobra.Command { newCmd := &CmdCreateDB{} - newCmd.configParamListStr = new(string) opt := vclusterops.VCreateDatabaseOptionsFactory() newCmd.createDBOptions = &opt @@ -68,7 +65,7 @@ Examples: --data-path --config-pram --config `, []string{dbNameFlag, hostsFlag, catalogPathFlag, dataPathFlag, depotPathFlag, - communalStorageLocationFlag, passwordFlag, configFlag, ipv6Flag}, + communalStorageLocationFlag, passwordFlag, configFlag, ipv6Flag, configParamFlag}, ) // local flags @@ -123,12 +120,6 @@ func (c *CmdCreateDB) setLocalFlags(cmd *cobra.Command) { false, util.GetEonFlagMsg("Read AWS credentials from environment variables"), ) - cmd.Flags().StringVar( - c.configParamListStr, - "config-param", - "", - "Comma-separated list of NAME=VALUE pairs for setting database configuration parameters", - ) cmd.Flags().BoolVar( c.createDBOptions.P2p, "point-to-point", @@ -224,14 +215,11 @@ func (c *CmdCreateDB) validateParse(logger vlog.Printer) error { return err } - // check the format of config param string, and parse it into configParams - configParams, err := util.ParseConfigParams(*c.configParamListStr) + err = c.getCertFilesFromCertPaths(&c.createDBOptions.DatabaseOptions) if err != nil { return err } - if configParams != nil { - c.createDBOptions.ConfigurationParameters = configParams - } + return c.setDBPassword(&c.createDBOptions.DatabaseOptions) } diff --git a/commands/cmd_drop_db.go b/commands/cmd_drop_db.go index 46aa2d6..1175eea 100644 --- a/commands/cmd_drop_db.go +++ b/commands/cmd_drop_db.go @@ -41,7 +41,7 @@ func makeCmdDropDB() *cobra.Command { // VER-92345 update the long description about the hosts option cmd := OldMakeBasicCobraCmd( newCmd, - "drop_db", + dropDBSubCmd, "Drop a database", `This subcommand drops a stopped database. @@ -78,10 +78,6 @@ func (c *CmdDropDB) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdDropDB) CommandType() string { - return "drop_db" -} - func (c *CmdDropDB) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogArgParse(&c.argv) @@ -89,7 +85,7 @@ func (c *CmdDropDB) Parse(inputArgv []string, logger vlog.Printer) error { // for some options, we do not want to use their default values, // if they are not provided in cli, // reset the value of those options to nil - if !c.parser.Changed("ipv6") { + if !c.parser.Changed(ipv6Flag) { c.CmdBase.ipv6 = nil } @@ -98,6 +94,10 @@ func (c *CmdDropDB) Parse(inputArgv []string, logger vlog.Printer) error { func (c *CmdDropDB) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") + err := c.getCertFilesFromCertPaths(&c.dropDBOptions.DatabaseOptions) + if err != nil { + return err + } return c.ValidateParseBaseOptions(&c.dropDBOptions.DatabaseOptions) } diff --git a/commands/cmd_help.go b/commands/cmd_help.go deleted file mode 100644 index a7b1675..0000000 --- a/commands/cmd_help.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - (c) Copyright [2023] 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. -*/ - -package commands - -import ( - "flag" - "fmt" - - "github.com/vertica/vcluster/vclusterops" - "github.com/vertica/vcluster/vclusterops/vlog" -) - -/* CmdHelp - * - * A command providing top-level help on - * various topics. PrintUsage() will print - * the requested help. - * - * Implements ClusterCommand interface - */ -type CmdHelp struct { - topic *string - - CmdBase -} - -func makeCmdHelp() *CmdHelp { - newCmd := &CmdHelp{} - newCmd.oldParser = flag.NewFlagSet("help", flag.ExitOnError) - newCmd.topic = newCmd.oldParser.String("topic", "", "The topic for more help") - return newCmd -} - -func (c *CmdHelp) CommandType() string { - return "help" -} - -func (c *CmdHelp) Parse(inputArgv []string, logger vlog.Printer) error { - logger.LogArgParse(&inputArgv) - - if c.oldParser == nil { - return fmt.Errorf("unexpected nil - the parser was nil") - } - - c.argv = inputArgv - err := c.ParseArgv() - if err != nil { - return err - } - - return c.validateParse(logger) -} - -func (c *CmdHelp) validateParse(logger vlog.Printer) error { - logger.Info("Called validateParse()") - return nil -} - -func (c *CmdHelp) Analyze(_ vlog.Printer) error { - return nil -} - -func (c *CmdHelp) Run(_ vclusterops.ClusterCommands) error { - return nil -} diff --git a/commands/cmd_install_packages.go b/commands/cmd_install_packages.go index ee4e757..3c9a94e 100644 --- a/commands/cmd_install_packages.go +++ b/commands/cmd_install_packages.go @@ -45,7 +45,7 @@ func makeCmdInstallPackages() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "install_packages", + installPkgSubCmd, "Install default package(s) in database", `This subcommand installs default packages in the database. @@ -81,10 +81,6 @@ func (c *CmdInstallPackages) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdInstallPackages) CommandType() string { - return "install_packages" -} - func (c *CmdInstallPackages) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) @@ -99,7 +95,12 @@ func (c *CmdInstallPackages) Parse(inputArgv []string, logger vlog.Printer) erro // all validations of the arguments should go in here func (c *CmdInstallPackages) validateParse() error { - err := c.ValidateParseBaseOptions(&c.installPkgOpts.DatabaseOptions) + err := c.getCertFilesFromCertPaths(&c.installPkgOpts.DatabaseOptions) + if err != nil { + return err + } + + err = c.ValidateParseBaseOptions(&c.installPkgOpts.DatabaseOptions) if err != nil { return err } diff --git a/commands/cmd_list_all_nodes.go b/commands/cmd_list_all_nodes.go index a71ff52..6d176bb 100644 --- a/commands/cmd_list_all_nodes.go +++ b/commands/cmd_list_all_nodes.go @@ -43,7 +43,7 @@ func makeListAllNodes() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "list_allnodes", + listAllNodesSubCmd, "List all nodes in the database", `This subcommand queries the status of the nodes in the consensus and prints whether they are currently up or down. @@ -64,10 +64,6 @@ Examples: return cmd } -func (c *CmdListAllNodes) CommandType() string { - return "list_allnodes" -} - func (c *CmdListAllNodes) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogArgParse(&c.argv) @@ -81,8 +77,13 @@ func (c *CmdListAllNodes) Parse(inputArgv []string, logger vlog.Printer) error { } func (c *CmdListAllNodes) validateParse(logger vlog.Printer) error { - logger.Info("Called validateParse()", "command", c.CommandType()) - err := c.ValidateParseBaseOptions(&c.fetchNodeStateOptions.DatabaseOptions) + logger.Info("Called validateParse()", "command", listAllNodesSubCmd) + err := c.getCertFilesFromCertPaths(&c.fetchNodeStateOptions.DatabaseOptions) + if err != nil { + return err + } + + err = c.ValidateParseBaseOptions(&c.fetchNodeStateOptions.DatabaseOptions) if err != nil { return err } diff --git a/commands/cmd_re_ip.go b/commands/cmd_re_ip.go index 8bff8d1..2bdc387 100644 --- a/commands/cmd_re_ip.go +++ b/commands/cmd_re_ip.go @@ -42,7 +42,7 @@ func makeCmdReIP() *cobra.Command { // as we need a better example with config file cmd := OldMakeBasicCobraCmd( newCmd, - "re_ip", + reIPSubCmd, "Re-ip database nodes", `This command alters the IP addresses of database nodes in the catalog. However, the database must be offline when running this command. If an IP change @@ -85,10 +85,6 @@ func (c *CmdReIP) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdReIP) CommandType() string { - return "re_ip" -} - func (c *CmdReIP) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogArgParse(&c.argv) @@ -104,6 +100,10 @@ func (c *CmdReIP) validateParse(logger vlog.Printer) error { return err } + err = c.getCertFilesFromCertPaths(&c.reIPOptions.DatabaseOptions) + if err != nil { + return err + } return c.reIPOptions.ReadReIPFile(c.reIPFilePath) } diff --git a/commands/cmd_remove_node.go b/commands/cmd_remove_node.go index 7de95d3..2542bf6 100644 --- a/commands/cmd_remove_node.go +++ b/commands/cmd_remove_node.go @@ -16,8 +16,11 @@ package commands import ( + "fmt" + "github.com/spf13/cobra" "github.com/vertica/vcluster/vclusterops" + "github.com/vertica/vcluster/vclusterops/util" "github.com/vertica/vcluster/vclusterops/vlog" ) @@ -27,8 +30,6 @@ import ( */ type CmdRemoveNode struct { removeNodeOptions *vclusterops.VRemoveNodeOptions - // Comma-separated list of hosts to remove - hostToRemoveListStr string CmdBase } @@ -42,7 +43,7 @@ func makeCmdRemoveNode() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "db_remove_node", + removeNodeSubCmd, "Remove host(s) from an existing database", `This subcommand removes one or more nodes from an existing database. @@ -77,10 +78,10 @@ Examples: // setLocalFlags will set the local flags the command has func (c *CmdRemoveNode) setLocalFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - &c.hostToRemoveListStr, + cmd.Flags().StringSliceVar( + &c.removeNodeOptions.HostsToRemove, "remove", - "", + []string{}, "Comma-separated list of host(s) to remove from the database", ) cmd.Flags().BoolVar( @@ -91,10 +92,6 @@ func (c *CmdRemoveNode) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdRemoveNode) CommandType() string { - return "db_remove_node" -} - func (c *CmdRemoveNode) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) @@ -109,10 +106,16 @@ func (c *CmdRemoveNode) Parse(inputArgv []string, logger vlog.Printer) error { func (c *CmdRemoveNode) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") - err := c.removeNodeOptions.ParseHostToRemoveList(c.hostToRemoveListStr) + err := c.parseHostToRemoveList() + if err != nil { + return err + } + + err = c.getCertFilesFromCertPaths(&c.removeNodeOptions.DatabaseOptions) if err != nil { return err } + err = c.ValidateParseBaseOptions(&c.removeNodeOptions.DatabaseOptions) if err != nil { return err @@ -120,6 +123,19 @@ func (c *CmdRemoveNode) validateParse(logger vlog.Printer) error { return c.setDBPassword(&c.removeNodeOptions.DatabaseOptions) } +// parseHostToRemoveList trims and lowercases the hosts in --remove +func (c *CmdRemoveNode) parseHostToRemoveList() error { + if len(c.removeNodeOptions.HostsToRemove) > 0 { + err := util.ParseHostList(&c.removeNodeOptions.HostsToRemove) + if err != nil { + // the err from util.ParseHostList will be "must specify a host or host list" + // we overwrite the error here to provide more details + return fmt.Errorf("must specify at least one host to remove") + } + } + return nil +} + func (c *CmdRemoveNode) Run(vcc vclusterops.ClusterCommands) error { vcc.LogInfo("Called method Run()") @@ -136,7 +152,7 @@ func (c *CmdRemoveNode) Run(vcc vclusterops.ClusterCommands) error { if err != nil { return err } - vcc.PrintInfo("Successfully removed nodes %s from database %s", c.hostToRemoveListStr, *options.DBName) + vcc.PrintInfo("Successfully removed nodes %v from database %s", c.removeNodeOptions.HostsToRemove, *options.DBName) // write cluster information to the YAML config file. err = vdb.WriteClusterConfig(options.ConfigPath, vcc.GetLog()) diff --git a/commands/cmd_remove_subcluster.go b/commands/cmd_remove_subcluster.go index 66f4966..e2b1e45 100644 --- a/commands/cmd_remove_subcluster.go +++ b/commands/cmd_remove_subcluster.go @@ -40,7 +40,7 @@ func makeCmdRemoveSubcluster() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "db_remove_subcluster", + removeSCSubCmd, "Remove a subcluster", `This subcommand removes a subcluster from an existing Eon Mode database. @@ -89,10 +89,6 @@ func (c *CmdRemoveSubcluster) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdRemoveSubcluster) CommandType() string { - return "db_remove_subcluster" -} - func (c *CmdRemoveSubcluster) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) @@ -101,8 +97,12 @@ func (c *CmdRemoveSubcluster) Parse(inputArgv []string, logger vlog.Printer) err func (c *CmdRemoveSubcluster) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") + err := c.getCertFilesFromCertPaths(&c.removeScOptions.DatabaseOptions) + if err != nil { + return err + } - err := c.ValidateParseBaseOptions(&c.removeScOptions.DatabaseOptions) + err = c.ValidateParseBaseOptions(&c.removeScOptions.DatabaseOptions) if err != nil { return nil } diff --git a/commands/cmd_restart_node.go b/commands/cmd_restart_node.go index cde345e..206fc44 100644 --- a/commands/cmd_restart_node.go +++ b/commands/cmd_restart_node.go @@ -31,7 +31,7 @@ type CmdRestartNodes struct { restartNodesOptions *vclusterops.VStartNodesOptions // Comma-separated list of vnode=host - vnodeListStr *string + vnodeListStr map[string]string } func makeCmdRestartNodes() *cobra.Command { @@ -40,11 +40,10 @@ func makeCmdRestartNodes() *cobra.Command { newCmd.ipv6 = new(bool) opt := vclusterops.VStartNodesOptionsFactory() newCmd.restartNodesOptions = &opt - newCmd.vnodeListStr = new(string) cmd := OldMakeBasicCobraCmd( newCmd, - "restart_node", + restartNodeSubCmd, "Restart nodes in the database", `This subcommand starts individual nodes in a running cluster. This differs from start_db, which starts Vertica after cluster quorum is lost. @@ -83,10 +82,10 @@ Examples: // setLocalFlags will set the local flags the command has func (c *CmdRestartNodes) setLocalFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - c.vnodeListStr, + cmd.Flags().StringToStringVar( + &c.vnodeListStr, "restart", - "", + map[string]string{}, "Comma-separated list of pairs part of the database nodes that need to be restarted", ) cmd.Flags().IntVar( @@ -97,10 +96,6 @@ func (c *CmdRestartNodes) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdRestartNodes) CommandType() string { - return "restart_node" -} - func (c *CmdRestartNodes) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogArgParse(&c.argv) @@ -115,10 +110,16 @@ func (c *CmdRestartNodes) Parse(inputArgv []string, logger vlog.Printer) error { func (c *CmdRestartNodes) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") - err := c.restartNodesOptions.ParseNodesList(*c.vnodeListStr) + err := c.restartNodesOptions.ParseNodesList(c.vnodeListStr) if err != nil { return err } + + err = c.getCertFilesFromCertPaths(&c.restartNodesOptions.DatabaseOptions) + if err != nil { + return err + } + err = c.ValidateParseBaseOptions(&c.restartNodesOptions.DatabaseOptions) if err != nil { return err diff --git a/commands/cmd_revive_db.go b/commands/cmd_revive_db.go index 56c0776..9baf3c5 100644 --- a/commands/cmd_revive_db.go +++ b/commands/cmd_revive_db.go @@ -30,8 +30,7 @@ import ( */ type CmdReviveDB struct { CmdBase - reviveDBOptions *vclusterops.VReviveDatabaseOptions - configurationParams *string // raw input from user, need further processing + reviveDBOptions *vclusterops.VReviveDatabaseOptions } func makeCmdReviveDB() *cobra.Command { @@ -41,7 +40,6 @@ func makeCmdReviveDB() *cobra.Command { opt := vclusterops.VReviveDBOptionsFactory() newCmd.reviveDBOptions = &opt newCmd.reviveDBOptions.CommunalStorageLocation = new(string) - newCmd.configurationParams = new(string) cmd := OldMakeBasicCobraCmd( newCmd, @@ -64,13 +62,13 @@ Examples: // common db flags newCmd.setCommonFlags(cmd, []string{dbNameFlag, hostsFlag, communalStorageLocationFlag, - configFlag, outputFileFlag}) + configFlag, outputFileFlag, configParamFlag}) // local flags newCmd.setLocalFlags(cmd) // require db-name - markFlagsRequired(cmd, []string{"db-name", "communal-storage-location"}) + markFlagsRequired(cmd, []string{dbNameFlag, communalStorageLocationFlag}) return cmd } @@ -122,12 +120,6 @@ func (c *CmdReviveDB) setLocalFlags(cmd *cobra.Command) { "", "The identifier of the restore point in the restore archive to restore from", ) - cmd.Flags().StringVar( - c.configurationParams, - "config-param", - "", - "Comma-separated list of NAME=VALUE pairs for configuration parameters", - ) // only one of restore-point-index or restore-point-id" will be required cmd.MarkFlagsMutuallyExclusive("restore-point-index", "restore-point-id") } @@ -139,7 +131,7 @@ func (c *CmdReviveDB) Parse(inputArgv []string, logger vlog.Printer) error { // for some options, we do not want to use their default values, // if they are not provided in cli, // reset the value of those options to nil - if !c.parser.Changed("ipv6") { + if !c.parser.Changed(ipv6Flag) { c.ipv6 = nil } @@ -149,14 +141,10 @@ func (c *CmdReviveDB) Parse(inputArgv []string, logger vlog.Printer) error { func (c *CmdReviveDB) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") - // check the format of configuration params string, and parse it into configParams - configurationParams, err := util.ParseConfigParams(*c.configurationParams) + err := c.getCertFilesFromCertPaths(&c.reviveDBOptions.DatabaseOptions) if err != nil { return err } - if configurationParams != nil { - c.reviveDBOptions.ConfigurationParameters = configurationParams - } // when --display-only is provided, we do not need to parse some base options like hostListStr if c.reviveDBOptions.DisplayOnly { diff --git a/commands/cmd_sandbox.go b/commands/cmd_sandbox.go index c89ffd1..00f5998 100644 --- a/commands/cmd_sandbox.go +++ b/commands/cmd_sandbox.go @@ -48,7 +48,7 @@ func makeCmdSandboxSubcluster() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "sandbox_subcluster", + sandboxSubCmd, "Sandbox a subcluster", `This subcommand sandboxes a subcluster in an existing Eon Mode database. @@ -100,10 +100,6 @@ func (c *CmdSandboxSubcluster) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdSandboxSubcluster) CommandType() string { - return "sandbox_subcluster" -} - func (c *CmdSandboxSubcluster) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) @@ -114,7 +110,12 @@ func (c *CmdSandboxSubcluster) Parse(inputArgv []string, logger vlog.Printer) er func (c *CmdSandboxSubcluster) parseInternal(logger vlog.Printer) error { logger.Info("Called parseInternal()") - err := c.ValidateParseBaseOptions(&c.sbOptions.DatabaseOptions) + err := c.getCertFilesFromCertPaths(&c.sbOptions.DatabaseOptions) + if err != nil { + return err + } + + err = c.ValidateParseBaseOptions(&c.sbOptions.DatabaseOptions) if err != nil { return err } @@ -128,7 +129,7 @@ func (c *CmdSandboxSubcluster) Analyze(logger vlog.Printer) error { func (c *CmdSandboxSubcluster) Run(vcc vclusterops.ClusterCommands) error { vcc.PrintInfo("Running sandbox subcluster") - vcc.LogInfo("Calling method Run() for command " + c.CommandType()) + vcc.LogInfo("Calling method Run() for command " + sandboxSubCmd) options := c.sbOptions // get config from vertica_cluster.yaml @@ -138,7 +139,7 @@ func (c *CmdSandboxSubcluster) Run(vcc vclusterops.ClusterCommands) error { } options.Config = config err = vcc.VSandbox(&options) - vcc.PrintInfo("Completed method Run() for command " + c.CommandType()) + vcc.PrintInfo("Completed method Run() for command " + sandboxSubCmd) return err } diff --git a/commands/cmd_scrutinize.go b/commands/cmd_scrutinize.go index def1cb6..95bf00a 100644 --- a/commands/cmd_scrutinize.go +++ b/commands/cmd_scrutinize.go @@ -28,6 +28,7 @@ import ( "github.com/spf13/cobra" "github.com/vertica/vcluster/vclusterops" "github.com/vertica/vcluster/vclusterops/vlog" + "github.com/vertica/vcluster/vclusterops/vstruct" "github.com/vertica/vertica-kubernetes/pkg/secrets" ) @@ -87,7 +88,7 @@ func makeCmdScrutinize() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "scrutinize", + scrutinizeSubCmd, "Scrutinize a database", `This scrutinizes a database on a given set of hosts. @@ -189,10 +190,6 @@ func (c *CmdScrutinize) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdScrutinize) CommandType() string { - return vclusterops.VScrutinizeTypeName -} - func (c *CmdScrutinize) Parse(inputArgv []string, logger vlog.Printer) error { logger.PrintInfo("Parsing scrutinize command input") c.argv = inputArgv @@ -201,9 +198,11 @@ func (c *CmdScrutinize) Parse(inputArgv []string, logger vlog.Printer) error { // if they are not provided in cli, // reset the value of those options to nil c.OldResetUserInputOptions() + + // remove eomModeFlag check in VER-92369 // just so generic parsing works - not relevant for functionality - if !c.parser.Changed("eon-mode") { - c.ipv6 = nil + if !c.parser.Changed(eonModeFlag) { + c.sOptions.OldIsEon = vstruct.NotSet } // if the tarballName was provided in the cli, we check @@ -223,8 +222,13 @@ func (c *CmdScrutinize) Parse(inputArgv []string, logger vlog.Printer) error { // all validations of the arguments should go in here func (c *CmdScrutinize) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") + err := c.getCertFilesFromCertPaths(&c.sOptions.DatabaseOptions) + if err != nil { + return err + } + // parses host list and ipv6 - eon is irrelevant but handled - err := c.ValidateParseBaseOptions(&c.sOptions.DatabaseOptions) + err = c.ValidateParseBaseOptions(&c.sOptions.DatabaseOptions) if err != nil { return err } diff --git a/commands/cmd_show_restore_points.go b/commands/cmd_show_restore_points.go index 4de7d37..c8a6d8c 100644 --- a/commands/cmd_show_restore_points.go +++ b/commands/cmd_show_restore_points.go @@ -18,7 +18,6 @@ package commands import ( "github.com/spf13/cobra" "github.com/vertica/vcluster/vclusterops" - "github.com/vertica/vcluster/vclusterops/util" "github.com/vertica/vcluster/vclusterops/vlog" ) @@ -29,8 +28,6 @@ import ( type CmdShowRestorePoints struct { CmdBase showRestorePointsOptions *vclusterops.VShowRestorePointsOptions - configurationParams string // raw input from user, need further processing - } func makeCmdShowRestorePoints() *cobra.Command { @@ -42,7 +39,7 @@ func makeCmdShowRestorePoints() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "show_restore_points", + showRestorePointsSubCmd, "Query and list restore point(s) in archive(s)", `This subcommand queries and lists restore point(s) in archive(s). @@ -73,8 +70,8 @@ Examples: ) // common db flags - newCmd.setCommonFlags(cmd, []string{dbNameFlag, configFlag, hostsFlag, passwordFlag, - communalStorageLocationFlag}) + newCmd.setCommonFlags(cmd, []string{dbNameFlag, configFlag, passwordFlag, hostsFlag, + communalStorageLocationFlag, configParamFlag}) // local flags newCmd.setLocalFlags(cmd) @@ -84,12 +81,6 @@ Examples: // setLocalFlags will set the local flags the command has func (c *CmdShowRestorePoints) setLocalFlags(cmd *cobra.Command) { - cmd.Flags().StringVar( - &c.configurationParams, - "config-param", - "", - "Comma-separated list of NAME=VALUE pairs for configuration parameters", - ) cmd.Flags().StringVar( c.showRestorePointsOptions.FilterOptions.ArchiveName, "restore-point-archive", @@ -122,10 +113,6 @@ func (c *CmdShowRestorePoints) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdShowRestorePoints) CommandType() string { - return "show_restore_points" -} - func (c *CmdShowRestorePoints) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) @@ -141,14 +128,10 @@ func (c *CmdShowRestorePoints) Parse(inputArgv []string, logger vlog.Printer) er func (c *CmdShowRestorePoints) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") - // check the format of configuration params string, and parse it into configParams - configurationParams, err := util.ParseConfigParams(c.configurationParams) + err := c.getCertFilesFromCertPaths(&c.showRestorePointsOptions.DatabaseOptions) if err != nil { return err } - if configurationParams != nil { - c.showRestorePointsOptions.ConfigurationParameters = configurationParams - } err = c.ValidateParseBaseOptions(&c.showRestorePointsOptions.DatabaseOptions) if err != nil { diff --git a/commands/cmd_start_db.go b/commands/cmd_start_db.go index b316cc4..f1a7ae3 100644 --- a/commands/cmd_start_db.go +++ b/commands/cmd_start_db.go @@ -30,12 +30,11 @@ type CmdStartDB struct { CmdBase startDBOptions *vclusterops.VStartDatabaseOptions - Force bool // force cleanup to start the database - AllowFallbackKeygen bool // Generate spread encryption key from Vertica. Use under support guidance only - IgnoreClusterLease bool // ignore the cluster lease in communal storage - Unsafe bool // Start database unsafely, skipping recovery. - Fast bool // Attempt fast startup database - configurationParams string // raw input from user, need further processing + Force bool // force cleanup to start the database + AllowFallbackKeygen bool // Generate spread encryption key from Vertica. Use under support guidance only + IgnoreClusterLease bool // ignore the cluster lease in communal storage + Unsafe bool // Start database unsafely, skipping recovery. + Fast bool // Attempt fast startup database } func makeCmdStartDB() *cobra.Command { @@ -49,7 +48,7 @@ func makeCmdStartDB() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "start_db", + startDBSubCmd, "Start a database", `This subcommand starts a database on a set of hosts. @@ -68,7 +67,8 @@ Examples: ) // common db flags - newCmd.setCommonFlags(cmd, []string{dbNameFlag, hostsFlag, communalStorageLocationFlag, configFlag, catalogPathFlag, passwordFlag}) + newCmd.setCommonFlags(cmd, []string{dbNameFlag, hostsFlag, communalStorageLocationFlag, + configFlag, catalogPathFlag, passwordFlag, eonModeFlag, configParamFlag}) // local flags newCmd.setLocalFlags(cmd) @@ -82,19 +82,6 @@ Examples: // setLocalFlags will set the local flags the command has func (c *CmdStartDB) setLocalFlags(cmd *cobra.Command) { - cmd.Flags().BoolVar( - c.isEon, - "eon-mode", - false, - util.GetEonFlagMsg("indicate if the database is an Eon database."+ - " Use it when you do not trust "+vclusterops.ConfigFileName), - ) - cmd.Flags().StringVar( - &c.configurationParams, - "config-param", - "", - "Comma-separated list of NAME=VALUE pairs of existing configuration parameters", - ) cmd.Flags().IntVar( c.startDBOptions.StatePollingTimeout, "timeout", @@ -145,35 +132,30 @@ func (c *CmdStartDB) setHiddenFlags(cmd *cobra.Command) { hideLocalFlags(cmd, []string{"unsafe", "force", "allow_fallback_keygen", "ignore_cluster_lease", "fast", "trim-hosts"}) } -func (c *CmdStartDB) CommandType() string { - return "start_db" -} - func (c *CmdStartDB) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) + // remove eonModeFlag check in VER-92369 // for some options, we do not want to use their default values, // if they are not provided in cli, // reset the value of those options to nil - if !c.parser.Changed("eon-mode") { + if !c.parser.Changed(eonModeFlag) { c.CmdBase.isEon = nil + } else { + *c.CmdBase.isEon = true } c.OldResetUserInputOptions() return c.validateParse(logger) } func (c *CmdStartDB) validateParse(logger vlog.Printer) error { - logger.Info("Called validateParse()", "command", c.CommandType()) + logger.Info("Called validateParse()", "command", startDBSubCmd) - // check the format of configuration params string, and parse it into configParams - configurationParams, err := util.ParseConfigParams(c.configurationParams) + err := c.getCertFilesFromCertPaths(&c.startDBOptions.DatabaseOptions) if err != nil { return err } - if configurationParams != nil { - c.startDBOptions.ConfigurationParameters = configurationParams - } err = c.ValidateParseBaseOptions(&c.startDBOptions.DatabaseOptions) if err != nil { diff --git a/commands/cmd_stop_db.go b/commands/cmd_stop_db.go index 858f791..d8d769b 100644 --- a/commands/cmd_stop_db.go +++ b/commands/cmd_stop_db.go @@ -126,7 +126,12 @@ func (c *CmdStopDB) Parse(inputArgv []string, logger vlog.Printer) error { // all validations of the arguments should go in here func (c *CmdStopDB) validateParse(logger vlog.Printer) error { logger.Info("Called validateParse()") - err := c.ValidateParseBaseOptions(&c.stopDBOptions.DatabaseOptions) + err := c.getCertFilesFromCertPaths(&c.stopDBOptions.DatabaseOptions) + if err != nil { + return err + } + + err = c.ValidateParseBaseOptions(&c.stopDBOptions.DatabaseOptions) if err != nil { return err } diff --git a/commands/cmd_unsandbox.go b/commands/cmd_unsandbox.go index 803ad19..dd33cc2 100644 --- a/commands/cmd_unsandbox.go +++ b/commands/cmd_unsandbox.go @@ -47,7 +47,7 @@ func makeCmdUnsandboxSubcluster() *cobra.Command { cmd := OldMakeBasicCobraCmd( newCmd, - "unsandbox_subcluster", + unsandboxSubCmd, "Unsandbox a subcluster", `This subcommand unsandboxes a subcluster in an existing Eon Mode database. @@ -98,10 +98,6 @@ func (c *CmdUnsandboxSubcluster) setLocalFlags(cmd *cobra.Command) { ) } -func (c *CmdUnsandboxSubcluster) CommandType() string { - return "unsandbox_subcluster" -} - func (c *CmdUnsandboxSubcluster) Parse(inputArgv []string, logger vlog.Printer) error { c.argv = inputArgv logger.LogMaskedArgParse(c.argv) @@ -112,7 +108,13 @@ func (c *CmdUnsandboxSubcluster) Parse(inputArgv []string, logger vlog.Printer) // ParseInternal parses internal commands for unsandboxed subclusters. func (c *CmdUnsandboxSubcluster) parseInternal(logger vlog.Printer) error { logger.Info("Called parseInternal()") - err := c.ValidateParseBaseOptions(&c.usOptions.DatabaseOptions) + + err := c.getCertFilesFromCertPaths(&c.usOptions.DatabaseOptions) + if err != nil { + return err + } + + err = c.ValidateParseBaseOptions(&c.usOptions.DatabaseOptions) if err != nil { return err } @@ -127,7 +129,7 @@ func (c *CmdUnsandboxSubcluster) Analyze(logger vlog.Printer) error { func (c *CmdUnsandboxSubcluster) Run(vcc vclusterops.ClusterCommands) error { vcc.PrintInfo("Running unsandbox subcluster") - vcc.LogInfo("Calling method Run() for command " + c.CommandType()) + vcc.LogInfo("Calling method Run() for command " + unsandboxSubCmd) options := c.usOptions @@ -138,7 +140,7 @@ func (c *CmdUnsandboxSubcluster) Run(vcc vclusterops.ClusterCommands) error { } options.Config = config err = vcc.VUnsandbox(&options) - vcc.PrintInfo("Completed method Run() for command " + c.CommandType()) + vcc.PrintInfo("Completed method Run() for command " + unsandboxSubCmd) return err } diff --git a/main.go b/main.go index e783dba..dcc13b3 100644 --- a/main.go +++ b/main.go @@ -16,46 +16,9 @@ package main import ( - "fmt" - "os" - "github.com/vertica/vcluster/commands" - "github.com/vertica/vcluster/vclusterops/util" ) func main() { - // only keep commands.Execute() in main() in VER-92222 - var cobraSupportedCmdAndFlags = []string{ - "-h", "--help", - "completion", - "create_db", - "stop_db", - "start_db", - "list_allnodes", - "restart_node", - "drop_db", - "revive_db", - "re_ip", - "scrutinize", - "config", - "db_add_node", - "db_remove_node", - "install_packages", - "show_restore_points", - "db_add_subcluster", - "db_remove_subcluster", - "sandbox_subcluster", - "unsandbox_subcluster", - } - - if len(os.Args) == 1 || util.StringInArray(os.Args[1], cobraSupportedCmdAndFlags) { - commands.Execute() - } else { - launcher, vcc := commands.MakeClusterCommandLauncher() - runError := launcher.Run(os.Args, vcc) - if runError != nil { - fmt.Printf("Error during execution: %s\n", runError) - os.Exit(1) - } - } + commands.Execute() } diff --git a/vclusterops/cluster_op_engine.go b/vclusterops/cluster_op_engine.go index b6144bb..8c2b75a 100644 --- a/vclusterops/cluster_op_engine.go +++ b/vclusterops/cluster_op_engine.go @@ -35,7 +35,7 @@ func makeClusterOpEngine(instructions []clusterOp, certs *httpsCerts) VClusterOp } func (opEngine *VClusterOpEngine) shouldGetCertsFromOptions() bool { - return (opEngine.certs.key != "" && opEngine.certs.cert != "" && opEngine.certs.caCert != "") + return (opEngine.certs.key != "" && opEngine.certs.cert != "") } func (opEngine *VClusterOpEngine) run(logger vlog.Printer) error { diff --git a/vclusterops/http_adapter.go b/vclusterops/http_adapter.go index edae605..2352efb 100644 --- a/vclusterops/http_adapter.go +++ b/vclusterops/http_adapter.go @@ -312,9 +312,11 @@ func (adapter *httpAdapter) buildCertsFromMemory(key, cert, caCert string) (tls. } caCertPool := x509.NewCertPool() - ok := caCertPool.AppendCertsFromPEM([]byte(caCert)) - if !ok { - return certificate, nil, fmt.Errorf("fail to load HTTPS CA certificates") + if caCert != "" { + ok := caCertPool.AppendCertsFromPEM([]byte(caCert)) + if !ok { + return certificate, nil, fmt.Errorf("fail to load HTTPS CA certificates") + } } return certificate, caCertPool, nil diff --git a/vclusterops/https_get_up_nodes_op.go b/vclusterops/https_get_up_nodes_op.go index 56586eb..1cbbaed 100644 --- a/vclusterops/https_get_up_nodes_op.go +++ b/vclusterops/https_get_up_nodes_op.go @@ -180,6 +180,14 @@ func (op *httpsGetUpNodesOp) processResult(execContext *opEngineExecContext) err continue } + if op.cmdType == StopDBCmd { + err = op.validateHosts(nodesStates) + if err != nil { + allErrs = errors.Join(allErrs, err) + break + } + } + // collect all the up hosts err = op.collectUpHosts(nodesStates, host, upHosts, upScInfo, sandboxInfo, upScNodes) if err != nil { @@ -260,6 +268,34 @@ func (op *httpsGetUpNodesOp) processHostLists(upHosts mapset.Set[string], upScIn return op.noUpHostsOk } +// validateHosts can validate if hosts in user input matches the ones in GET /nodes response +func (op *httpsGetUpNodesOp) validateHosts(nodesStates nodesStateInfo) error { + var dbHosts []string + dbUnexpected := false + unexpectedDBName := "" + for _, node := range nodesStates.NodeList { + if node.Database != op.DBName { + unexpectedDBName = node.Database + dbUnexpected = true + } + dbHosts = append(dbHosts, node.Address) + } + // when db name does not match, we throw an error + if dbUnexpected { + unexpectedHosts := util.SliceCommon(op.hosts, dbHosts) + return fmt.Errorf(`[%s] unexpected database %q is running on hosts %v. Please ensure the provided hosts or database name are correct`, + op.name, unexpectedDBName, unexpectedHosts) + } + // when hosts from user input do not match the ones from running db, we throw an error + unexpectedHosts := util.SliceDiff(op.hosts, dbHosts) + if len(unexpectedHosts) > 0 { + return fmt.Errorf(`[%s] database %q does not contain any nodes on the hosts %v. Please ensure the hosts are correct`, + op.name, op.DBName, unexpectedHosts) + } + + return nil +} + func (op *httpsGetUpNodesOp) collectUpHosts(nodesStates nodesStateInfo, host string, upHosts mapset.Set[string], upScInfo, sandboxInfo map[string]string, upScNodes mapset.Set[NodeInfo]) (err error) { upMainNodeFound := false diff --git a/vclusterops/remove_node.go b/vclusterops/remove_node.go index 6459e97..5a3c711 100644 --- a/vclusterops/remove_node.go +++ b/vclusterops/remove_node.go @@ -47,21 +47,6 @@ func (o *VRemoveNodeOptions) setDefaultValues() { *o.ForceDelete = true } -// ParseHostToRemoveList converts a comma-separated string list of hosts into a slice of host names -// to remove from the database. During parsing, the hosts are converted to lowercase. -// It returns any parsing error encountered. -func (o *VRemoveNodeOptions) ParseHostToRemoveList(hosts string) error { - inputHostList, err := util.SplitHosts(hosts) - if err != nil { - if len(inputHostList) == 0 { - return fmt.Errorf("must specify at least one host to remove") - } - } - - o.HostsToRemove = inputHostList - return nil -} - func (o *VRemoveNodeOptions) validateRequiredOptions(log vlog.Printer) error { err := o.validateBaseOptions("db_remove_node", log) if err != nil { diff --git a/vclusterops/start_node.go b/vclusterops/start_node.go index 1fcce52..fc7e642 100644 --- a/vclusterops/start_node.go +++ b/vclusterops/start_node.go @@ -83,15 +83,12 @@ func (options *VStartNodesOptions) analyzeOptions() (err error) { return nil } -// ParseNodesList builds and returns a map of nodes from a comma-separated list of nodes. -// For example, vnodeName1=host1,vnodeName2=host2 is converted to map[string]string{vnodeName1: host1, vnodeName2: host2} -func (options *VStartNodesOptions) ParseNodesList(nodeListStr string) error { - nodes, err := util.ParseKeyValueListStr(nodeListStr, "restart") - if err != nil { - return err - } +// ParseNodesList resolves hostname in a nodeName-hostname map and build a new map. +// For example, map[string]string{vnodeName1: host1, vnodeName2: host2} is converted to +// map[string]string{vnodeName1: 192.168.1.101, vnodeName2: 192.168.1.102} +func (options *VStartNodesOptions) ParseNodesList(rawNodeMap map[string]string) error { options.Nodes = make(map[string]string) - for k, v := range nodes { + for k, v := range rawNodeMap { ip, err := util.ResolveToOneIP(v, options.OldIpv6.ToBool()) if err != nil { return err diff --git a/vclusterops/util/util.go b/vclusterops/util/util.go index 41ad03a..86995a9 100644 --- a/vclusterops/util/util.go +++ b/vclusterops/util/util.go @@ -35,6 +35,7 @@ import ( "golang.org/x/exp/slices" "golang.org/x/sys/unix" + mapset "github.com/deckarep/golang-set/v2" "github.com/vertica/vcluster/vclusterops/vlog" ) @@ -130,6 +131,16 @@ func SliceDiff[K comparable](m, n []K) []K { return diff } +// calculate and sort array commonalities: m ∩ n +func SliceCommon[K constraints.Ordered](m, n []K) []K { + mSet := mapset.NewSet[K](m...) + nSet := mapset.NewSet[K](n...) + common := mSet.Intersect(nSet).ToSlice() + slices.Sort(common) + + return common +} + // calculate diff of map keys: m-n func MapKeyDiff[M ~map[K]V, K comparable, V any](m, n M) []K { var diff []K @@ -321,18 +332,6 @@ func AbsPathCheck(dirPath string) error { return nil } -// remove this function in VER-92222 -func SplitHosts(hosts string) ([]string, error) { - if strings.TrimSpace(hosts) == "" { - return []string{}, fmt.Errorf("must specify a host or host list") - } - splitRes := strings.Split(strings.ToLower(strings.TrimSpace(hosts)), ",") - for i, host := range splitRes { - splitRes[i] = strings.TrimSpace(host) - } - return splitRes, nil -} - // ParseHostList will trim spaces and convert all chars to lowercase in the hosts func ParseHostList(hosts *[]string) error { var parsedHosts []string @@ -502,45 +501,6 @@ func ParamNotSetErrorMsg(param string) error { return fmt.Errorf("%s is pointed to nil", param) } -// ParseConfigParams builds and returns a map from a comma-separated list of params. -func ParseConfigParams(configParamListStr string) (map[string]string, error) { - return ParseKeyValueListStr(configParamListStr, "config-param") -} - -// ParseKeyValueListStr converts a comma-separated list of key-value pairs into a map. -// Ex: key1=val1,key2=val2 ---> map[string]string{key1: val1, key2: val2} -func ParseKeyValueListStr(listStr, opt string) (map[string]string, error) { - if listStr == "" { - return nil, nil - } - list := strings.Split(strings.TrimSpace(listStr), ",") - // passed an empty string to the given flag - if len(list) == 0 { - return nil, nil - } - - listMap := make(map[string]string) - for _, param := range list { - // expected to see key value pairs of the format key=value - keyValue := strings.Split(param, "=") - if len(keyValue) != keyValueArrayLen { - return nil, fmt.Errorf("--%s option must take NAME=VALUE as argument: %s is invalid", opt, param) - } else if len(keyValue) > 0 && strings.TrimSpace(keyValue[0]) == "" { - return nil, fmt.Errorf("--%s option must take NAME=VALUE as argument with NAME being non-empty: %s is invalid", opt, param) - } - key := strings.TrimSpace(keyValue[0]) - // the user is possible to set aws auth key to different strings like "awsauth" and "AWSAuth" - // we convert aws auth key to lowercase for easy retrieval in vclusterops - if strings.EqualFold(key, AWSAuthKey) { - key = strings.ToLower(key) - } - // we allow empty string value - value := strings.TrimSpace(keyValue[1]) - listMap[key] = value - } - return listMap, nil -} - // GenVNodeName generates a vnode and returns it after checking it is not already // taken by an existing node. func GenVNodeName(vnodes map[string]string, dbName string, hostCount int) (string, bool) { diff --git a/vclusterops/util/util_test.go b/vclusterops/util/util_test.go index ed5821e..f9ecfa3 100644 --- a/vclusterops/util/util_test.go +++ b/vclusterops/util/util_test.go @@ -141,21 +141,6 @@ func TestGetCleanPath(t *testing.T) { assert.Equal(t, res, "/data") } -func TestSplitHosts(t *testing.T) { - // positive case - hosts := "vnode1, vnode2" - res, err := SplitHosts(hosts) - expected := []string{"vnode1", "vnode2"} - assert.Nil(t, err) - assert.Equal(t, res, expected) - - // negative case - hosts = " " - res, err = SplitHosts(hosts) - assert.NotNil(t, err) - assert.Equal(t, res, []string{}) -} - func TestParseHostList(t *testing.T) { // positive case hosts := []string{" vnode1 ", " vnode2", "vnode3 ", " "} @@ -204,6 +189,14 @@ func TestSliceDiff(t *testing.T) { assert.Equal(t, expected, actual) } +func TestSliceCommon(t *testing.T) { + a := []string{"3", "5", "4", "1", "2"} + b := []string{"5", "6", "7", "4", "3"} + expected := []string{"3", "4", "5"} + actual := SliceCommon(a, b) + assert.Equal(t, expected, actual) +} + func TestMapKeyDiff(t *testing.T) { a := map[string]bool{"1": true, "2": true} b := map[string]bool{"1": true, "3": true, "4": false} @@ -282,28 +275,6 @@ func TestSetEonFlagHelpMsg(t *testing.T) { assert.Equal(t, GetEonFlagMsg(msg), finalMsg) } -func TestParseConfigParams(t *testing.T) { - configParamsListStr := "" - configParams, err := ParseConfigParams(configParamsListStr) - assert.Nil(t, err) - assert.Nil(t, configParams) - - configParamsListStr = "key1=val1,key2" - configParams, err = ParseConfigParams(configParamsListStr) - assert.NotNil(t, err) - assert.Nil(t, configParams) - - configParamsListStr = "key1=val1,=val2" - configParams, err = ParseConfigParams(configParamsListStr) - assert.NotNil(t, err) - assert.Nil(t, configParams) - - configParamsListStr = "key1=val1,key2=val2" - configParams, err = ParseConfigParams(configParamsListStr) - assert.Nil(t, err) - assert.ObjectsAreEqualValues(configParams, map[string]string{"key1": "val1", "key2": "val2"}) -} - func TestGenVNodeName(t *testing.T) { dbName := "test_db" // returns vnode