From e2e37e3cca428e1ae3bb5f8692323981c744b9c7 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 08:27:15 -0500 Subject: [PATCH 01/28] first stab at generic validation --- cmd/authAddContext.go | 1 - cmd/cloudBackupList.go | 11 ++ cmd/cloudBackupRestore.go | 9 ++ cmd/cloudImageCreate.go | 8 + cmd/cloudImageRestore.go | 9 ++ cmd/cloudNetworkPrivateAttach.go | 8 + cmd/cloudNetworkPrivateDetach.go | 8 + cmd/cloudNetworkPrivateDetails.go | 8 + cmd/cloudNetworkPublicAdd.go | 11 +- cmd/cloudNetworkPublicRemove.go | 18 ++- cmd/cloudNetworkVipCreate.go | 8 + cmd/cloudNetworkVipDelete.go | 8 + cmd/cloudNetworkVipDetails.go | 8 + cmd/cloudPrivateParentCreate.go | 14 ++ cmd/cloudPrivateParentRename.go | 9 ++ cmd/cloudServerBlockStorageOptimizedCheck.go | 8 + ...cloudServerBlockStorageOptimizedDisable.go | 8 + cmd/cloudServerBlockStorageOptimizedEnable.go | 8 + cmd/cloudServerClone.go | 9 +- cmd/cloudServerCreate.go | 8 + cmd/cloudServerDestroy.go | 11 ++ cmd/cloudServerDetails.go | 9 +- cmd/cloudServerReboot.go | 8 + cmd/cloudServerResize.go | 8 + cmd/cloudServerShutdown.go | 8 + cmd/cloudServerStart.go | 8 + cmd/cloudServerStatus.go | 8 + cmd/cloudServerUpdate.go | 8 + cmd/cloudStorageBlockVolumeAttach.go | 14 ++ cmd/cloudStorageBlockVolumeCreate.go | 8 + cmd/cloudStorageBlockVolumeDelete.go | 11 +- cmd/cloudStorageBlockVolumeDetach.go | 14 ++ cmd/cloudStorageBlockVolumeDetails.go | 8 + cmd/cloudStorageBlockVolumeResize.go | 9 ++ cmd/cloudStorageBlockVolumeUpdate.go | 10 +- cmd/cloudStorageObjectCreateKey.go | 8 + cmd/cloudStorageObjectDelete.go | 8 + cmd/cloudStorageObjectDeleteKey.go | 8 + cmd/cloudStorageObjectDetails.go | 8 + cmd/networkIpPoolDelete.go | 8 + cmd/networkIpPoolDetails.go | 8 + cmd/networkIpPoolUpdate.go | 8 + utils/utils.go | 9 ++ validate/types.go | 107 +++++++++++++ validate/validate.go | 148 ++++++++++++++++++ 45 files changed, 634 insertions(+), 7 deletions(-) create mode 100644 validate/types.go create mode 100644 validate/validate.go diff --git a/cmd/authAddContext.go b/cmd/authAddContext.go index e70aebb..8150a11 100644 --- a/cmd/authAddContext.go +++ b/cmd/authAddContext.go @@ -85,7 +85,6 @@ func init() { authAddContextCmd.Flags().String("api-url", "https://api.liquidweb.com", "API URL to use") authAddContextCmd.Flags().Int("timeout", 30, "timeout value when communicating with api-url") - authAddContextCmd.MarkFlagRequired("uniq_id") authAddContextCmd.MarkFlagRequired("username") authAddContextCmd.MarkFlagRequired("password") } diff --git a/cmd/cloudBackupList.go b/cmd/cloudBackupList.go index dbecaf4..702849b 100644 --- a/cmd/cloudBackupList.go +++ b/cmd/cloudBackupList.go @@ -23,6 +23,7 @@ import ( "github.com/liquidweb/liquidweb-cli/instance" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudBackupListCmd = &cobra.Command{ @@ -33,6 +34,16 @@ var cloudBackupListCmd = &cobra.Command{ jsonFlag, _ := cmd.Flags().GetBool("json") uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + if uniqIdFlag != "" { + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + } + methodArgs := instance.AllPaginatedResultsArgs{ Method: "bleed/storm/backup/list", ResultsPerPage: 100, diff --git a/cmd/cloudBackupRestore.go b/cmd/cloudBackupRestore.go index 568dcd2..0621669 100644 --- a/cmd/cloudBackupRestore.go +++ b/cmd/cloudBackupRestore.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudBackupRestoreCmd = &cobra.Command{ @@ -32,6 +33,14 @@ var cloudBackupRestoreCmd = &cobra.Command{ rebuildFsFlag, _ := cmd.Flags().GetBool("rebuild-fs") backupIdFlag, _ := cmd.Flags().GetInt64("backup_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + "PositiveInt64": backupIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{"id": backupIdFlag, "uniq_id": uniqIdFlag} if rebuildFsFlag { apiArgs["force"] = 1 diff --git a/cmd/cloudImageCreate.go b/cmd/cloudImageCreate.go index fe629cb..418dda4 100644 --- a/cmd/cloudImageCreate.go +++ b/cmd/cloudImageCreate.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudImageCreateCmd = &cobra.Command{ @@ -31,6 +32,13 @@ var cloudImageCreateCmd = &cobra.Command{ uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") nameFlag, _ := cmd.Flags().GetString("name") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{"name": nameFlag, "uniq_id": uniqIdFlag} var details apiTypes.CloudImageCreateResponse diff --git a/cmd/cloudImageRestore.go b/cmd/cloudImageRestore.go index 0aebfc5..e502e9d 100644 --- a/cmd/cloudImageRestore.go +++ b/cmd/cloudImageRestore.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudImageRestoreCmd = &cobra.Command{ @@ -32,6 +33,14 @@ var cloudImageRestoreCmd = &cobra.Command{ rebuildFsFlag, _ := cmd.Flags().GetBool("rebuild-fs") imageIdFlag, _ := cmd.Flags().GetInt64("image_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + "PositiveInt64": imageIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{"id": imageIdFlag, "uniq_id": uniqIdFlag} if rebuildFsFlag { apiArgs["force"] = 1 diff --git a/cmd/cloudNetworkPrivateAttach.go b/cmd/cloudNetworkPrivateAttach.go index fe2009d..9238a8a 100644 --- a/cmd/cloudNetworkPrivateAttach.go +++ b/cmd/cloudNetworkPrivateAttach.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudNetworkPrivateAttachCmd = &cobra.Command{ @@ -40,6 +41,13 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} var attachedDetails apiTypes.CloudNetworkPrivateIsAttachedResponse diff --git a/cmd/cloudNetworkPrivateDetach.go b/cmd/cloudNetworkPrivateDetach.go index 32431bb..6dd60e5 100644 --- a/cmd/cloudNetworkPrivateDetach.go +++ b/cmd/cloudNetworkPrivateDetach.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudNetworkPrivateDetachCmd = &cobra.Command{ @@ -40,6 +41,13 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} var attachedDetails apiTypes.CloudNetworkPrivateIsAttachedResponse diff --git a/cmd/cloudNetworkPrivateDetails.go b/cmd/cloudNetworkPrivateDetails.go index 565c5c0..8cbd51d 100644 --- a/cmd/cloudNetworkPrivateDetails.go +++ b/cmd/cloudNetworkPrivateDetails.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudNetworkPrivateDetailsCmd = &cobra.Command{ @@ -40,6 +41,13 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} var details apiTypes.CloudNetworkPrivateGetIpResponse diff --git a/cmd/cloudNetworkPublicAdd.go b/cmd/cloudNetworkPublicAdd.go index 450e99f..b4360d4 100644 --- a/cmd/cloudNetworkPublicAdd.go +++ b/cmd/cloudNetworkPublicAdd.go @@ -18,8 +18,10 @@ package cmd import ( "fmt" - "github.com/liquidweb/liquidweb-cli/types/api" "github.com/spf13/cobra" + + "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudNetworkPublicAddCmdPoolIpsFlag []string @@ -40,6 +42,13 @@ will be up to the administrator to configure the IP address(es) within the serve rebootFlag, _ := cmd.Flags().GetBool("reboot") newIpsFlag, _ := cmd.Flags().GetInt64("new-ips") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + if newIpsFlag == 0 && len(cloudNetworkPublicAddCmdPoolIpsFlag) == 0 { lwCliInst.Die(fmt.Errorf("at least one of --new-ips --pool-ips must be given")) } diff --git a/cmd/cloudNetworkPublicRemove.go b/cmd/cloudNetworkPublicRemove.go index b4553e3..dcf1b1e 100644 --- a/cmd/cloudNetworkPublicRemove.go +++ b/cmd/cloudNetworkPublicRemove.go @@ -18,8 +18,10 @@ package cmd import ( "fmt" - "github.com/liquidweb/liquidweb-cli/types/api" "github.com/spf13/cobra" + + "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudNetworkPublicRemoveCmdIpsFlag []string @@ -42,12 +44,26 @@ Note that you cannot remove the Cloud Servers primary ip with this command.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") rebootFlag, _ := cmd.Flags().GetBool("reboot") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "reboot": rebootFlag, "uniq_id": uniqIdFlag, } for _, ip := range cloudNetworkPublicRemoveCmdIpsFlag { + validateFields := map[string]interface{}{ + "IP": ip, + } + if err := validate.Validate(validateFields); err != nil { + fmt.Printf("passed ip failed validation: %s ... skipping\n", err) + } + var details apiTypes.NetworkIpRemove apiArgs["ip"] = ip err := lwCliInst.CallLwApiInto("bleed/network/ip/remove", apiArgs, &details) diff --git a/cmd/cloudNetworkVipCreate.go b/cmd/cloudNetworkVipCreate.go index 65a6ca5..b7853e2 100644 --- a/cmd/cloudNetworkVipCreate.go +++ b/cmd/cloudNetworkVipCreate.go @@ -22,6 +22,7 @@ import ( "github.com/liquidweb/liquidweb-cli/types/api" "github.com/liquidweb/liquidweb-cli/utils" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudNetworkVipCreateCmd = &cobra.Command{ @@ -64,6 +65,13 @@ Heartbeat nameFlag, _ := cmd.Flags().GetString("name") zoneFlag, _ := cmd.Flags().GetInt64("zone") + validateFields := map[string]interface{}{ + "PositiveInt64": zoneFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "domain": nameFlag, "zone": zoneFlag, diff --git a/cmd/cloudNetworkVipDelete.go b/cmd/cloudNetworkVipDelete.go index 080de2a..1216a6e 100644 --- a/cmd/cloudNetworkVipDelete.go +++ b/cmd/cloudNetworkVipDelete.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudNetworkVipDeleteCmd = &cobra.Command{ @@ -62,6 +63,13 @@ Heartbeat Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, } diff --git a/cmd/cloudNetworkVipDetails.go b/cmd/cloudNetworkVipDetails.go index c6c8913..1ded628 100644 --- a/cmd/cloudNetworkVipDetails.go +++ b/cmd/cloudNetworkVipDetails.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudNetworkVipDetailsCmd = &cobra.Command{ @@ -62,6 +63,13 @@ Heartbeat Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, } diff --git a/cmd/cloudPrivateParentCreate.go b/cmd/cloudPrivateParentCreate.go index 7823241..bcf0ee1 100644 --- a/cmd/cloudPrivateParentCreate.go +++ b/cmd/cloudPrivateParentCreate.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudPrivateParentCreateCmd = &cobra.Command{ @@ -40,6 +41,19 @@ of configs, check 'cloud server options --configs'.`, configIdFlag, _ := cmd.Flags().GetInt64("config_id") zoneFlag, _ := cmd.Flags().GetInt64("zone") + validateFields := map[string]interface{}{ + "PositiveInt64": zoneFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + validateFields = map[string]interface{}{ + "PositiveInt64": configIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "domain": nameFlag, "config_id": configIdFlag, diff --git a/cmd/cloudPrivateParentRename.go b/cmd/cloudPrivateParentRename.go index 7517c6d..89e6cf4 100644 --- a/cmd/cloudPrivateParentRename.go +++ b/cmd/cloudPrivateParentRename.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudPrivateParentRenameCmd = &cobra.Command{ @@ -36,6 +37,14 @@ as well as how many resources each Cloud Server gets.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") nameFlag, _ := cmd.Flags().GetString("name") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + "NonEmptyString": nameFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, "domain": nameFlag, diff --git a/cmd/cloudServerBlockStorageOptimizedCheck.go b/cmd/cloudServerBlockStorageOptimizedCheck.go index 7918eb5..f71e7ba 100644 --- a/cmd/cloudServerBlockStorageOptimizedCheck.go +++ b/cmd/cloudServerBlockStorageOptimizedCheck.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerBlockStorageOptimizedCheckCmd = &cobra.Command{ @@ -37,6 +38,13 @@ Storage Optimized.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, } diff --git a/cmd/cloudServerBlockStorageOptimizedDisable.go b/cmd/cloudServerBlockStorageOptimizedDisable.go index 9f3ff17..31b42c1 100644 --- a/cmd/cloudServerBlockStorageOptimizedDisable.go +++ b/cmd/cloudServerBlockStorageOptimizedDisable.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerBlockStorageOptimizedDisableCmd = &cobra.Command{ @@ -39,6 +40,13 @@ Disabling Cloud Block Storage will cause your Cloud Server to reboot.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + var optimized apiTypes.CloudServerIsBlockStorageOptimized if err := lwCliInst.CallLwApiInto("bleed/storm/server/issbsoptimized", map[string]interface{}{"uniq_id": uniqIdFlag}, &optimized); err != nil { diff --git a/cmd/cloudServerBlockStorageOptimizedEnable.go b/cmd/cloudServerBlockStorageOptimizedEnable.go index 2d69259..5f40945 100644 --- a/cmd/cloudServerBlockStorageOptimizedEnable.go +++ b/cmd/cloudServerBlockStorageOptimizedEnable.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerBlockStorageOptimizedEnableCmd = &cobra.Command{ @@ -39,6 +40,13 @@ Enabling Cloud Block Storage will cause your Cloud Server to reboot.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + var optimized apiTypes.CloudServerIsBlockStorageOptimized if err := lwCliInst.CallLwApiInto("bleed/storm/server/issbsoptimized", map[string]interface{}{"uniq_id": uniqIdFlag}, &optimized); err != nil { diff --git a/cmd/cloudServerClone.go b/cmd/cloudServerClone.go index b725db3..3755308 100644 --- a/cmd/cloudServerClone.go +++ b/cmd/cloudServerClone.go @@ -22,6 +22,7 @@ import ( "github.com/liquidweb/liquidweb-cli/types/api" "github.com/liquidweb/liquidweb-cli/utils" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerCloneCmdPoolIpsFlag []string @@ -57,7 +58,13 @@ Server is not on a Private Parent.`, vcpuFlag, _ := cmd.Flags().GetInt64("vcpu") configIdFlag, _ := cmd.Flags().GetInt64("config_id") - // flag check + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + if privateParentFlag != "" && configIdFlag != -1 { lwCliInst.Die(fmt.Errorf("cant pass both --config_id and --private-parent flags")) } diff --git a/cmd/cloudServerCreate.go b/cmd/cloudServerCreate.go index f9e4e1b..7da08fb 100644 --- a/cmd/cloudServerCreate.go +++ b/cmd/cloudServerCreate.go @@ -26,6 +26,7 @@ import ( "github.com/liquidweb/liquidweb-cli/types/api" "github.com/liquidweb/liquidweb-cli/utils" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerCreateCmdPoolIpsFlag []string @@ -78,6 +79,13 @@ For a list of backups, see 'cloud inventory backups list' backupIdFlag, _ := cmd.Flags().GetInt("backup-id") imageIdFlag, _ := cmd.Flags().GetInt("image-id") + validateFields := map[string]interface{}{ + "PositiveInt": zoneFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + // sanity check flags if configIdFlag == 0 && privateParentFlag == "" { lwCliInst.Die(fmt.Errorf("--config_id is a required flag without --private-parent")) diff --git a/cmd/cloudServerDestroy.go b/cmd/cloudServerDestroy.go index 77a505b..e8f6a7f 100644 --- a/cmd/cloudServerDestroy.go +++ b/cmd/cloudServerDestroy.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerDestroyCmdUniqIdFlag []string @@ -38,6 +39,16 @@ server.`, reasonFlag, _ := cmd.Flags().GetString("reason") for _, uniqId := range cloudServerDestroyCmdUniqIdFlag { + + validateFields := map[string]interface{}{ + "UniqId": uniqId, + } + + if err := validate.Validate(validateFields); err != nil { + fmt.Printf("passed uniq_id [%s] failed validation: %s ... skipping\n", uniqId, err) + continue + } + destroyArgs := map[string]interface{}{ "uniq_id": uniqId, "cancellation_comment": commentFlag, diff --git a/cmd/cloudServerDetails.go b/cmd/cloudServerDetails.go index e5e2cca..519ad8e 100644 --- a/cmd/cloudServerDetails.go +++ b/cmd/cloudServerDetails.go @@ -23,6 +23,7 @@ import ( "github.com/liquidweb/liquidweb-cli/instance" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var blockStorageVolumeList apiTypes.MergedPaginatedList @@ -38,10 +39,16 @@ You can check this methods API documentation for what the returned fields mean: https://cart.liquidweb.com/storm/api/docs/bleed/Storm/Server.html#method_details `, Run: func(cmd *cobra.Command, args []string) { - uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + var details apiTypes.CloudServerDetails if err := lwCliInst.CallLwApiInto("bleed/storm/server/details", map[string]interface{}{"uniq_id": uniqIdFlag}, &details); err != nil { diff --git a/cmd/cloudServerReboot.go b/cmd/cloudServerReboot.go index 270c5c9..d250768 100644 --- a/cmd/cloudServerReboot.go +++ b/cmd/cloudServerReboot.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerRebootCmd = &cobra.Command{ @@ -34,6 +35,13 @@ To perform a forced a reboot, you must use --force`, jsonOutput, _ := cmd.Flags().GetBool("json") force, _ := cmd.Flags().GetBool("force") + validateFields := map[string]interface{}{ + "UniqId": uniqId, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + var resp apiTypes.CloudServerRebootResponse if err := lwCliInst.CallLwApiInto("bleed/storm/server/reboot", map[string]interface{}{ "uniq_id": uniqId, "force": force}, &resp); err != nil { diff --git a/cmd/cloudServerResize.go b/cmd/cloudServerResize.go index b8d4393..4479954 100644 --- a/cmd/cloudServerResize.go +++ b/cmd/cloudServerResize.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerResizeCmd = &cobra.Command{ @@ -75,6 +76,13 @@ During all resizes, the Cloud Server is online as the disk synchronizes. vcpuFlag, _ := cmd.Flags().GetInt64("vcpu") privateParentFlag, _ := cmd.Flags().GetString("private-parent") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + // convert bool to int for api skipFsResizeInt := 0 if skipFsResizeFlag { diff --git a/cmd/cloudServerShutdown.go b/cmd/cloudServerShutdown.go index 76b07ea..60bc4ee 100644 --- a/cmd/cloudServerShutdown.go +++ b/cmd/cloudServerShutdown.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerShutdownCmd = &cobra.Command{ @@ -34,6 +35,13 @@ will issue a halt command to the server and shutdown normally.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + shutdownArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, } diff --git a/cmd/cloudServerStart.go b/cmd/cloudServerStart.go index 6746872..c145436 100644 --- a/cmd/cloudServerStart.go +++ b/cmd/cloudServerStart.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerStartCmd = &cobra.Command{ @@ -33,6 +34,13 @@ Boot a server. If the server is already running, this will do nothing.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + startArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, } diff --git a/cmd/cloudServerStatus.go b/cmd/cloudServerStatus.go index 1f0b621..cea67f0 100644 --- a/cmd/cloudServerStatus.go +++ b/cmd/cloudServerStatus.go @@ -24,6 +24,7 @@ import ( "github.com/liquidweb/liquidweb-cli/instance" "github.com/liquidweb/liquidweb-cli/types/api" "github.com/liquidweb/liquidweb-cli/utils" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerStatusCmdUniqIdFlag []string @@ -88,6 +89,13 @@ If nothing is currently running, only the 'status' field will be returned with o } } else { for _, uid := range cloudServerStatusCmdUniqIdFlag { + validateFields := map[string]interface{}{ + "UniqId": uid, + } + if err := validate.Validate(validateFields); err != nil { + fmt.Printf("flag validation failure: %s ... skipping", err) + continue + } _printCloudServerStatus(uid, "") } } diff --git a/cmd/cloudServerUpdate.go b/cmd/cloudServerUpdate.go index 738e34d..f613fa4 100644 --- a/cmd/cloudServerUpdate.go +++ b/cmd/cloudServerUpdate.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudServerUpdateCmd = &cobra.Command{ @@ -42,6 +43,13 @@ as-you-go, usage-based bandwidth charges.`, bandwidthQuotaFlag, _ := cmd.Flags().GetInt64("bandwidth-quota") backupQuotaFlag, _ := cmd.Flags().GetInt64("backup-quota") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + if backupPlanFlag == "Quota" { if backupQuotaFlag == -1 { lwCliInst.Die(fmt.Errorf("cannot enable Quota backups without --backup-quota")) diff --git a/cmd/cloudStorageBlockVolumeAttach.go b/cmd/cloudStorageBlockVolumeAttach.go index 1ecbdc5..7c6935a 100644 --- a/cmd/cloudStorageBlockVolumeAttach.go +++ b/cmd/cloudStorageBlockVolumeAttach.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageBlockVolumeAttachCmd = &cobra.Command{ @@ -35,6 +36,19 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") attachToFlag, _ := cmd.Flags().GetString("attach-to") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + validateFields = map[string]interface{}{ + "UniqId": attachToFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, "to": attachToFlag, diff --git a/cmd/cloudStorageBlockVolumeCreate.go b/cmd/cloudStorageBlockVolumeCreate.go index 10d8f7d..dc245e1 100644 --- a/cmd/cloudStorageBlockVolumeCreate.go +++ b/cmd/cloudStorageBlockVolumeCreate.go @@ -22,6 +22,7 @@ import ( "github.com/liquidweb/liquidweb-cli/types/api" "github.com/liquidweb/liquidweb-cli/utils" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageBlockVolumeCreateCmd = &cobra.Command{ @@ -40,6 +41,13 @@ Once attached, volumes appear as normal block devices, and can be used as such. crossAttachFlag, _ := cmd.Flags().GetBool("cross-attach") attachFlag, _ := cmd.Flags().GetString("attach") + validateFields := map[string]interface{}{ + "PositiveInt64": sizeFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "domain": nameFlag, "size": sizeFlag, diff --git a/cmd/cloudStorageBlockVolumeDelete.go b/cmd/cloudStorageBlockVolumeDelete.go index 63b2ac6..30224e6 100644 --- a/cmd/cloudStorageBlockVolumeDelete.go +++ b/cmd/cloudStorageBlockVolumeDelete.go @@ -18,8 +18,10 @@ package cmd import ( "fmt" - "github.com/liquidweb/liquidweb-cli/types/api" "github.com/spf13/cobra" + + "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageBlockVolumeDeleteCmd = &cobra.Command{ @@ -33,6 +35,13 @@ Once attached, volumes appear as normal block devices, and can be used as such. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} var details apiTypes.CloudBlockStorageVolumeDelete err := lwCliInst.CallLwApiInto("bleed/storage/block/volume/delete", apiArgs, &details) diff --git a/cmd/cloudStorageBlockVolumeDetach.go b/cmd/cloudStorageBlockVolumeDetach.go index 0f92f83..212c253 100644 --- a/cmd/cloudStorageBlockVolumeDetach.go +++ b/cmd/cloudStorageBlockVolumeDetach.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageBlockVolumeDetachCmd = &cobra.Command{ @@ -35,6 +36,19 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") detachFromFlag, _ := cmd.Flags().GetString("detach-from") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + validateFields = map[string]interface{}{ + "UniqId": detachFromFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, "detach_from": detachFromFlag, diff --git a/cmd/cloudStorageBlockVolumeDetails.go b/cmd/cloudStorageBlockVolumeDetails.go index d3e6afe..dda3c87 100644 --- a/cmd/cloudStorageBlockVolumeDetails.go +++ b/cmd/cloudStorageBlockVolumeDetails.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageBlockVolumeDetailsCmd = &cobra.Command{ @@ -35,6 +36,13 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} var details apiTypes.CloudBlockStorageVolumeDetails diff --git a/cmd/cloudStorageBlockVolumeResize.go b/cmd/cloudStorageBlockVolumeResize.go index f409eef..b49ddec 100644 --- a/cmd/cloudStorageBlockVolumeResize.go +++ b/cmd/cloudStorageBlockVolumeResize.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageBlockVolumeResizeCmd = &cobra.Command{ @@ -35,6 +36,14 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") newSizeFlag, _ := cmd.Flags().GetInt64("new-size") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + "PositiveInt64": newSizeFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, "new_size": newSizeFlag, diff --git a/cmd/cloudStorageBlockVolumeUpdate.go b/cmd/cloudStorageBlockVolumeUpdate.go index 534ce0f..ac01248 100644 --- a/cmd/cloudStorageBlockVolumeUpdate.go +++ b/cmd/cloudStorageBlockVolumeUpdate.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageBlockVolumeUpdateCmd = &cobra.Command{ @@ -37,8 +38,15 @@ Once attached, volumes appear as normal block devices, and can be used as such. enableCrossAttachFlag, _ := cmd.Flags().GetBool("enable-cross-attach") disableCrossAttachFlag, _ := cmd.Flags().GetBool("disable-cross-attach") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + if enableCrossAttachFlag && disableCrossAttachFlag { - lwCliInst.Die(fmt.Errorf("cant both enable and disab")) + lwCliInst.Die(fmt.Errorf("cant both enable and disable")) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudStorageObjectCreateKey.go b/cmd/cloudStorageObjectCreateKey.go index 7b8b33d..939ca20 100644 --- a/cmd/cloudStorageObjectCreateKey.go +++ b/cmd/cloudStorageObjectCreateKey.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageObjectCreateKeyCmd = &cobra.Command{ @@ -30,6 +31,13 @@ var cloudStorageObjectCreateKeyCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, } diff --git a/cmd/cloudStorageObjectDelete.go b/cmd/cloudStorageObjectDelete.go index d7a0f4c..01e3c8e 100644 --- a/cmd/cloudStorageObjectDelete.go +++ b/cmd/cloudStorageObjectDelete.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageObjectDeleteCmd = &cobra.Command{ @@ -30,6 +31,13 @@ var cloudStorageObjectDeleteCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} var details apiTypes.CloudObjectStoreDelete diff --git a/cmd/cloudStorageObjectDeleteKey.go b/cmd/cloudStorageObjectDeleteKey.go index b610a9c..c3f3fde 100644 --- a/cmd/cloudStorageObjectDeleteKey.go +++ b/cmd/cloudStorageObjectDeleteKey.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageObjectDeleteKeyCmd = &cobra.Command{ @@ -31,6 +32,13 @@ var cloudStorageObjectDeleteKeyCmd = &cobra.Command{ uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") accessKeyFlag, _ := cmd.Flags().GetString("access-key") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, "access_key": accessKeyFlag, diff --git a/cmd/cloudStorageObjectDetails.go b/cmd/cloudStorageObjectDetails.go index b0f54ee..6785059 100644 --- a/cmd/cloudStorageObjectDetails.go +++ b/cmd/cloudStorageObjectDetails.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var cloudStorageObjectDetailsCmd = &cobra.Command{ @@ -30,6 +31,13 @@ var cloudStorageObjectDetailsCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + var details apiTypes.CloudObjectStoreDetails apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, diff --git a/cmd/networkIpPoolDelete.go b/cmd/networkIpPoolDelete.go index 7dc53d9..e2b7661 100644 --- a/cmd/networkIpPoolDelete.go +++ b/cmd/networkIpPoolDelete.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var networkIpPoolDeleteCmd = &cobra.Command{ @@ -33,6 +34,13 @@ your account.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, } diff --git a/cmd/networkIpPoolDetails.go b/cmd/networkIpPoolDetails.go index f0127ab..f97b960 100644 --- a/cmd/networkIpPoolDetails.go +++ b/cmd/networkIpPoolDetails.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var networkIpPoolDetailsCmd = &cobra.Command{ @@ -34,6 +35,13 @@ your account.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") freeOnlyFlag, _ := cmd.Flags().GetBool("free-only") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + apiArgs := map[string]interface{}{ "uniq_id": uniqIdFlag, "free_only": freeOnlyFlag, diff --git a/cmd/networkIpPoolUpdate.go b/cmd/networkIpPoolUpdate.go index 4b980e6..1ab3e81 100644 --- a/cmd/networkIpPoolUpdate.go +++ b/cmd/networkIpPoolUpdate.go @@ -21,6 +21,7 @@ import ( "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/validate" ) var networkIpPoolUpdateCmdAddIpsFlag []string @@ -37,6 +38,13 @@ your account.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") newIpsFlag, _ := cmd.Flags().GetInt64("new-ips") + validateFields := map[string]interface{}{ + "UniqId": uniqIdFlag, + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + } + if len(networkIpPoolUpdateCmdAddIpsFlag) == 0 && len(networkIpPoolUpdateCmdRemoveIpsFlag) == 0 && newIpsFlag == -1 { lwCliInst.Die(fmt.Errorf( diff --git a/utils/utils.go b/utils/utils.go index 1655f66..626df92 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -18,10 +18,19 @@ package utils import ( "fmt" "math/rand" + "net" "os" "time" ) +func IpIsValid(ip string) bool { + if err := net.ParseIP(ip); err == nil { + return true + } + + return false +} + func RandomString(length int) string { charset := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + "0123456789" var seededRand *rand.Rand = rand.New( diff --git a/validate/types.go b/validate/types.go new file mode 100644 index 0000000..f6bd06d --- /dev/null +++ b/validate/types.go @@ -0,0 +1,107 @@ +/* +Copyright © LiquidWeb + +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 validate + +import ( + "fmt" + "strings" + + "github.com/liquidweb/liquidweb-cli/utils" +) + +type InputTypes struct { + UniqId InputTypeUniqId + IP InputTypeIP + PositiveInt64 InputTypePositiveInt64 + PostiveInt InputTypePositiveInt + NonEmptyString InputTypeNonEmptyString +} + +// UniqId + +type InputTypeUniqId struct { + UniqId string +} + +func (x InputTypeUniqId) Validate() error { + allUpper := strings.ToUpper(x.UniqId) + if allUpper != x.UniqId { + return fmt.Errorf("a uniq_id must be uppercase") + } + + if len(x.UniqId) != 6 { + return fmt.Errorf("a uniq_id must be 6 characters long") + } + + return nil +} + +// IP + +type InputTypeIP struct { + IP string +} + +func (x InputTypeIP) Validate() error { + + if !utils.IpIsValid(x.IP) { + return fmt.Errorf("ip [%s] is not a valid IP address", x.IP) + } + + return nil +} + +// PositiveInt64 + +type InputTypePositiveInt64 struct { + PositiveInt64 int64 +} + +func (x InputTypePositiveInt64) Validate() error { + if x.PositiveInt64 < 0 { + return fmt.Errorf("PositiveInt64 is not > 0") + } + + return nil +} + +// PositiveInt + +type InputTypePositiveInt struct { + PositiveInt int +} + +func (x InputTypePositiveInt) Validate() error { + if x.PositiveInt < 0 { + return fmt.Errorf("PositiveInt is not > 0") + } + + return nil +} + +// NonEmptyString + +type InputTypeNonEmptyString struct { + NonEmptyString string +} + +func (x InputTypeNonEmptyString) Validate() error { + if x.NonEmptyString == "" { + return fmt.Errorf("NonEmptyString cannot be empty") + } + + return nil +} diff --git a/validate/validate.go b/validate/validate.go new file mode 100644 index 0000000..0729719 --- /dev/null +++ b/validate/validate.go @@ -0,0 +1,148 @@ +/* +Copyright © LiquidWeb + +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 validate + +import ( + "fmt" + "reflect" + + "github.com/spf13/cast" +) + +func Validate(chk map[string]interface{}) error { + + for inputField, inputFieldValue := range chk { + // inputField must be defined + defined, shouldBeType, fieldVal := inputTypeDefined(inputField) + if !defined { + return fmt.Errorf("input field [%s] is not defined", inputField) + } + + // inputFieldValue must be of the correct type + reflectValue := reflect.TypeOf(inputFieldValue).Name() + if reflectValue != shouldBeType { + return fmt.Errorf("input field [%s] has an invalid type of [%s] wanted [%s]", + inputField, reflectValue, shouldBeType) + } + + // if there's a Validate method call it + iface := fieldVal.Interface() + if interfaceHasMethod(iface, "Validate") { + if err := interfaceInputTypeValidate(iface, inputFieldValue); err != nil { + return err + } + } + } + + return nil +} + +func interfaceInputTypeValidate(iface, inputFieldValue interface{}) error { + switch iface.(type) { + case InputTypeUniqId: + var obj InputTypeUniqId + obj.UniqId = cast.ToString(inputFieldValue) + if err := obj.Validate(); err != nil { + return err + } + case InputTypeIP: + var obj InputTypeIP + obj.IP = cast.ToString(inputFieldValue) + if err := obj.Validate(); err != nil { + return err + } + case InputTypePositiveInt64: + var obj InputTypePositiveInt64 + obj.PositiveInt64 = cast.ToInt64(inputFieldValue) + if err := obj.Validate(); err != nil { + return err + } + case InputTypePositiveInt: + var obj InputTypePositiveInt + obj.PositiveInt = cast.ToInt(inputFieldValue) + if err := obj.Validate(); err != nil { + return err + } + case InputTypeNonEmptyString: + var obj InputTypeNonEmptyString + obj.NonEmptyString = cast.ToString(inputFieldValue) + if err := obj.Validate(); err != nil { + return err + } + default: + return fmt.Errorf("bug: validation missing entry for %s", inputFieldValue) + } + + return nil +} + +func interfaceHasMethod(iface interface{}, methodName string) bool { + ifaceVal := reflect.ValueOf(iface) + + if !ifaceVal.IsValid() { + // not valid, so we already know its false + return false + } + + if ifaceVal.Type().Kind() != reflect.Ptr { + ifaceVal = reflect.New(reflect.TypeOf(iface)) + } + + method := ifaceVal.MethodByName(methodName) + + if method.IsValid() { + return true + } + + return false +} + +func inputTypeDefined(inputType string) (bool, string, reflect.Value) { + var validTypes InputTypes + + err, fieldType, fieldVal := structHasField(validTypes, inputType) + if err != nil { + return false, fieldType, fieldVal + } + + return true, fieldType, fieldVal +} + +func structHasField(data interface{}, fieldName string) (error, string, reflect.Value) { + dataVal := reflect.ValueOf(data) + + if !dataVal.IsValid() { + return fmt.Errorf("failed fetching value for fieldName [%s]", fieldName), "", + reflect.Value{} + } + + if dataVal.Type().Kind() != reflect.Ptr { + dataVal = reflect.New(reflect.TypeOf(data)) + } + + fieldVal := dataVal.Elem().FieldByName(fieldName) + if !fieldVal.IsValid() { + return fmt.Errorf("[%s] has no field [%s]", dataVal.Type(), fieldName), "", fieldVal + } + + fieldValKindStr := fieldVal.Kind().String() + + if fieldValKindStr == "struct" { + fieldValKindStr = fieldVal.Field(0).Kind().String() + } + + return nil, fieldValKindStr, fieldVal +} From dc8c2cb080d222cc55a40fec51371cbc33050b26 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 10:08:25 -0500 Subject: [PATCH 02/28] generic validation v2 --- cmd/cloudBackupList.go | 6 +++--- cmd/cloudBackupRestore.go | 8 ++++---- cmd/cloudImageCreate.go | 6 +++--- cmd/cloudImageRestore.go | 8 ++++---- cmd/cloudNetworkPrivateAttach.go | 6 +++--- cmd/cloudNetworkPrivateDetach.go | 6 +++--- cmd/cloudNetworkPrivateDetails.go | 6 +++--- cmd/cloudNetworkPublicAdd.go | 6 +++--- cmd/cloudNetworkPublicRemove.go | 12 ++++++------ cmd/cloudNetworkVipCreate.go | 6 +++--- cmd/cloudNetworkVipDelete.go | 6 +++--- cmd/cloudNetworkVipDetails.go | 6 +++--- cmd/cloudPrivateParentCreate.go | 13 ++++--------- cmd/cloudPrivateParentRename.go | 8 ++++---- cmd/cloudServerBlockStorageOptimizedCheck.go | 6 +++--- cmd/cloudServerBlockStorageOptimizedDisable.go | 6 +++--- cmd/cloudServerBlockStorageOptimizedEnable.go | 6 +++--- cmd/cloudServerClone.go | 6 +++--- cmd/cloudServerCreate.go | 6 +++--- cmd/cloudServerDestroy.go | 6 +++--- cmd/cloudServerDetails.go | 6 +++--- cmd/cloudServerReboot.go | 6 +++--- cmd/cloudServerResize.go | 6 +++--- cmd/cloudServerShutdown.go | 6 +++--- cmd/cloudServerStart.go | 6 +++--- cmd/cloudServerStatus.go | 6 +++--- cmd/cloudServerUpdate.go | 6 +++--- cmd/cloudStorageBlockVolumeAttach.go | 13 ++++--------- cmd/cloudStorageBlockVolumeCreate.go | 6 +++--- cmd/cloudStorageBlockVolumeDelete.go | 6 +++--- cmd/cloudStorageBlockVolumeDetach.go | 13 ++++--------- cmd/cloudStorageBlockVolumeDetails.go | 6 +++--- cmd/cloudStorageBlockVolumeResize.go | 8 ++++---- cmd/cloudStorageBlockVolumeUpdate.go | 6 +++--- cmd/cloudStorageObjectCreateKey.go | 6 +++--- cmd/cloudStorageObjectDelete.go | 6 +++--- cmd/cloudStorageObjectDeleteKey.go | 6 +++--- cmd/cloudStorageObjectDetails.go | 6 +++--- cmd/networkIpPoolDelete.go | 6 +++--- cmd/networkIpPoolDetails.go | 6 +++--- cmd/networkIpPoolUpdate.go | 6 +++--- utils/utils.go | 6 +++--- validate/types.go | 16 ++++++++++++++++ validate/validate.go | 12 ++++++------ 44 files changed, 158 insertions(+), 157 deletions(-) diff --git a/cmd/cloudBackupList.go b/cmd/cloudBackupList.go index 702849b..da5c3c8 100644 --- a/cmd/cloudBackupList.go +++ b/cmd/cloudBackupList.go @@ -35,12 +35,12 @@ var cloudBackupListCmd = &cobra.Command{ uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") if uniqIdFlag != "" { - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } } diff --git a/cmd/cloudBackupRestore.go b/cmd/cloudBackupRestore.go index 0621669..16e2cfe 100644 --- a/cmd/cloudBackupRestore.go +++ b/cmd/cloudBackupRestore.go @@ -33,12 +33,12 @@ var cloudBackupRestoreCmd = &cobra.Command{ rebuildFsFlag, _ := cmd.Flags().GetBool("rebuild-fs") backupIdFlag, _ := cmd.Flags().GetInt64("backup_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, - "PositiveInt64": backupIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", + backupIdFlag: "PositiveInt64", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"id": backupIdFlag, "uniq_id": uniqIdFlag} diff --git a/cmd/cloudImageCreate.go b/cmd/cloudImageCreate.go index 418dda4..50e7fa7 100644 --- a/cmd/cloudImageCreate.go +++ b/cmd/cloudImageCreate.go @@ -32,11 +32,11 @@ var cloudImageCreateCmd = &cobra.Command{ uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") nameFlag, _ := cmd.Flags().GetString("name") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"name": nameFlag, "uniq_id": uniqIdFlag} diff --git a/cmd/cloudImageRestore.go b/cmd/cloudImageRestore.go index e502e9d..b3a61b9 100644 --- a/cmd/cloudImageRestore.go +++ b/cmd/cloudImageRestore.go @@ -33,12 +33,12 @@ var cloudImageRestoreCmd = &cobra.Command{ rebuildFsFlag, _ := cmd.Flags().GetBool("rebuild-fs") imageIdFlag, _ := cmd.Flags().GetInt64("image_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, - "PositiveInt64": imageIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", + imageIdFlag: "PositiveInt64", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"id": imageIdFlag, "uniq_id": uniqIdFlag} diff --git a/cmd/cloudNetworkPrivateAttach.go b/cmd/cloudNetworkPrivateAttach.go index 9238a8a..8a54a35 100644 --- a/cmd/cloudNetworkPrivateAttach.go +++ b/cmd/cloudNetworkPrivateAttach.go @@ -41,11 +41,11 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} diff --git a/cmd/cloudNetworkPrivateDetach.go b/cmd/cloudNetworkPrivateDetach.go index 6dd60e5..233adc8 100644 --- a/cmd/cloudNetworkPrivateDetach.go +++ b/cmd/cloudNetworkPrivateDetach.go @@ -41,11 +41,11 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} diff --git a/cmd/cloudNetworkPrivateDetails.go b/cmd/cloudNetworkPrivateDetails.go index 8cbd51d..f744331 100644 --- a/cmd/cloudNetworkPrivateDetails.go +++ b/cmd/cloudNetworkPrivateDetails.go @@ -41,11 +41,11 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} diff --git a/cmd/cloudNetworkPublicAdd.go b/cmd/cloudNetworkPublicAdd.go index b4360d4..111cefa 100644 --- a/cmd/cloudNetworkPublicAdd.go +++ b/cmd/cloudNetworkPublicAdd.go @@ -42,11 +42,11 @@ will be up to the administrator to configure the IP address(es) within the serve rebootFlag, _ := cmd.Flags().GetBool("reboot") newIpsFlag, _ := cmd.Flags().GetInt64("new-ips") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } if newIpsFlag == 0 && len(cloudNetworkPublicAddCmdPoolIpsFlag) == 0 { diff --git a/cmd/cloudNetworkPublicRemove.go b/cmd/cloudNetworkPublicRemove.go index dcf1b1e..f10059b 100644 --- a/cmd/cloudNetworkPublicRemove.go +++ b/cmd/cloudNetworkPublicRemove.go @@ -44,11 +44,11 @@ Note that you cannot remove the Cloud Servers primary ip with this command.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") rebootFlag, _ := cmd.Flags().GetBool("reboot") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ @@ -57,11 +57,11 @@ Note that you cannot remove the Cloud Servers primary ip with this command.`, } for _, ip := range cloudNetworkPublicRemoveCmdIpsFlag { - validateFields := map[string]interface{}{ - "IP": ip, + validateFields := map[interface{}]string{ + ip: "IP", } if err := validate.Validate(validateFields); err != nil { - fmt.Printf("passed ip failed validation: %s ... skipping\n", err) + fmt.Printf("%s ... skipping\n", err) } var details apiTypes.NetworkIpRemove diff --git a/cmd/cloudNetworkVipCreate.go b/cmd/cloudNetworkVipCreate.go index b7853e2..ed01e18 100644 --- a/cmd/cloudNetworkVipCreate.go +++ b/cmd/cloudNetworkVipCreate.go @@ -65,11 +65,11 @@ Heartbeat nameFlag, _ := cmd.Flags().GetString("name") zoneFlag, _ := cmd.Flags().GetInt64("zone") - validateFields := map[string]interface{}{ - "PositiveInt64": zoneFlag, + validateFields := map[interface{}]string{ + zoneFlag: "PositiveInt64", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudNetworkVipDelete.go b/cmd/cloudNetworkVipDelete.go index 1216a6e..f79d3fd 100644 --- a/cmd/cloudNetworkVipDelete.go +++ b/cmd/cloudNetworkVipDelete.go @@ -63,11 +63,11 @@ Heartbeat Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudNetworkVipDetails.go b/cmd/cloudNetworkVipDetails.go index 1ded628..6312ba2 100644 --- a/cmd/cloudNetworkVipDetails.go +++ b/cmd/cloudNetworkVipDetails.go @@ -63,11 +63,11 @@ Heartbeat Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudPrivateParentCreate.go b/cmd/cloudPrivateParentCreate.go index bcf0ee1..eff16e8 100644 --- a/cmd/cloudPrivateParentCreate.go +++ b/cmd/cloudPrivateParentCreate.go @@ -41,17 +41,12 @@ of configs, check 'cloud server options --configs'.`, configIdFlag, _ := cmd.Flags().GetInt64("config_id") zoneFlag, _ := cmd.Flags().GetInt64("zone") - validateFields := map[string]interface{}{ - "PositiveInt64": zoneFlag, + validateFields := map[interface{}]string{ + zoneFlag: "PositiveInt64", + configIdFlag: "PositiveInt64", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) - } - validateFields = map[string]interface{}{ - "PositiveInt64": configIdFlag, - } - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudPrivateParentRename.go b/cmd/cloudPrivateParentRename.go index 89e6cf4..0729fb5 100644 --- a/cmd/cloudPrivateParentRename.go +++ b/cmd/cloudPrivateParentRename.go @@ -37,12 +37,12 @@ as well as how many resources each Cloud Server gets.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") nameFlag, _ := cmd.Flags().GetString("name") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, - "NonEmptyString": nameFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", + nameFlag: "NonEmptyString", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudServerBlockStorageOptimizedCheck.go b/cmd/cloudServerBlockStorageOptimizedCheck.go index f71e7ba..b90d9cc 100644 --- a/cmd/cloudServerBlockStorageOptimizedCheck.go +++ b/cmd/cloudServerBlockStorageOptimizedCheck.go @@ -38,11 +38,11 @@ Storage Optimized.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudServerBlockStorageOptimizedDisable.go b/cmd/cloudServerBlockStorageOptimizedDisable.go index 31b42c1..d6e3a62 100644 --- a/cmd/cloudServerBlockStorageOptimizedDisable.go +++ b/cmd/cloudServerBlockStorageOptimizedDisable.go @@ -40,11 +40,11 @@ Disabling Cloud Block Storage will cause your Cloud Server to reboot.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } var optimized apiTypes.CloudServerIsBlockStorageOptimized diff --git a/cmd/cloudServerBlockStorageOptimizedEnable.go b/cmd/cloudServerBlockStorageOptimizedEnable.go index 5f40945..afde397 100644 --- a/cmd/cloudServerBlockStorageOptimizedEnable.go +++ b/cmd/cloudServerBlockStorageOptimizedEnable.go @@ -40,11 +40,11 @@ Enabling Cloud Block Storage will cause your Cloud Server to reboot.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } var optimized apiTypes.CloudServerIsBlockStorageOptimized diff --git a/cmd/cloudServerClone.go b/cmd/cloudServerClone.go index 3755308..672039c 100644 --- a/cmd/cloudServerClone.go +++ b/cmd/cloudServerClone.go @@ -58,11 +58,11 @@ Server is not on a Private Parent.`, vcpuFlag, _ := cmd.Flags().GetInt64("vcpu") configIdFlag, _ := cmd.Flags().GetInt64("config_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } if privateParentFlag != "" && configIdFlag != -1 { diff --git a/cmd/cloudServerCreate.go b/cmd/cloudServerCreate.go index 7da08fb..a50ecd7 100644 --- a/cmd/cloudServerCreate.go +++ b/cmd/cloudServerCreate.go @@ -79,11 +79,11 @@ For a list of backups, see 'cloud inventory backups list' backupIdFlag, _ := cmd.Flags().GetInt("backup-id") imageIdFlag, _ := cmd.Flags().GetInt("image-id") - validateFields := map[string]interface{}{ - "PositiveInt": zoneFlag, + validateFields := map[interface{}]string{ + zoneFlag: "PositiveInt", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } // sanity check flags diff --git a/cmd/cloudServerDestroy.go b/cmd/cloudServerDestroy.go index e8f6a7f..e9f2122 100644 --- a/cmd/cloudServerDestroy.go +++ b/cmd/cloudServerDestroy.go @@ -40,12 +40,12 @@ server.`, for _, uniqId := range cloudServerDestroyCmdUniqIdFlag { - validateFields := map[string]interface{}{ - "UniqId": uniqId, + validateFields := map[interface{}]string{ + uniqId: "UniqId", } if err := validate.Validate(validateFields); err != nil { - fmt.Printf("passed uniq_id [%s] failed validation: %s ... skipping\n", uniqId, err) + fmt.Printf("%s ... skipping\n", err) continue } diff --git a/cmd/cloudServerDetails.go b/cmd/cloudServerDetails.go index 519ad8e..c4579d9 100644 --- a/cmd/cloudServerDetails.go +++ b/cmd/cloudServerDetails.go @@ -42,11 +42,11 @@ https://cart.liquidweb.com/storm/api/docs/bleed/Storm/Server.html#method_details uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } var details apiTypes.CloudServerDetails diff --git a/cmd/cloudServerReboot.go b/cmd/cloudServerReboot.go index d250768..0196f45 100644 --- a/cmd/cloudServerReboot.go +++ b/cmd/cloudServerReboot.go @@ -35,11 +35,11 @@ To perform a forced a reboot, you must use --force`, jsonOutput, _ := cmd.Flags().GetBool("json") force, _ := cmd.Flags().GetBool("force") - validateFields := map[string]interface{}{ - "UniqId": uniqId, + validateFields := map[interface{}]string{ + uniqId: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } var resp apiTypes.CloudServerRebootResponse diff --git a/cmd/cloudServerResize.go b/cmd/cloudServerResize.go index 4479954..3317625 100644 --- a/cmd/cloudServerResize.go +++ b/cmd/cloudServerResize.go @@ -76,11 +76,11 @@ During all resizes, the Cloud Server is online as the disk synchronizes. vcpuFlag, _ := cmd.Flags().GetInt64("vcpu") privateParentFlag, _ := cmd.Flags().GetString("private-parent") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } // convert bool to int for api diff --git a/cmd/cloudServerShutdown.go b/cmd/cloudServerShutdown.go index 60bc4ee..1286e4f 100644 --- a/cmd/cloudServerShutdown.go +++ b/cmd/cloudServerShutdown.go @@ -35,11 +35,11 @@ will issue a halt command to the server and shutdown normally.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } shutdownArgs := map[string]interface{}{ diff --git a/cmd/cloudServerStart.go b/cmd/cloudServerStart.go index c145436..51f2d3f 100644 --- a/cmd/cloudServerStart.go +++ b/cmd/cloudServerStart.go @@ -34,11 +34,11 @@ Boot a server. If the server is already running, this will do nothing.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } startArgs := map[string]interface{}{ diff --git a/cmd/cloudServerStatus.go b/cmd/cloudServerStatus.go index cea67f0..1933db6 100644 --- a/cmd/cloudServerStatus.go +++ b/cmd/cloudServerStatus.go @@ -89,11 +89,11 @@ If nothing is currently running, only the 'status' field will be returned with o } } else { for _, uid := range cloudServerStatusCmdUniqIdFlag { - validateFields := map[string]interface{}{ - "UniqId": uid, + validateFields := map[interface{}]string{ + uid: "UniqId", } if err := validate.Validate(validateFields); err != nil { - fmt.Printf("flag validation failure: %s ... skipping", err) + fmt.Printf("%s ... skipping\n", err) continue } _printCloudServerStatus(uid, "") diff --git a/cmd/cloudServerUpdate.go b/cmd/cloudServerUpdate.go index f613fa4..d3d2f2a 100644 --- a/cmd/cloudServerUpdate.go +++ b/cmd/cloudServerUpdate.go @@ -43,11 +43,11 @@ as-you-go, usage-based bandwidth charges.`, bandwidthQuotaFlag, _ := cmd.Flags().GetInt64("bandwidth-quota") backupQuotaFlag, _ := cmd.Flags().GetInt64("backup-quota") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } if backupPlanFlag == "Quota" { diff --git a/cmd/cloudStorageBlockVolumeAttach.go b/cmd/cloudStorageBlockVolumeAttach.go index 7c6935a..918b50b 100644 --- a/cmd/cloudStorageBlockVolumeAttach.go +++ b/cmd/cloudStorageBlockVolumeAttach.go @@ -36,17 +36,12 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") attachToFlag, _ := cmd.Flags().GetString("attach-to") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", + attachToFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) - } - validateFields = map[string]interface{}{ - "UniqId": attachToFlag, - } - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudStorageBlockVolumeCreate.go b/cmd/cloudStorageBlockVolumeCreate.go index dc245e1..c9d5bb4 100644 --- a/cmd/cloudStorageBlockVolumeCreate.go +++ b/cmd/cloudStorageBlockVolumeCreate.go @@ -41,11 +41,11 @@ Once attached, volumes appear as normal block devices, and can be used as such. crossAttachFlag, _ := cmd.Flags().GetBool("cross-attach") attachFlag, _ := cmd.Flags().GetString("attach") - validateFields := map[string]interface{}{ - "PositiveInt64": sizeFlag, + validateFields := map[interface{}]string{ + sizeFlag: "PositiveInt64", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudStorageBlockVolumeDelete.go b/cmd/cloudStorageBlockVolumeDelete.go index 30224e6..fe718b6 100644 --- a/cmd/cloudStorageBlockVolumeDelete.go +++ b/cmd/cloudStorageBlockVolumeDelete.go @@ -35,11 +35,11 @@ Once attached, volumes appear as normal block devices, and can be used as such. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} diff --git a/cmd/cloudStorageBlockVolumeDetach.go b/cmd/cloudStorageBlockVolumeDetach.go index 212c253..3f2edcf 100644 --- a/cmd/cloudStorageBlockVolumeDetach.go +++ b/cmd/cloudStorageBlockVolumeDetach.go @@ -36,17 +36,12 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") detachFromFlag, _ := cmd.Flags().GetString("detach-from") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", + detachFromFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) - } - validateFields = map[string]interface{}{ - "UniqId": detachFromFlag, - } - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudStorageBlockVolumeDetails.go b/cmd/cloudStorageBlockVolumeDetails.go index dda3c87..0cadbb7 100644 --- a/cmd/cloudStorageBlockVolumeDetails.go +++ b/cmd/cloudStorageBlockVolumeDetails.go @@ -36,11 +36,11 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} diff --git a/cmd/cloudStorageBlockVolumeResize.go b/cmd/cloudStorageBlockVolumeResize.go index b49ddec..97aab3a 100644 --- a/cmd/cloudStorageBlockVolumeResize.go +++ b/cmd/cloudStorageBlockVolumeResize.go @@ -36,12 +36,12 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") newSizeFlag, _ := cmd.Flags().GetInt64("new-size") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, - "PositiveInt64": newSizeFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", + newSizeFlag: "PositiveInt64", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("flag validation failure: %s", err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudStorageBlockVolumeUpdate.go b/cmd/cloudStorageBlockVolumeUpdate.go index ac01248..34aaf34 100644 --- a/cmd/cloudStorageBlockVolumeUpdate.go +++ b/cmd/cloudStorageBlockVolumeUpdate.go @@ -38,11 +38,11 @@ Once attached, volumes appear as normal block devices, and can be used as such. enableCrossAttachFlag, _ := cmd.Flags().GetBool("enable-cross-attach") disableCrossAttachFlag, _ := cmd.Flags().GetBool("disable-cross-attach") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } if enableCrossAttachFlag && disableCrossAttachFlag { diff --git a/cmd/cloudStorageObjectCreateKey.go b/cmd/cloudStorageObjectCreateKey.go index 939ca20..a15d321 100644 --- a/cmd/cloudStorageObjectCreateKey.go +++ b/cmd/cloudStorageObjectCreateKey.go @@ -31,11 +31,11 @@ var cloudStorageObjectCreateKeyCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudStorageObjectDelete.go b/cmd/cloudStorageObjectDelete.go index 01e3c8e..c040bd3 100644 --- a/cmd/cloudStorageObjectDelete.go +++ b/cmd/cloudStorageObjectDelete.go @@ -31,11 +31,11 @@ var cloudStorageObjectDeleteCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{"uniq_id": uniqIdFlag} diff --git a/cmd/cloudStorageObjectDeleteKey.go b/cmd/cloudStorageObjectDeleteKey.go index c3f3fde..bf05f3c 100644 --- a/cmd/cloudStorageObjectDeleteKey.go +++ b/cmd/cloudStorageObjectDeleteKey.go @@ -32,11 +32,11 @@ var cloudStorageObjectDeleteKeyCmd = &cobra.Command{ uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") accessKeyFlag, _ := cmd.Flags().GetString("access-key") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/cloudStorageObjectDetails.go b/cmd/cloudStorageObjectDetails.go index 6785059..405ea72 100644 --- a/cmd/cloudStorageObjectDetails.go +++ b/cmd/cloudStorageObjectDetails.go @@ -31,11 +31,11 @@ var cloudStorageObjectDetailsCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } var details apiTypes.CloudObjectStoreDetails diff --git a/cmd/networkIpPoolDelete.go b/cmd/networkIpPoolDelete.go index e2b7661..7cfeb16 100644 --- a/cmd/networkIpPoolDelete.go +++ b/cmd/networkIpPoolDelete.go @@ -34,11 +34,11 @@ your account.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/networkIpPoolDetails.go b/cmd/networkIpPoolDetails.go index f97b960..e747827 100644 --- a/cmd/networkIpPoolDetails.go +++ b/cmd/networkIpPoolDetails.go @@ -35,11 +35,11 @@ your account.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") freeOnlyFlag, _ := cmd.Flags().GetBool("free-only") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } apiArgs := map[string]interface{}{ diff --git a/cmd/networkIpPoolUpdate.go b/cmd/networkIpPoolUpdate.go index 1ab3e81..ed035e5 100644 --- a/cmd/networkIpPoolUpdate.go +++ b/cmd/networkIpPoolUpdate.go @@ -38,11 +38,11 @@ your account.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") newIpsFlag, _ := cmd.Flags().GetInt64("new-ips") - validateFields := map[string]interface{}{ - "UniqId": uniqIdFlag, + validateFields := map[interface{}]string{ + uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(fmt.Errorf("passed uniq_id [%s] failed validation: %s", uniqIdFlag, err)) + lwCliInst.Die(err) } if len(networkIpPoolUpdateCmdAddIpsFlag) == 0 && len(networkIpPoolUpdateCmdRemoveIpsFlag) == 0 && diff --git a/utils/utils.go b/utils/utils.go index 626df92..d0fca3b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -24,11 +24,11 @@ import ( ) func IpIsValid(ip string) bool { - if err := net.ParseIP(ip); err == nil { - return true + if parsedIp := net.ParseIP(ip); parsedIp == nil { + return false } - return false + return true } func RandomString(length int) string { diff --git a/validate/types.go b/validate/types.go index f6bd06d..1edf1cf 100644 --- a/validate/types.go +++ b/validate/types.go @@ -16,12 +16,16 @@ limitations under the License. package validate import ( + "errors" "fmt" + "regexp" "strings" "github.com/liquidweb/liquidweb-cli/utils" ) +var ValidationFailure = errors.New("validation failed") + type InputTypes struct { UniqId InputTypeUniqId IP InputTypeIP @@ -37,15 +41,27 @@ type InputTypeUniqId struct { } func (x InputTypeUniqId) Validate() error { + // must be uppercase allUpper := strings.ToUpper(x.UniqId) if allUpper != x.UniqId { return fmt.Errorf("a uniq_id must be uppercase") } + // must be 6 characters if len(x.UniqId) != 6 { return fmt.Errorf("a uniq_id must be 6 characters long") } + // must be alphanumeric + reg, err := regexp.Compile("[^a-zA-Z0-9]+") + if err != nil { + return err + } + regexStr := reg.ReplaceAllString(x.UniqId, "") + if regexStr != x.UniqId { + return fmt.Errorf("a uniq_id must be alphanumeric") + } + return nil } diff --git a/validate/validate.go b/validate/validate.go index 0729719..9fe19d8 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -22,27 +22,27 @@ import ( "github.com/spf13/cast" ) -func Validate(chk map[string]interface{}) error { +func Validate(chk map[interface{}]string) error { - for inputField, inputFieldValue := range chk { + for inputFieldValue, inputField := range chk { // inputField must be defined defined, shouldBeType, fieldVal := inputTypeDefined(inputField) if !defined { - return fmt.Errorf("input field [%s] is not defined", inputField) + return fmt.Errorf("%w for input field [%+v] type [%s] is not valid", ValidationFailure, inputFieldValue, inputField) } // inputFieldValue must be of the correct type reflectValue := reflect.TypeOf(inputFieldValue).Name() if reflectValue != shouldBeType { - return fmt.Errorf("input field [%s] has an invalid type of [%s] wanted [%s]", - inputField, reflectValue, shouldBeType) + return fmt.Errorf("%w for input field [%+v] type [%s] has an invalid type of [%s] wanted [%s]", + ValidationFailure, inputFieldValue, inputField, reflectValue, shouldBeType) } // if there's a Validate method call it iface := fieldVal.Interface() if interfaceHasMethod(iface, "Validate") { if err := interfaceInputTypeValidate(iface, inputFieldValue); err != nil { - return err + return fmt.Errorf("%w for input field [%+v] %s", ValidationFailure, inputFieldValue, err) } } } From bc6c8ac746a5dbba43576b7ce90be1b066f2412e Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 12:30:37 -0500 Subject: [PATCH 03/28] generic validation v3 (allow optional validation) --- cmd/cloudBackupList.go | 2 +- cmd/cloudBackupRestore.go | 2 +- cmd/cloudImageCreate.go | 2 +- cmd/cloudImageRestore.go | 2 +- cmd/cloudNetworkPrivateAttach.go | 2 +- cmd/cloudNetworkPrivateDetach.go | 2 +- cmd/cloudNetworkPrivateDetails.go | 2 +- cmd/cloudNetworkPublicAdd.go | 2 +- cmd/cloudNetworkPublicRemove.go | 4 +-- cmd/cloudNetworkVipCreate.go | 2 +- cmd/cloudNetworkVipDelete.go | 2 +- cmd/cloudNetworkVipDetails.go | 2 +- cmd/cloudPrivateParentCreate.go | 2 +- cmd/cloudPrivateParentRename.go | 2 +- cmd/cloudServerBlockStorageOptimizedCheck.go | 2 +- ...cloudServerBlockStorageOptimizedDisable.go | 2 +- cmd/cloudServerBlockStorageOptimizedEnable.go | 2 +- cmd/cloudServerClone.go | 21 ++++++++--- cmd/cloudServerCreate.go | 2 +- cmd/cloudServerDestroy.go | 2 +- cmd/cloudServerDetails.go | 2 +- cmd/cloudServerReboot.go | 2 +- cmd/cloudServerResize.go | 2 +- cmd/cloudServerShutdown.go | 2 +- cmd/cloudServerStart.go | 2 +- cmd/cloudServerStatus.go | 2 +- cmd/cloudServerUpdate.go | 2 +- cmd/cloudStorageBlockVolumeAttach.go | 2 +- cmd/cloudStorageBlockVolumeCreate.go | 2 +- cmd/cloudStorageBlockVolumeDelete.go | 2 +- cmd/cloudStorageBlockVolumeDetach.go | 2 +- cmd/cloudStorageBlockVolumeDetails.go | 2 +- cmd/cloudStorageBlockVolumeResize.go | 2 +- cmd/cloudStorageBlockVolumeUpdate.go | 2 +- cmd/cloudStorageObjectCreateKey.go | 2 +- cmd/cloudStorageObjectDelete.go | 2 +- cmd/cloudStorageObjectDeleteKey.go | 5 +-- cmd/cloudStorageObjectDetails.go | 2 +- cmd/networkIpPoolDelete.go | 2 +- cmd/networkIpPoolDetails.go | 2 +- cmd/networkIpPoolUpdate.go | 16 ++++++--- validate/validate.go | 36 ++++++++++++++++--- 42 files changed, 101 insertions(+), 55 deletions(-) diff --git a/cmd/cloudBackupList.go b/cmd/cloudBackupList.go index da5c3c8..fce645c 100644 --- a/cmd/cloudBackupList.go +++ b/cmd/cloudBackupList.go @@ -35,7 +35,7 @@ var cloudBackupListCmd = &cobra.Command{ uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") if uniqIdFlag != "" { - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } diff --git a/cmd/cloudBackupRestore.go b/cmd/cloudBackupRestore.go index 16e2cfe..6d2cb7f 100644 --- a/cmd/cloudBackupRestore.go +++ b/cmd/cloudBackupRestore.go @@ -33,7 +33,7 @@ var cloudBackupRestoreCmd = &cobra.Command{ rebuildFsFlag, _ := cmd.Flags().GetBool("rebuild-fs") backupIdFlag, _ := cmd.Flags().GetInt64("backup_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", backupIdFlag: "PositiveInt64", } diff --git a/cmd/cloudImageCreate.go b/cmd/cloudImageCreate.go index 50e7fa7..a79767f 100644 --- a/cmd/cloudImageCreate.go +++ b/cmd/cloudImageCreate.go @@ -32,7 +32,7 @@ var cloudImageCreateCmd = &cobra.Command{ uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") nameFlag, _ := cmd.Flags().GetString("name") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudImageRestore.go b/cmd/cloudImageRestore.go index b3a61b9..ca78138 100644 --- a/cmd/cloudImageRestore.go +++ b/cmd/cloudImageRestore.go @@ -33,7 +33,7 @@ var cloudImageRestoreCmd = &cobra.Command{ rebuildFsFlag, _ := cmd.Flags().GetBool("rebuild-fs") imageIdFlag, _ := cmd.Flags().GetInt64("image_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", imageIdFlag: "PositiveInt64", } diff --git a/cmd/cloudNetworkPrivateAttach.go b/cmd/cloudNetworkPrivateAttach.go index 8a54a35..a3b7845 100644 --- a/cmd/cloudNetworkPrivateAttach.go +++ b/cmd/cloudNetworkPrivateAttach.go @@ -41,7 +41,7 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudNetworkPrivateDetach.go b/cmd/cloudNetworkPrivateDetach.go index 233adc8..1239221 100644 --- a/cmd/cloudNetworkPrivateDetach.go +++ b/cmd/cloudNetworkPrivateDetach.go @@ -41,7 +41,7 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudNetworkPrivateDetails.go b/cmd/cloudNetworkPrivateDetails.go index f744331..e205c31 100644 --- a/cmd/cloudNetworkPrivateDetails.go +++ b/cmd/cloudNetworkPrivateDetails.go @@ -41,7 +41,7 @@ and cost-savings. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudNetworkPublicAdd.go b/cmd/cloudNetworkPublicAdd.go index 111cefa..18e4ef1 100644 --- a/cmd/cloudNetworkPublicAdd.go +++ b/cmd/cloudNetworkPublicAdd.go @@ -42,7 +42,7 @@ will be up to the administrator to configure the IP address(es) within the serve rebootFlag, _ := cmd.Flags().GetBool("reboot") newIpsFlag, _ := cmd.Flags().GetInt64("new-ips") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudNetworkPublicRemove.go b/cmd/cloudNetworkPublicRemove.go index f10059b..a69433e 100644 --- a/cmd/cloudNetworkPublicRemove.go +++ b/cmd/cloudNetworkPublicRemove.go @@ -44,7 +44,7 @@ Note that you cannot remove the Cloud Servers primary ip with this command.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") rebootFlag, _ := cmd.Flags().GetBool("reboot") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { @@ -57,7 +57,7 @@ Note that you cannot remove the Cloud Servers primary ip with this command.`, } for _, ip := range cloudNetworkPublicRemoveCmdIpsFlag { - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ ip: "IP", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudNetworkVipCreate.go b/cmd/cloudNetworkVipCreate.go index ed01e18..db57cf5 100644 --- a/cmd/cloudNetworkVipCreate.go +++ b/cmd/cloudNetworkVipCreate.go @@ -65,7 +65,7 @@ Heartbeat nameFlag, _ := cmd.Flags().GetString("name") zoneFlag, _ := cmd.Flags().GetInt64("zone") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ zoneFlag: "PositiveInt64", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudNetworkVipDelete.go b/cmd/cloudNetworkVipDelete.go index f79d3fd..e3ef0a9 100644 --- a/cmd/cloudNetworkVipDelete.go +++ b/cmd/cloudNetworkVipDelete.go @@ -63,7 +63,7 @@ Heartbeat Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudNetworkVipDetails.go b/cmd/cloudNetworkVipDetails.go index 6312ba2..34c3d91 100644 --- a/cmd/cloudNetworkVipDetails.go +++ b/cmd/cloudNetworkVipDetails.go @@ -63,7 +63,7 @@ Heartbeat Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudPrivateParentCreate.go b/cmd/cloudPrivateParentCreate.go index eff16e8..b997070 100644 --- a/cmd/cloudPrivateParentCreate.go +++ b/cmd/cloudPrivateParentCreate.go @@ -41,7 +41,7 @@ of configs, check 'cloud server options --configs'.`, configIdFlag, _ := cmd.Flags().GetInt64("config_id") zoneFlag, _ := cmd.Flags().GetInt64("zone") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ zoneFlag: "PositiveInt64", configIdFlag: "PositiveInt64", } diff --git a/cmd/cloudPrivateParentRename.go b/cmd/cloudPrivateParentRename.go index 0729fb5..4991d11 100644 --- a/cmd/cloudPrivateParentRename.go +++ b/cmd/cloudPrivateParentRename.go @@ -37,7 +37,7 @@ as well as how many resources each Cloud Server gets.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") nameFlag, _ := cmd.Flags().GetString("name") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", nameFlag: "NonEmptyString", } diff --git a/cmd/cloudServerBlockStorageOptimizedCheck.go b/cmd/cloudServerBlockStorageOptimizedCheck.go index b90d9cc..ede35e0 100644 --- a/cmd/cloudServerBlockStorageOptimizedCheck.go +++ b/cmd/cloudServerBlockStorageOptimizedCheck.go @@ -38,7 +38,7 @@ Storage Optimized.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerBlockStorageOptimizedDisable.go b/cmd/cloudServerBlockStorageOptimizedDisable.go index d6e3a62..51a026f 100644 --- a/cmd/cloudServerBlockStorageOptimizedDisable.go +++ b/cmd/cloudServerBlockStorageOptimizedDisable.go @@ -40,7 +40,7 @@ Disabling Cloud Block Storage will cause your Cloud Server to reboot.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerBlockStorageOptimizedEnable.go b/cmd/cloudServerBlockStorageOptimizedEnable.go index afde397..7734ae4 100644 --- a/cmd/cloudServerBlockStorageOptimizedEnable.go +++ b/cmd/cloudServerBlockStorageOptimizedEnable.go @@ -40,7 +40,7 @@ Enabling Cloud Block Storage will cause your Cloud Server to reboot.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerClone.go b/cmd/cloudServerClone.go index 672039c..753ce61 100644 --- a/cmd/cloudServerClone.go +++ b/cmd/cloudServerClone.go @@ -58,11 +58,10 @@ Server is not on a Private Parent.`, vcpuFlag, _ := cmd.Flags().GetInt64("vcpu") configIdFlag, _ := cmd.Flags().GetInt64("config_id") - validateFields := map[interface{}]string{ - uniqIdFlag: "UniqId", - } - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(err) + validateFields := map[interface{}]interface{}{ + uniqIdFlag: "UniqId", + hostnameFlag: map[string]string{"type": "NonEmptyString", "optional": "true"}, + passwordFlag: map[string]string{"type": "NonEmptyString", "optional": "true"}, } if privateParentFlag != "" && configIdFlag != -1 { @@ -92,24 +91,36 @@ Server is not on a Private Parent.`, } if zoneFlag != -1 { cloneArgs["zone"] = zoneFlag + validateFields[zoneFlag] = "PositiveInt64" } if privateParentUniqId != "" { cloneArgs["parent"] = privateParentUniqId } if diskspaceFlag != -1 { cloneArgs["diskspace"] = diskspaceFlag + validateFields[diskspaceFlag] = "PositiveInt64" } if memoryFlag != -1 { cloneArgs["memory"] = memoryFlag + validateFields[memoryFlag] = "PositiveInt64" } if vcpuFlag != -1 { cloneArgs["vcpu"] = vcpuFlag + validateFields[vcpuFlag] = "PositiveInt64" } if configIdFlag != -1 { cloneArgs["config_id"] = configIdFlag + validateFields[configIdFlag] = "PositiveInt64" } if len(cloudServerCloneCmdPoolIpsFlag) > 0 { cloneArgs["pool_ips"] = cloudServerCloneCmdPoolIpsFlag + for _, ip := range cloudServerCloneCmdPoolIpsFlag { + validateFields[ip] = "IP" + } + } + + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(err) } var details apiTypes.CloudServerCloneResponse diff --git a/cmd/cloudServerCreate.go b/cmd/cloudServerCreate.go index a50ecd7..ab4fc0a 100644 --- a/cmd/cloudServerCreate.go +++ b/cmd/cloudServerCreate.go @@ -79,7 +79,7 @@ For a list of backups, see 'cloud inventory backups list' backupIdFlag, _ := cmd.Flags().GetInt("backup-id") imageIdFlag, _ := cmd.Flags().GetInt("image-id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ zoneFlag: "PositiveInt", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerDestroy.go b/cmd/cloudServerDestroy.go index e9f2122..ed4e700 100644 --- a/cmd/cloudServerDestroy.go +++ b/cmd/cloudServerDestroy.go @@ -40,7 +40,7 @@ server.`, for _, uniqId := range cloudServerDestroyCmdUniqIdFlag { - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqId: "UniqId", } diff --git a/cmd/cloudServerDetails.go b/cmd/cloudServerDetails.go index c4579d9..6b18692 100644 --- a/cmd/cloudServerDetails.go +++ b/cmd/cloudServerDetails.go @@ -42,7 +42,7 @@ https://cart.liquidweb.com/storm/api/docs/bleed/Storm/Server.html#method_details uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerReboot.go b/cmd/cloudServerReboot.go index 0196f45..dc50d86 100644 --- a/cmd/cloudServerReboot.go +++ b/cmd/cloudServerReboot.go @@ -35,7 +35,7 @@ To perform a forced a reboot, you must use --force`, jsonOutput, _ := cmd.Flags().GetBool("json") force, _ := cmd.Flags().GetBool("force") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqId: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerResize.go b/cmd/cloudServerResize.go index 3317625..368332c 100644 --- a/cmd/cloudServerResize.go +++ b/cmd/cloudServerResize.go @@ -76,7 +76,7 @@ During all resizes, the Cloud Server is online as the disk synchronizes. vcpuFlag, _ := cmd.Flags().GetInt64("vcpu") privateParentFlag, _ := cmd.Flags().GetString("private-parent") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerShutdown.go b/cmd/cloudServerShutdown.go index 1286e4f..f096525 100644 --- a/cmd/cloudServerShutdown.go +++ b/cmd/cloudServerShutdown.go @@ -35,7 +35,7 @@ will issue a halt command to the server and shutdown normally.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerStart.go b/cmd/cloudServerStart.go index 51f2d3f..1e9e06e 100644 --- a/cmd/cloudServerStart.go +++ b/cmd/cloudServerStart.go @@ -34,7 +34,7 @@ Boot a server. If the server is already running, this will do nothing.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerStatus.go b/cmd/cloudServerStatus.go index 1933db6..79083d1 100644 --- a/cmd/cloudServerStatus.go +++ b/cmd/cloudServerStatus.go @@ -89,7 +89,7 @@ If nothing is currently running, only the 'status' field will be returned with o } } else { for _, uid := range cloudServerStatusCmdUniqIdFlag { - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uid: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudServerUpdate.go b/cmd/cloudServerUpdate.go index d3d2f2a..86cd932 100644 --- a/cmd/cloudServerUpdate.go +++ b/cmd/cloudServerUpdate.go @@ -43,7 +43,7 @@ as-you-go, usage-based bandwidth charges.`, bandwidthQuotaFlag, _ := cmd.Flags().GetInt64("bandwidth-quota") backupQuotaFlag, _ := cmd.Flags().GetInt64("backup-quota") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudStorageBlockVolumeAttach.go b/cmd/cloudStorageBlockVolumeAttach.go index 918b50b..0d8c862 100644 --- a/cmd/cloudStorageBlockVolumeAttach.go +++ b/cmd/cloudStorageBlockVolumeAttach.go @@ -36,7 +36,7 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") attachToFlag, _ := cmd.Flags().GetString("attach-to") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", attachToFlag: "UniqId", } diff --git a/cmd/cloudStorageBlockVolumeCreate.go b/cmd/cloudStorageBlockVolumeCreate.go index c9d5bb4..975493f 100644 --- a/cmd/cloudStorageBlockVolumeCreate.go +++ b/cmd/cloudStorageBlockVolumeCreate.go @@ -41,7 +41,7 @@ Once attached, volumes appear as normal block devices, and can be used as such. crossAttachFlag, _ := cmd.Flags().GetBool("cross-attach") attachFlag, _ := cmd.Flags().GetString("attach") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ sizeFlag: "PositiveInt64", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudStorageBlockVolumeDelete.go b/cmd/cloudStorageBlockVolumeDelete.go index fe718b6..32c56a6 100644 --- a/cmd/cloudStorageBlockVolumeDelete.go +++ b/cmd/cloudStorageBlockVolumeDelete.go @@ -35,7 +35,7 @@ Once attached, volumes appear as normal block devices, and can be used as such. Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudStorageBlockVolumeDetach.go b/cmd/cloudStorageBlockVolumeDetach.go index 3f2edcf..235e59c 100644 --- a/cmd/cloudStorageBlockVolumeDetach.go +++ b/cmd/cloudStorageBlockVolumeDetach.go @@ -36,7 +36,7 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") detachFromFlag, _ := cmd.Flags().GetString("detach-from") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", detachFromFlag: "UniqId", } diff --git a/cmd/cloudStorageBlockVolumeDetails.go b/cmd/cloudStorageBlockVolumeDetails.go index 0cadbb7..2cbcec1 100644 --- a/cmd/cloudStorageBlockVolumeDetails.go +++ b/cmd/cloudStorageBlockVolumeDetails.go @@ -36,7 +36,7 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") jsonFlag, _ := cmd.Flags().GetBool("json") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudStorageBlockVolumeResize.go b/cmd/cloudStorageBlockVolumeResize.go index 97aab3a..8d532f9 100644 --- a/cmd/cloudStorageBlockVolumeResize.go +++ b/cmd/cloudStorageBlockVolumeResize.go @@ -36,7 +36,7 @@ Once attached, volumes appear as normal block devices, and can be used as such. uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") newSizeFlag, _ := cmd.Flags().GetInt64("new-size") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", newSizeFlag: "PositiveInt64", } diff --git a/cmd/cloudStorageBlockVolumeUpdate.go b/cmd/cloudStorageBlockVolumeUpdate.go index 34aaf34..bf4cac6 100644 --- a/cmd/cloudStorageBlockVolumeUpdate.go +++ b/cmd/cloudStorageBlockVolumeUpdate.go @@ -38,7 +38,7 @@ Once attached, volumes appear as normal block devices, and can be used as such. enableCrossAttachFlag, _ := cmd.Flags().GetBool("enable-cross-attach") disableCrossAttachFlag, _ := cmd.Flags().GetBool("disable-cross-attach") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudStorageObjectCreateKey.go b/cmd/cloudStorageObjectCreateKey.go index a15d321..7e53a71 100644 --- a/cmd/cloudStorageObjectCreateKey.go +++ b/cmd/cloudStorageObjectCreateKey.go @@ -31,7 +31,7 @@ var cloudStorageObjectCreateKeyCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudStorageObjectDelete.go b/cmd/cloudStorageObjectDelete.go index c040bd3..7bd6620 100644 --- a/cmd/cloudStorageObjectDelete.go +++ b/cmd/cloudStorageObjectDelete.go @@ -31,7 +31,7 @@ var cloudStorageObjectDeleteCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/cloudStorageObjectDeleteKey.go b/cmd/cloudStorageObjectDeleteKey.go index bf05f3c..c611d2d 100644 --- a/cmd/cloudStorageObjectDeleteKey.go +++ b/cmd/cloudStorageObjectDeleteKey.go @@ -32,8 +32,9 @@ var cloudStorageObjectDeleteKeyCmd = &cobra.Command{ uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") accessKeyFlag, _ := cmd.Flags().GetString("access-key") - validateFields := map[interface{}]string{ - uniqIdFlag: "UniqId", + validateFields := map[interface{}]interface{}{ + uniqIdFlag: "UniqId", + accessKeyFlag: "NonEmptyString", } if err := validate.Validate(validateFields); err != nil { lwCliInst.Die(err) diff --git a/cmd/cloudStorageObjectDetails.go b/cmd/cloudStorageObjectDetails.go index 405ea72..79ad5a9 100644 --- a/cmd/cloudStorageObjectDetails.go +++ b/cmd/cloudStorageObjectDetails.go @@ -31,7 +31,7 @@ var cloudStorageObjectDetailsCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/networkIpPoolDelete.go b/cmd/networkIpPoolDelete.go index 7cfeb16..bb467de 100644 --- a/cmd/networkIpPoolDelete.go +++ b/cmd/networkIpPoolDelete.go @@ -34,7 +34,7 @@ your account.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/networkIpPoolDetails.go b/cmd/networkIpPoolDetails.go index e747827..d6b59d2 100644 --- a/cmd/networkIpPoolDetails.go +++ b/cmd/networkIpPoolDetails.go @@ -35,7 +35,7 @@ your account.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") freeOnlyFlag, _ := cmd.Flags().GetBool("free-only") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } if err := validate.Validate(validateFields); err != nil { diff --git a/cmd/networkIpPoolUpdate.go b/cmd/networkIpPoolUpdate.go index ed035e5..cb47088 100644 --- a/cmd/networkIpPoolUpdate.go +++ b/cmd/networkIpPoolUpdate.go @@ -38,12 +38,9 @@ your account.`, uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") newIpsFlag, _ := cmd.Flags().GetInt64("new-ips") - validateFields := map[interface{}]string{ + validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(err) - } if len(networkIpPoolUpdateCmdAddIpsFlag) == 0 && len(networkIpPoolUpdateCmdRemoveIpsFlag) == 0 && newIpsFlag == -1 { @@ -57,12 +54,23 @@ your account.`, if len(networkIpPoolUpdateCmdAddIpsFlag) > 0 { apiArgs["add_ips"] = networkIpPoolUpdateCmdAddIpsFlag + for _, ip := range networkIpPoolUpdateCmdAddIpsFlag { + validateFields[ip] = "IP" + } } if len(networkIpPoolUpdateCmdRemoveIpsFlag) > 0 { apiArgs["remove_ips"] = networkIpPoolUpdateCmdRemoveIpsFlag + for _, ip := range networkIpPoolUpdateCmdRemoveIpsFlag { + validateFields[ip] = "IP" + } } if newIpsFlag != -1 { apiArgs["new_ips"] = newIpsFlag + validateFields[newIpsFlag] = "PositiveInt64" + } + + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(err) } var details apiTypes.NetworkIpPoolDetails diff --git a/validate/validate.go b/validate/validate.go index 9fe19d8..8a7e2e9 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -22,20 +22,45 @@ import ( "github.com/spf13/cast" ) -func Validate(chk map[interface{}]string) error { +func Validate(chk map[interface{}]interface{}) error { for inputFieldValue, inputField := range chk { - // inputField must be defined - defined, shouldBeType, fieldVal := inputTypeDefined(inputField) + + inputFieldVal := reflect.ValueOf(inputField) + + // by default, assume input field passed by user cannot be empty + inputFieldOptional := false + if inputFieldVal.Kind() == reflect.Map { + iface := inputFieldVal.Interface() + inputFieldType := iface.(map[string]string)["type"] + if iface.(map[string]string)["optional"] == "true" { + inputFieldOptional = true + } + inputFieldVal = reflect.ValueOf(inputFieldType) + } + + inputFieldStr := cast.ToString(inputFieldVal) + + // inputField must be defined in InputTypes struct + defined, shouldBeType, fieldVal := inputTypeDefined(inputFieldStr) if !defined { - return fmt.Errorf("%w for input field [%+v] type [%s] is not valid", ValidationFailure, inputFieldValue, inputField) + return fmt.Errorf("%w for input field [%+v] type [%s] is not valid", ValidationFailure, inputFieldValue, inputFieldStr) } // inputFieldValue must be of the correct type reflectValue := reflect.TypeOf(inputFieldValue).Name() if reflectValue != shouldBeType { return fmt.Errorf("%w for input field [%+v] type [%s] has an invalid type of [%s] wanted [%s]", - ValidationFailure, inputFieldValue, inputField, reflectValue, shouldBeType) + ValidationFailure, inputFieldValue, inputFieldStr, reflectValue, shouldBeType) + } + + // if the input field wasn't passed, and allow optional is true, continue + if inputFieldOptional { + // if inputFieldValue is a zero value, return without error + inputFieldValueVal := reflect.ValueOf(inputFieldValue) + if inputFieldValueVal.IsZero() { + continue + } } // if there's a Validate method call it @@ -45,6 +70,7 @@ func Validate(chk map[interface{}]string) error { return fmt.Errorf("%w for input field [%+v] %s", ValidationFailure, inputFieldValue, err) } } + } return nil From 74e1f0c328350c7fb7024279ba14f6e4a93c4471 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 13:04:43 -0500 Subject: [PATCH 04/28] add a bit more resiliency to validate.Validate() --- cmd/cloudServerClone.go | 6 +++--- validate/validate.go | 31 +++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/cmd/cloudServerClone.go b/cmd/cloudServerClone.go index 753ce61..42becfc 100644 --- a/cmd/cloudServerClone.go +++ b/cmd/cloudServerClone.go @@ -59,9 +59,9 @@ Server is not on a Private Parent.`, configIdFlag, _ := cmd.Flags().GetInt64("config_id") validateFields := map[interface{}]interface{}{ - uniqIdFlag: "UniqId", - hostnameFlag: map[string]string{"type": "NonEmptyString", "optional": "true"}, - passwordFlag: map[string]string{"type": "NonEmptyString", "optional": "true"}, + uniqIdFlag: "UniqId", + // expanded out struct to show ability.. its treated as required like above + hostnameFlag: map[string]string{"type": "NonEmptyString", "optional": "false"}, } if privateParentFlag != "" && configIdFlag != -1 { diff --git a/validate/validate.go b/validate/validate.go index 8a7e2e9..23190b6 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -22,7 +22,12 @@ import ( "github.com/spf13/cast" ) -func Validate(chk map[interface{}]interface{}) error { +func Validate(chk map[interface{}]interface{}) (err error) { + defer func() { + if paniced := recover(); paniced != nil { + err = fmt.Errorf("%w %s", ValidationFailure, paniced) + } + }() for inputFieldValue, inputField := range chk { @@ -44,20 +49,32 @@ func Validate(chk map[interface{}]interface{}) error { // inputField must be defined in InputTypes struct defined, shouldBeType, fieldVal := inputTypeDefined(inputFieldStr) if !defined { - return fmt.Errorf("%w for input field [%+v] type [%s] is not valid", ValidationFailure, inputFieldValue, inputFieldStr) + err = fmt.Errorf("%w for input field [%+v] type [%s] is not valid", ValidationFailure, + inputFieldValue, inputFieldStr) + return } // inputFieldValue must be of the correct type reflectValue := reflect.TypeOf(inputFieldValue).Name() if reflectValue != shouldBeType { - return fmt.Errorf("%w for input field [%+v] type [%s] has an invalid type of [%s] wanted [%s]", + err = fmt.Errorf("%w for input field [%+v] type [%s] has an invalid type of [%s] wanted [%s]", ValidationFailure, inputFieldValue, inputFieldStr, reflectValue, shouldBeType) + return } // if the input field wasn't passed, and allow optional is true, continue if inputFieldOptional { // if inputFieldValue is a zero value, return without error inputFieldValueVal := reflect.ValueOf(inputFieldValue) + + if inputFieldStr == "NonEmptyString" { + // since we check by going by the zero value for the type if the input field was passed, + // we can't enforce the string type to not be empty optionally for NonEmptyString. Since + // we can't differentiate between not passed and its zero value. + err = fmt.Errorf("NonEmptyString input fields cannot be optional") + return + } + if inputFieldValueVal.IsZero() { continue } @@ -66,14 +83,16 @@ func Validate(chk map[interface{}]interface{}) error { // if there's a Validate method call it iface := fieldVal.Interface() if interfaceHasMethod(iface, "Validate") { - if err := interfaceInputTypeValidate(iface, inputFieldValue); err != nil { - return fmt.Errorf("%w for input field [%+v] %s", ValidationFailure, inputFieldValue, err) + if validateErr := interfaceInputTypeValidate(iface, inputFieldValue); validateErr != nil { + err = fmt.Errorf("%w for input field [%+v] %s", ValidationFailure, inputFieldValue, + validateErr) + return } } } - return nil + return } func interfaceInputTypeValidate(iface, inputFieldValue interface{}) error { From 851ed4a82cc82c6b644b6112719381655802ba36 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 13:22:26 -0500 Subject: [PATCH 05/28] improve flag validation in 'cloud server resize' --- cmd/cloudServerResize.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cmd/cloudServerResize.go b/cmd/cloudServerResize.go index 368332c..e900295 100644 --- a/cmd/cloudServerResize.go +++ b/cmd/cloudServerResize.go @@ -79,6 +79,7 @@ During all resizes, the Cloud Server is online as the disk synchronizes. validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", } + // must validate UniqId now because we call api methods with this uniq_id before below validate if err := validate.Validate(validateFields); err != nil { lwCliInst.Die(err) } @@ -120,6 +121,11 @@ During all resizes, the Cloud Server is online as the disk synchronizes. lwCliInst.Die(fmt.Errorf("already on config_id [%d]; not initiating a resize", configIdFlag)) } + validateFields[configIdFlag] = "PositiveInt64" + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(err) + } + // determine reboot expectation. // resize up full: 2 reboot // resize up quick (skip-fs-resize) 1 reboot @@ -179,6 +185,7 @@ During all resizes, the Cloud Server is online as the disk synchronizes. resizeArgs["newsize"] = 0 // 0 indicates private parent resize resizeArgs["parent"] = privateParentUniqId // uniq_id of the private parent + validateFields[privateParentUniqId] = "UniqId" // server/resize api method always wants diskspace, vcpu, memory passed for pp resize, even if not changing // value. So set to current value, then override based on passed flags. resizeArgs["diskspace"] = cloudServerDetails.DiskSpace @@ -187,12 +194,15 @@ During all resizes, the Cloud Server is online as the disk synchronizes. if diskspaceFlag != -1 { resizeArgs["diskspace"] = diskspaceFlag // desired diskspace + validateFields[diskspaceFlag] = "PositiveInt64" } if memoryFlag != -1 { resizeArgs["memory"] = memoryFlag // desired memory + validateFields[memoryFlag] = "PositiveInt64" } if vcpuFlag != -1 { resizeArgs["vcpu"] = vcpuFlag // desired vcpus + validateFields[vcpuFlag] = "PositiveInt64" } // determine if this will be a live resize @@ -227,6 +237,10 @@ During all resizes, the Cloud Server is online as the disk synchronizes. } } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(err) + } + _, err := lwCliInst.LwApiClient.Call("bleed/server/resize", resizeArgs) if err != nil { lwCliInst.Die(err) From 7b3beef747d2fd39d4f3b45664e75ced3fc6931f Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 14:06:05 -0500 Subject: [PATCH 06/28] more input field validation in 'cloud network public add' --- cmd/cloudNetworkPublicAdd.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmd/cloudNetworkPublicAdd.go b/cmd/cloudNetworkPublicAdd.go index 18e4ef1..7a9c9c1 100644 --- a/cmd/cloudNetworkPublicAdd.go +++ b/cmd/cloudNetworkPublicAdd.go @@ -59,9 +59,20 @@ will be up to the administrator to configure the IP address(es) within the serve } if newIpsFlag != 0 { apiArgs["ip_count"] = newIpsFlag + validateFields := map[interface{}]interface{}{newIpsFlag: "PositiveInt64"} + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(err) + } } if len(cloudNetworkPublicAddCmdPoolIpsFlag) != 0 { apiArgs["pool_ips"] = cloudNetworkPublicAddCmdPoolIpsFlag + validateFields := map[interface{}]interface{}{} + for _, ip := range cloudNetworkPublicAddCmdPoolIpsFlag { + validateFields[ip] = "IP" + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(err) + } } var details apiTypes.NetworkIpAdd From 29f419f296b5327382d3cc570edd2bd851c82309 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 14:37:33 -0500 Subject: [PATCH 07/28] more flag hardening in 'cloud server create' and fix PositiveInt typo --- cmd/cloudServerCreate.go | 33 ++++++++++++++++++++++++++------- validate/types.go | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/cmd/cloudServerCreate.go b/cmd/cloudServerCreate.go index ab4fc0a..8327324 100644 --- a/cmd/cloudServerCreate.go +++ b/cmd/cloudServerCreate.go @@ -79,13 +79,6 @@ For a list of backups, see 'cloud inventory backups list' backupIdFlag, _ := cmd.Flags().GetInt("backup-id") imageIdFlag, _ := cmd.Flags().GetInt("image-id") - validateFields := map[interface{}]interface{}{ - zoneFlag: "PositiveInt", - } - if err := validate.Validate(validateFields); err != nil { - lwCliInst.Die(err) - } - // sanity check flags if configIdFlag == 0 && privateParentFlag == "" { lwCliInst.Die(fmt.Errorf("--config_id is a required flag without --private-parent")) @@ -94,6 +87,32 @@ For a list of backups, see 'cloud inventory backups list' lwCliInst.Die(fmt.Errorf("at least one of the following flags must be set --template --image-id --backup-id")) } + validateFields := map[interface{}]interface{}{ + zoneFlag: map[string]string{"type": "PositiveInt", "optional": "true"}, + hostnameFlag: "NonEmptyString", + typeFlag: "NonEmptyString", + ipsFlag: "PositiveInt", + passwordFlag: "NonEmptyString", + backupPlanFlag: "NonEmptyString", + } + if backupIdFlag != -1 { + validateFields[backupIdFlag] = "PositiveInt" + } + if imageIdFlag != -1 { + validateFields[imageIdFlag] = "PositiveInt" + } + if vcpuFlag == -1 { + validateFields[configIdFlag] = "PositiveInt" + } + if configIdFlag == -1 { + validateFields[vcpuFlag] = "PositiveInt" + validateFields[memoryFlag] = "PositiveInt" + validateFields[diskspaceFlag] = "PositiveInt" + } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(err) + } + var publicSshKeyContents string sshPkeyContents, err := ioutil.ReadFile(pubSshKeyFlag) if err == nil { diff --git a/validate/types.go b/validate/types.go index 1edf1cf..bba0d02 100644 --- a/validate/types.go +++ b/validate/types.go @@ -30,7 +30,7 @@ type InputTypes struct { UniqId InputTypeUniqId IP InputTypeIP PositiveInt64 InputTypePositiveInt64 - PostiveInt InputTypePositiveInt + PositiveInt InputTypePositiveInt NonEmptyString InputTypeNonEmptyString } From 125e90ba388f26a0f194f43c16e38523c51a7f01 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 14:49:29 -0500 Subject: [PATCH 08/28] enforce these two being non empty strings as well --- cmd/cloudImageCreate.go | 1 + cmd/cloudNetworkVipCreate.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd/cloudImageCreate.go b/cmd/cloudImageCreate.go index a79767f..bb42028 100644 --- a/cmd/cloudImageCreate.go +++ b/cmd/cloudImageCreate.go @@ -34,6 +34,7 @@ var cloudImageCreateCmd = &cobra.Command{ validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", + nameFlag: "NonEmptyString", } if err := validate.Validate(validateFields); err != nil { lwCliInst.Die(err) diff --git a/cmd/cloudNetworkVipCreate.go b/cmd/cloudNetworkVipCreate.go index db57cf5..7261015 100644 --- a/cmd/cloudNetworkVipCreate.go +++ b/cmd/cloudNetworkVipCreate.go @@ -67,6 +67,7 @@ Heartbeat validateFields := map[interface{}]interface{}{ zoneFlag: "PositiveInt64", + nameFlag: "NonEmptyString", } if err := validate.Validate(validateFields); err != nil { lwCliInst.Die(err) From eaf69173e0ec4b24af16a547b298bb0be0350e4e Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 14:57:43 -0500 Subject: [PATCH 09/28] whoops, missed the continue --- cmd/cloudNetworkPublicRemove.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/cloudNetworkPublicRemove.go b/cmd/cloudNetworkPublicRemove.go index a69433e..4594875 100644 --- a/cmd/cloudNetworkPublicRemove.go +++ b/cmd/cloudNetworkPublicRemove.go @@ -62,6 +62,7 @@ Note that you cannot remove the Cloud Servers primary ip with this command.`, } if err := validate.Validate(validateFields); err != nil { fmt.Printf("%s ... skipping\n", err) + continue } var details apiTypes.NetworkIpRemove From 8c2eaabaa755be533f1c0285822ad932e335b246 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 22:25:51 -0500 Subject: [PATCH 10/28] make install default Makefile target, not build most users will want to just install the program on their system, placing it automatically in a location within their PATH. So instead of defaulting to build default to install, since go install handles that for us. --- Makefile | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9a960e1..65a28a7 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SHELL=/bin/bash GO_LINKER_SYMBOL := "main.version" default: - $(MAKE) build + $(MAKE) install %: @: @@ -21,6 +21,17 @@ static: build: go build -ldflags="-s -w" -o _exe/liquidweb-cli github.com/liquidweb/liquidweb-cli +install: + go install + @echo "" + @echo "liquidweb-cli has been installed, and it should now be in your PATH." + @echo "" + @echo "Executables are installed in the directory named by the GOBIN environment" + @echo "variable, which defaults to GOPATH/bin or HOME/go/bin if the GOPATH" + @echo "environment variable is not set. Executables in GOROOT" + @echo "are installed in GOROOT/bin or GOTOOLDIR instead of GOBIN." + @echo "" + run: go run main.go $(call args,) From 0953b529a5689f06c5edea4f05416a7b83d24eed Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Wed, 22 Jan 2020 22:31:42 -0500 Subject: [PATCH 11/28] update readme for makefile change --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index b477121..c249c6b 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,16 @@ Flags: Use "liquidweb-cli [command] --help" for more information about a command. ``` + +## Obtaining prebuilt binaries + +Head on over to the [releases page](https://github.com/liquidweb/liquidweb-cli/releases) to get prebuilt binaries for your platform. + ## Building from source You can build liquidweb-cli from source by running `make build` from the root of this repository. The resulting program will be located at `./_exe/liquidweb-cli`. +You can also build+install liquidweb-cli onto your system in the ordinary `go install` way. To do this, either just run `go install` from the root of this repository, +or `make install`. If you run `make` with no arguments, this will be the default action. ## First Time Setup The first time you use liquidweb-cli, you will need to setup an auth context. An auth context holds authentication related data for a specific LiquidWeb account. You can follow a guided questionnaire to add your auth contexts if you pass arguments `auth init` to liquidweb-cli. By default contexts are stored in `~/.liquidweb-cli.yaml` or `%APPDATA%/.liquidweb-cli.yaml` on Windows. From 03899e1ca3c98e70dff79c13801af9ab8508114b Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 14:50:11 -0500 Subject: [PATCH 12/28] remove help references to old inventory subcommands --- cmd/cloudBackupDetails.go | 2 +- cmd/cloudImageCreate.go | 2 +- cmd/cloudImageDelete.go | 2 +- cmd/cloudImageDetails.go | 2 +- cmd/cloudImageRename.go | 2 +- cmd/cloudServerClone.go | 2 +- cmd/cloudServerCreate.go | 8 ++++---- cmd/cloudStorageObjectDelete.go | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/cloudBackupDetails.go b/cmd/cloudBackupDetails.go index 055bb00..cb8365f 100644 --- a/cmd/cloudBackupDetails.go +++ b/cmd/cloudBackupDetails.go @@ -46,6 +46,6 @@ func init() { cloudBackupCmd.AddCommand(cloudBackupDetailsCmd) cloudBackupDetailsCmd.Flags().Int64("backup_id", -1, - "id number of the backup (see 'cloud inventory backup list')") + "id number of the backup (see 'cloud backup list')") cloudBackupDetailsCmd.MarkFlagRequired("backup_id") } diff --git a/cmd/cloudImageCreate.go b/cmd/cloudImageCreate.go index bb42028..8eb0659 100644 --- a/cmd/cloudImageCreate.go +++ b/cmd/cloudImageCreate.go @@ -49,7 +49,7 @@ var cloudImageCreateCmd = &cobra.Command{ } fmt.Printf("Creating image! %+v\n", details) - fmt.Printf("\tthe Cloud Image will not appear in 'cloud inventory image list' until complete\n") + fmt.Printf("\tthe Cloud Image will not appear in 'cloud image list' until complete\n") }, } diff --git a/cmd/cloudImageDelete.go b/cmd/cloudImageDelete.go index 6bafad7..742ff1e 100644 --- a/cmd/cloudImageDelete.go +++ b/cmd/cloudImageDelete.go @@ -46,6 +46,6 @@ func init() { cloudImageCmd.AddCommand(cloudImageDeleteCmd) cloudImageDeleteCmd.Flags().Int64("image_id", -1, - "id number of the image (see 'cloud inventory image list')") + "id number of the image (see 'cloud image list')") cloudImageDeleteCmd.MarkFlagRequired("image_id") } diff --git a/cmd/cloudImageDetails.go b/cmd/cloudImageDetails.go index 5b11a27..ef374d2 100644 --- a/cmd/cloudImageDetails.go +++ b/cmd/cloudImageDetails.go @@ -46,6 +46,6 @@ func init() { cloudImageCmd.AddCommand(cloudImageDetailsCmd) cloudImageDetailsCmd.Flags().Int64("image_id", -1, - "id number of the image (see 'cloud inventory image list')") + "id number of the image (see 'cloud image list')") cloudImageDetailsCmd.MarkFlagRequired("image_id") } diff --git a/cmd/cloudImageRename.go b/cmd/cloudImageRename.go index 2f3975b..3c48305 100644 --- a/cmd/cloudImageRename.go +++ b/cmd/cloudImageRename.go @@ -47,7 +47,7 @@ func init() { cloudImageCmd.AddCommand(cloudImageRenameCmd) cloudImageRenameCmd.Flags().Int64("image_id", -1, - "id number of the image (see 'cloud inventory image list')") + "id number of the image (see 'cloud image list')") cloudImageRenameCmd.Flags().String("name", "", "new name for the Cloud Image") cloudImageRenameCmd.MarkFlagRequired("image_id") diff --git a/cmd/cloudServerClone.go b/cmd/cloudServerClone.go index 42becfc..48615b9 100644 --- a/cmd/cloudServerClone.go +++ b/cmd/cloudServerClone.go @@ -150,7 +150,7 @@ func init() { // Private Parent cloudServerCloneCmd.Flags().String("private-parent", "", - "name or uniq_id of the Private Parent to place new Cloud Server on (see: 'cloud inventory private-parent list')") + "name or uniq_id of the Private Parent to place new Cloud Server on (see: 'cloud private-parent list')") cloudServerCloneCmd.Flags().Int64("diskspace", -1, "diskspace for new Cloud Server (when private-parent)") cloudServerCloneCmd.Flags().Int64("memory", -1, "memory for new Cloud Server (when private-parent)") cloudServerCloneCmd.Flags().Int64("vcpu", -1, "amount of vcpus for new Cloud Server (when private-parent)") diff --git a/cmd/cloudServerCreate.go b/cmd/cloudServerCreate.go index 8327324..f97ee39 100644 --- a/cmd/cloudServerCreate.go +++ b/cmd/cloudServerCreate.go @@ -55,8 +55,8 @@ Examples: These examples use default values for various flags, such as password, type, ssh-key, hostname, etc. For a list of Templates, Configs, and Region/Zones, see 'cloud server options --configs --templates --zones' -For a list of images, see 'cloud inventory images list' -For a list of backups, see 'cloud inventory backups list' +For a list of images, see 'cloud images list' +For a list of backups, see 'cloud backups list' `, Run: func(cmd *cobra.Command, args []string) { templateFlag, _ := cmd.Flags().GetString("template") @@ -270,8 +270,8 @@ func init() { cloudServerCreateCmd.Flags().Int("zone", 0, "zone (id) to create new Cloud Server in (see 'cloud server options --zones')") cloudServerCreateCmd.Flags().String("password", randomPassword, "root or administrator password to set") - cloudServerCreateCmd.Flags().Int("backup-id", -1, "id of backup to create from (see 'cloud inventory backup list')") - cloudServerCreateCmd.Flags().Int("image-id", -1, "id of image to create from (see 'cloud inventory image list')") + cloudServerCreateCmd.Flags().Int("backup-id", -1, "id of backup to create from (see 'cloud backup list')") + cloudServerCreateCmd.Flags().Int("image-id", -1, "id of image to create from (see 'cloud image list')") cloudServerCreateCmd.Flags().StringSliceVar(&cloudServerCreateCmdPoolIpsFlag, "pool-ips", []string{}, "ips from your IP Pool separated by ',' to assign to the new Cloud Server") diff --git a/cmd/cloudStorageObjectDelete.go b/cmd/cloudStorageObjectDelete.go index 7bd6620..2d422ac 100644 --- a/cmd/cloudStorageObjectDelete.go +++ b/cmd/cloudStorageObjectDelete.go @@ -53,7 +53,7 @@ var cloudStorageObjectDeleteCmd = &cobra.Command{ func init() { cloudStorageObjectCmd.AddCommand(cloudStorageObjectDeleteCmd) cloudStorageObjectDeleteCmd.Flags().String("uniq_id", "", - "uniq_id of object store to delete (see 'cloud inventory storage object list')") + "uniq_id of object store to delete (see 'cloud storage object list')") cloudStorageObjectDeleteCmd.MarkFlagRequired("uniq_id") } From 06d351776d9ec09567f1643a1a4ed19f1e87bb19 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 15:06:25 -0500 Subject: [PATCH 13/28] dont show non-open zones here --- cmd/cloudServerOptions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/cloudServerOptions.go b/cmd/cloudServerOptions.go index 52c9e06..6aede3e 100644 --- a/cmd/cloudServerOptions.go +++ b/cmd/cloudServerOptions.go @@ -157,9 +157,9 @@ Be sure to take a look at the flags section for specific flags to pass.`, lwCliInst.Die(err) } - //if zoneDetails.(map[string]interface{})["status"] != "Open" { - // continue - //} + if zoneDetails.(map[string]interface{})["status"] != "Open" { + continue + } regionId := cast.ToInt(zoneDetails.(map[string]interface{})["region"].(map[string]interface{})["id"]) regionsWithZoneInfo[regionId] = append(regionsWithZoneInfo[regionId], map[string]interface{}{ From 35f822609fbc9d7368a3302b4cb225073f014b71 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 16:42:36 -0500 Subject: [PATCH 14/28] stricter validation around config and lwApi --- cmd/cloudServerCreate.go | 2 +- cmd/cloudServerOptions.go | 2 +- cmd/cloudServerResize.go | 2 +- cmd/root.go | 3 +-- instance/api/api.go | 24 ++++++++++++++++--- instance/api/types.go | 48 ++++++++++++++++++++++++++++++++++++++ instance/instance.go | 12 +++++----- instance/types.go | 6 ++--- types/errors/errorTypes.go | 2 ++ 9 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 instance/api/types.go diff --git a/cmd/cloudServerCreate.go b/cmd/cloudServerCreate.go index f97ee39..aa5bd38 100644 --- a/cmd/cloudServerCreate.go +++ b/cmd/cloudServerCreate.go @@ -233,7 +233,7 @@ For a list of backups, see 'cloud backups list' createArgs["public_ssh_key"] = publicSshKeyContents } - result, err := lwCliInst.LwApiClient.Call("bleed/server/create", createArgs) + result, err := lwCliInst.LwCliApiClient.Call("bleed/server/create", createArgs) if err != nil { lwCliInst.Die(err) } diff --git a/cmd/cloudServerOptions.go b/cmd/cloudServerOptions.go index 6aede3e..a0ce889 100644 --- a/cmd/cloudServerOptions.go +++ b/cmd/cloudServerOptions.go @@ -152,7 +152,7 @@ Be sure to take a look at the flags section for specific flags to pass.`, // add final region, config, and template data to regionsWithZoneInfo for zone, _ := range zones { - zoneDetails, err := lwCliInst.LwApiClient.Call("bleed/network/zone/details", map[string]interface{}{"id": zone}) + zoneDetails, err := lwCliInst.LwCliApiClient.Call("bleed/network/zone/details", map[string]interface{}{"id": zone}) if err != nil { lwCliInst.Die(err) } diff --git a/cmd/cloudServerResize.go b/cmd/cloudServerResize.go index e900295..c12552b 100644 --- a/cmd/cloudServerResize.go +++ b/cmd/cloudServerResize.go @@ -241,7 +241,7 @@ During all resizes, the Cloud Server is online as the disk synchronizes. lwCliInst.Die(err) } - _, err := lwCliInst.LwApiClient.Call("bleed/server/resize", resizeArgs) + _, err := lwCliInst.LwCliApiClient.Call("bleed/server/resize", resizeArgs) if err != nil { lwCliInst.Die(err) } diff --git a/cmd/root.go b/cmd/root.go index 5cedac2..d47ae60 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -89,8 +89,7 @@ func initConfig() { var lwCliInstErr error lwCliInst, lwCliInstErr = instance.New(vp) if lwCliInstErr != nil { - fmt.Printf("Fatal: [%s]\n", lwCliInstErr) - os.Exit(1) + lwCliInst.Die(lwCliInstErr) } } diff --git a/instance/api/api.go b/instance/api/api.go index e42f3fd..4f83e4d 100644 --- a/instance/api/api.go +++ b/instance/api/api.go @@ -17,16 +17,29 @@ package api import ( "fmt" + "strings" "github.com/spf13/cast" "github.com/spf13/viper" lwApi "github.com/liquidweb/go-lwApi" + + "github.com/liquidweb/liquidweb-cli/types/errors" ) -func New(viper *viper.Viper) (lwApiClient *lwApi.Client, err error) { +func New(viper *viper.Viper) (*LwCliApiClient, error) { + if err := viper.ReadInConfig(); err != nil { + strErr := strings.ToUpper(cast.ToString(err)) + if strings.Contains(strErr, "CONFIG FILE") && strings.Contains(strErr, "NOT FOUND IN") { + err = nil // no present yet config is not fatal here + } else { + return &LwCliApiClient{}, fmt.Errorf("%w Raw error: %s", errorTypes.InvalidConfigSyntax, err) + } + } + // create the object from the current context if there is one. If "auth init" has not yet been ran, // there would be no current context yet. + lwCliApiClient := LwCliApiClient{Viper: viper} currentContext := viper.GetString("liquidweb.api.current_context") if currentContext != "" { apiUsername := viper.GetString(fmt.Sprintf("liquidweb.api.contexts.%s.username", currentContext)) @@ -41,8 +54,13 @@ func New(viper *viper.Viper) (lwApiClient *lwApi.Client, err error) { currentContext))), } - lwApiClient, err = lwApi.New(&lwApiCfg) + lwApiClient, err := lwApi.New(&lwApiCfg) + if err != nil { + return &LwCliApiClient{}, err + } + + lwCliApiClient.LwApiClient = lwApiClient } - return + return &lwCliApiClient, nil } diff --git a/instance/api/types.go b/instance/api/types.go new file mode 100644 index 0000000..bcd60b1 --- /dev/null +++ b/instance/api/types.go @@ -0,0 +1,48 @@ +/* +Copyright © LiquidWeb + +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 api + +import ( + "fmt" + "github.com/spf13/viper" + + lwApi "github.com/liquidweb/go-lwApi" + + "github.com/liquidweb/liquidweb-cli/types/errors" +) + +type LwCliApiClient struct { + LwApiClient *lwApi.Client + Viper *viper.Viper +} + +func (x LwCliApiClient) Call(method string, params interface{}) (got interface{}, err error) { + if err = x.Viper.ReadInConfig(); err != nil { + err = fmt.Errorf("%w Raw error: %s", errorTypes.InvalidConfigSyntax, err) + return + } + + currentContext := x.Viper.GetString("liquidweb.api.current_context") + if currentContext == "" { + err = errorTypes.NoCurrentContext + return + } + + got, err = x.LwApiClient.Call(method, params) + + return +} diff --git a/instance/instance.go b/instance/instance.go index b31b019..f32d27f 100644 --- a/instance/instance.go +++ b/instance/instance.go @@ -34,7 +34,7 @@ import ( func New(viper *viper.Viper) (Client, error) { - lwApiClient, err := lwCliInstApi.New(viper) + lwCliApiClient, err := lwCliInstApi.New(viper) if err != nil { return Client{}, fmt.Errorf( "Failed creating an lwApi client. Error was:\n%s\nPlease check your liquidweb-cli config file for errors or ommissions\n", @@ -42,8 +42,8 @@ func New(viper *viper.Viper) (Client, error) { } client := Client{ - LwApiClient: lwApiClient, - Viper: viper, + LwCliApiClient: lwCliApiClient, + Viper: viper, } return client, nil @@ -119,7 +119,7 @@ func (client *Client) RemoveContext(context string) error { } func (client *Client) CallLwApiInto(method string, methodArgs map[string]interface{}, obj interface{}) (err error) { - got, err := client.LwApiClient.Call(method, methodArgs) + got, err := client.LwCliApiClient.Call(method, methodArgs) if err != nil { return } @@ -149,7 +149,7 @@ func (client *Client) AllPaginatedResults(args *AllPaginatedResultsArgs) (apiTyp methodArgs["page_size"] = resultsPerPage } - got, err := client.LwApiClient.Call(args.Method, methodArgs) + got, err := client.LwCliApiClient.Call(args.Method, methodArgs) if err != nil { return apiTypes.MergedPaginatedList{}, err } @@ -170,7 +170,7 @@ func (client *Client) AllPaginatedResults(args *AllPaginatedResultsArgs) (apiTyp for morePages { methodArgs["page_num"] = nextPage - got, err := client.LwApiClient.Call(args.Method, methodArgs) + got, err := client.LwCliApiClient.Call(args.Method, methodArgs) if err != nil { return apiTypes.MergedPaginatedList{}, err } diff --git a/instance/types.go b/instance/types.go index f952b6f..27f46a2 100644 --- a/instance/types.go +++ b/instance/types.go @@ -18,12 +18,12 @@ package instance import ( "github.com/spf13/viper" - lwApi "github.com/liquidweb/go-lwApi" + lwCliInstApi "github.com/liquidweb/liquidweb-cli/instance/api" ) type Client struct { - LwApiClient *lwApi.Client - Viper *viper.Viper + LwCliApiClient *lwCliInstApi.LwCliApiClient + Viper *viper.Viper } type AllPaginatedResultsArgs struct { diff --git a/types/errors/errorTypes.go b/types/errors/errorTypes.go index 8034929..6fd2be1 100644 --- a/types/errors/errorTypes.go +++ b/types/errors/errorTypes.go @@ -23,3 +23,5 @@ var LwCliInputError = errors.New("Invalid input; missing required paramater") var LwApiUnexpectedResponseStructure = errors.New("Unexpected API response structure when calling method") var UnknownTerminal = errors.New("unknown terminal") var MergeConfigError = errors.New("error merging configuration") +var InvalidConfigSyntax = errors.New("configuration contains invalid syntax; use 'auth init' to create a new configuration.") +var NoCurrentContext = errors.New("No current context is set; cannot continue.\nSee 'help auth' for assistance creating/deleting/modifying/setting contexts.") From 82e3825c9a7ed8c1266d552e715c5191b1e3e72c Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 16:46:57 -0500 Subject: [PATCH 15/28] consistent import order --- instance/api/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instance/api/types.go b/instance/api/types.go index bcd60b1..795fc74 100644 --- a/instance/api/types.go +++ b/instance/api/types.go @@ -18,9 +18,9 @@ package api import ( "fmt" - "github.com/spf13/viper" lwApi "github.com/liquidweb/go-lwApi" + "github.com/spf13/viper" "github.com/liquidweb/liquidweb-cli/types/errors" ) From 903d0b383226dbc29e04d7a58cf83c193b54e17f Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 16:53:29 -0500 Subject: [PATCH 16/28] 'auth init': make toggle some default answers --- cmd/authInit.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/authInit.go b/cmd/authInit.go index 43ab3fd..c153dbe 100644 --- a/cmd/authInit.go +++ b/cmd/authInit.go @@ -245,7 +245,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { // make current context? for !haveMakeCurrentContextAnswer { - term.Write([]byte("Make current context? (yes/[no])")) + term.Write([]byte("Make current context? ([yes]/no)")) makeCurrentContextBytes, err := term.ReadLine() if err != nil { return contexts, err @@ -257,14 +257,14 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { } haveMakeCurrentContextAnswer = true - if makeCurrentContextString == "yes" { + if makeCurrentContextString == "yes" || makeCurrentContextString == "" { lwCliInst.Viper.Set("liquidweb.api.current_context", contextNameAnswer) } } // more contexts to add ? for !haveMoreContextsToAddAnswer { - term.Write([]byte("Add another context? ([yes]/no): ")) + term.Write([]byte("Add another context? (yes/[no]): ")) moreContextsBytes, err := term.ReadLine() if err != nil { return contexts, err @@ -276,7 +276,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { continue } - if answer == "no" { + if answer == "no" || answer == "" { moreAdds = false haveMoreContextsToAddAnswer = true } From 3dcc0643de11a7c0f77ff5914531276a0961dd24 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 21:29:50 -0500 Subject: [PATCH 17/28] add warning dialoag in 'auth init' --- cmd/authInit.go | 107 +++++++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/cmd/authInit.go b/cmd/authInit.go index c153dbe..7604817 100644 --- a/cmd/authInit.go +++ b/cmd/authInit.go @@ -39,8 +39,6 @@ var authInitCmd = &cobra.Command{ Intended to be ran for initial setup only.`, Run: func(cmd *cobra.Command, args []string) { - writeEmptyConfig() - if err := setAuthDataInteractively(); err != nil { lwCliInst.Die(err) } @@ -52,46 +50,29 @@ func init() { } func setAuthDataInteractively() error { - cfgFile, err := getExpectedConfigPath() + _, writeConfig, err := fetchAuthDataInteractively() if err != nil { - lwCliInst.Die(err) - } - if utils.FileExists(cfgFile) { - if err := os.Remove(cfgFile); err != nil { - lwCliInst.Die(err) - } - f, err := os.Create(cfgFile) - if err != nil { - lwCliInst.Die(err) - } - f.Close() - if err := os.Chmod(cfgFile, 0600); err != nil { - lwCliInst.Die(err) - } - - lwCliInst.Viper.ReadConfig(bytes.NewBuffer([]byte{})) - } - - if _, err := fetchAuthDataInteractively(); err != nil { return err } - if err := lwCliInst.Viper.WriteConfig(); err != nil { - return err + if writeConfig { + if err := lwCliInst.Viper.WriteConfig(); err != nil { + return err + } } return nil } -func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { +func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { var contexts []cmdTypes.AuthContext if !terminal.IsTerminal(0) || !terminal.IsTerminal(1) { - return contexts, errorTypes.UnknownTerminal + return contexts, false, errorTypes.UnknownTerminal } oldState, err := terminal.MakeRaw(0) if err != nil { - return contexts, err + return contexts, false, err } defer terminal.Restore(0, oldState) screen := struct { @@ -101,7 +82,58 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { term := terminal.NewTerminal(screen, "") term.SetPrompt(cast.ToString(term.Escape.Blue) + " > " + cast.ToString(term.Escape.Reset)) - moreAdds := true + moreAdds := false + + // warn before deleting config + var haveProceedAnswer bool + for !haveProceedAnswer { + term.Write([]byte("Warning: This will delete all auth contexts. Continue (yes/[no])?: ")) + proceedBytes, err := term.ReadLine() + if err != nil { + return contexts, false, err + } + proceedString := cast.ToString(proceedBytes) + if proceedString != "yes" && proceedString != "no" && proceedString != "" { + term.Write([]byte("invalid input.\n")) + continue + } + + if proceedString == "yes" { + moreAdds = true + haveProceedAnswer = true + } else if proceedString == "" || proceedString == "no" { + haveProceedAnswer = true + moreAdds = false + } + } + + // return if user didnt acknowledge to proceed + if !moreAdds { + return contexts, false, nil + } + + // if user consented to proceed, clear config + writeEmptyConfig() + cfgFile, err := getExpectedConfigPath() + if err != nil { + return contexts, false, err + } + if utils.FileExists(cfgFile) { + if err := os.Remove(cfgFile); err != nil { + lwCliInst.Die(err) + } + f, err := os.Create(cfgFile) + if err != nil { + lwCliInst.Die(err) + } + f.Close() + if err := os.Chmod(cfgFile, 0600); err != nil { + lwCliInst.Die(err) + } + + lwCliInst.Viper.ReadConfig(bytes.NewBuffer([]byte{})) + } + for moreAdds { var ( contextNameAnswer string @@ -125,7 +157,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { term.Write([]byte("Name this context: ")) contextNameBytes, err := term.ReadLine() if err != nil { - return contexts, err + return contexts, false, err } contextNameAnswer = cast.ToString(contextNameBytes) if contextNameAnswer == "" { @@ -142,7 +174,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { term.Write([]byte("LiquidWeb username: ")) usernameBytes, err := term.ReadLine() if err != nil { - return contexts, err + return contexts, false, err } usernameAnswer = cast.ToString(usernameBytes) if usernameAnswer == "" { @@ -158,7 +190,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { for !havePasswordAnswer { passwordBytes, err := term.ReadPassword("LiquidWeb password: ") if err != nil { - return contexts, err + return contexts, false, err } passwordAnswer = cast.ToString(passwordBytes) if passwordAnswer == "" { @@ -177,7 +209,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { term.Write([]byte(question)) urlBytes, err := term.ReadLine() if err != nil { - return contexts, err + return contexts, false, err } urlAnswer := cast.ToString(urlBytes) if urlAnswer == "" { @@ -203,7 +235,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { term.Write([]byte("Insecure SSL Validation (yes/[no]): ")) insecureBytes, err := term.ReadLine() if err != nil { - return contexts, err + return contexts, false, err } insecureString := cast.ToString(insecureBytes) if insecureString != "yes" && insecureString != "no" && insecureString != "" { @@ -226,7 +258,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { term.Write([]byte(question)) timeoutBytes, err := term.ReadLine() if err != nil { - return contexts, err + return contexts, false, err } timeoutAnswer = cast.ToInt(timeoutBytes) if timeoutAnswer == 0 { @@ -248,7 +280,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { term.Write([]byte("Make current context? ([yes]/no)")) makeCurrentContextBytes, err := term.ReadLine() if err != nil { - return contexts, err + return contexts, false, err } makeCurrentContextString := cast.ToString(makeCurrentContextBytes) if makeCurrentContextString != "" && makeCurrentContextString != "yes" && makeCurrentContextString != "no" { @@ -267,7 +299,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { term.Write([]byte("Add another context? (yes/[no]): ")) moreContextsBytes, err := term.ReadLine() if err != nil { - return contexts, err + return contexts, false, err } answer := cast.ToString(moreContextsBytes) @@ -297,7 +329,7 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, error) { contexts = append(contexts, context) } - return contexts, err + return contexts, true, err } func getExpectedConfigPath() (string, error) { @@ -317,7 +349,6 @@ func writeEmptyConfig() error { return err } - fmt.Printf("using config file %s\n", cfgFile) f, err := os.Create(cfgFile) if err != nil { return err From 9fddee8619f22c884a157a171f82728bc88e0e2b Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 23:22:45 -0500 Subject: [PATCH 18/28] remove url, timeout, insecure opts from 'auth init' .. these aren't universally useful to an average user, so take it out of 'auth init' in the interest of simplicity. One can still override these defaults via 'auth update-context' later. --- cmd/authInit.go | 99 ++++++++----------------------------------------- 1 file changed, 15 insertions(+), 84 deletions(-) diff --git a/cmd/authInit.go b/cmd/authInit.go index 7604817..b01cbd2 100644 --- a/cmd/authInit.go +++ b/cmd/authInit.go @@ -20,7 +20,6 @@ import ( "fmt" "io" "os" - "strings" "golang.org/x/crypto/ssh/terminal" @@ -142,12 +141,6 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { haveUsernameAnswer bool passwordAnswer string havePasswordAnswer bool - urlAnswer string - haveUrlAnswer bool - insecureAnswer bool - haveInsecureAnswer bool - timeoutAnswer int - haveTimeoutAnswer bool haveMakeCurrentContextAnswer bool haveMoreContextsToAddAnswer bool ) @@ -202,79 +195,6 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { } } - // url - for !haveUrlAnswer { - defaultUrl := "https://api.liquidweb.com" - question := fmt.Sprintf("API URL (enter for default [%s]): ", defaultUrl) - term.Write([]byte(question)) - urlBytes, err := term.ReadLine() - if err != nil { - return contexts, false, err - } - urlAnswer := cast.ToString(urlBytes) - if urlAnswer == "" { - haveUrlAnswer = true - urlAnswer = defaultUrl - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.url", - contextNameAnswer), defaultUrl) - } else { - // make sure it looks like a url - if strings.HasPrefix(urlAnswer, "https://") { - haveUrlAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.url", - contextNameAnswer), urlAnswer) - } else { - resp := fmt.Sprintf("url [%s] looks invalid; should start with 'https://'\n", urlAnswer) - term.Write([]byte(resp)) - } - } - } - - // insecure ssl validation - for !haveInsecureAnswer { - term.Write([]byte("Insecure SSL Validation (yes/[no]): ")) - insecureBytes, err := term.ReadLine() - if err != nil { - return contexts, false, err - } - insecureString := cast.ToString(insecureBytes) - if insecureString != "yes" && insecureString != "no" && insecureString != "" { - term.Write([]byte("invalid input.\n")) - continue - } - - if insecureString == "yes" { - insecureAnswer = true - } - haveInsecureAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.insecure", - contextNameAnswer), insecureAnswer) - } - - // timeout - for !haveTimeoutAnswer { - defaultTimeout := 90 - question := fmt.Sprintf("API timeout (enter for default [%d]): ", defaultTimeout) - term.Write([]byte(question)) - timeoutBytes, err := term.ReadLine() - if err != nil { - return contexts, false, err - } - timeoutAnswer = cast.ToInt(timeoutBytes) - if timeoutAnswer == 0 { - timeoutAnswer = defaultTimeout - } - if timeoutAnswer <= 0 { - resp := fmt.Sprintf("timeout [%d] must be > 0.\n", timeoutAnswer) - term.Write([]byte(resp)) - continue - } - - haveTimeoutAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.timeout", - contextNameAnswer), timeoutAnswer) - } - // make current context? for !haveMakeCurrentContextAnswer { term.Write([]byte("Make current context? ([yes]/no)")) @@ -316,14 +236,25 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { haveMoreContextsToAddAnswer = true } - // save entry data + // if you can't use these defaults, see `auth update-context` to change it later + defaultUrl := "https://api.liquidweb.com" + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.url", + contextNameAnswer), defaultUrl) + defaultTimeout := 90 + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.timeout", + contextNameAnswer), defaultTimeout) + defaultInsecure := false + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.insecure", + contextNameAnswer), defaultInsecure) + + // return entry data context := cmdTypes.AuthContext{ ContextName: contextNameAnswer, Username: usernameAnswer, Password: passwordAnswer, - Url: urlAnswer, - Insecure: insecureAnswer, - Timeout: timeoutAnswer, + Url: defaultUrl, + Insecure: defaultInsecure, + Timeout: defaultTimeout, } contexts = append(contexts, context) From 686c658bfb2a6c3634cf91688470bfb9aca7aeab Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 23:34:31 -0500 Subject: [PATCH 19/28] remove remnant of named return --- instance/api/api.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/instance/api/api.go b/instance/api/api.go index 4f83e4d..50abed4 100644 --- a/instance/api/api.go +++ b/instance/api/api.go @@ -30,10 +30,9 @@ import ( func New(viper *viper.Viper) (*LwCliApiClient, error) { if err := viper.ReadInConfig(); err != nil { strErr := strings.ToUpper(cast.ToString(err)) - if strings.Contains(strErr, "CONFIG FILE") && strings.Contains(strErr, "NOT FOUND IN") { - err = nil // no present yet config is not fatal here - } else { - return &LwCliApiClient{}, fmt.Errorf("%w Raw error: %s", errorTypes.InvalidConfigSyntax, err) + // this is only considered an error if there is a config file + if !strings.Contains(strErr, "CONFIG FILE") && !strings.Contains(strErr, "NOT FOUND IN") { + return &LwCliApiClient{}, fmt.Errorf("%w Raw error: %s", errorTypes.InvalidConfigSyntax, strErr) } } From e6271a9e205f0ce0f4115d1ecfc803c50ad14d08 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 23:41:20 -0500 Subject: [PATCH 20/28] rename this error for clarity --- instance/api/api.go | 4 ++-- instance/api/types.go | 2 +- types/errors/errorTypes.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/instance/api/api.go b/instance/api/api.go index 50abed4..3cd0bc1 100644 --- a/instance/api/api.go +++ b/instance/api/api.go @@ -30,9 +30,9 @@ import ( func New(viper *viper.Viper) (*LwCliApiClient, error) { if err := viper.ReadInConfig(); err != nil { strErr := strings.ToUpper(cast.ToString(err)) - // this is only considered an error if there is a config file + // only consider this an error here if there is a config file if !strings.Contains(strErr, "CONFIG FILE") && !strings.Contains(strErr, "NOT FOUND IN") { - return &LwCliApiClient{}, fmt.Errorf("%w Raw error: %s", errorTypes.InvalidConfigSyntax, strErr) + return &LwCliApiClient{}, fmt.Errorf("%w Raw error: %s", errorTypes.ErrorReadingConfig, strErr) } } diff --git a/instance/api/types.go b/instance/api/types.go index 795fc74..f1887c9 100644 --- a/instance/api/types.go +++ b/instance/api/types.go @@ -32,7 +32,7 @@ type LwCliApiClient struct { func (x LwCliApiClient) Call(method string, params interface{}) (got interface{}, err error) { if err = x.Viper.ReadInConfig(); err != nil { - err = fmt.Errorf("%w Raw error: %s", errorTypes.InvalidConfigSyntax, err) + err = fmt.Errorf("%w Raw error: %s", errorTypes.ErrorReadingConfig, err) return } diff --git a/types/errors/errorTypes.go b/types/errors/errorTypes.go index 6fd2be1..ce334e1 100644 --- a/types/errors/errorTypes.go +++ b/types/errors/errorTypes.go @@ -23,5 +23,5 @@ var LwCliInputError = errors.New("Invalid input; missing required paramater") var LwApiUnexpectedResponseStructure = errors.New("Unexpected API response structure when calling method") var UnknownTerminal = errors.New("unknown terminal") var MergeConfigError = errors.New("error merging configuration") -var InvalidConfigSyntax = errors.New("configuration contains invalid syntax; use 'auth init' to create a new configuration.") +var ErrorReadingConfig = errors.New("error reading configuration; use 'auth init' to create a new configuration.") var NoCurrentContext = errors.New("No current context is set; cannot continue.\nSee 'help auth' for assistance creating/deleting/modifying/setting contexts.") From 7610d408842ae8c7eb5b25518fc42b9e69e66c37 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Mon, 27 Jan 2020 23:48:49 -0500 Subject: [PATCH 21/28] no longer required.. moved to .Call() --- instance/api/api.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/instance/api/api.go b/instance/api/api.go index 3cd0bc1..8ba1ed2 100644 --- a/instance/api/api.go +++ b/instance/api/api.go @@ -17,25 +17,14 @@ package api import ( "fmt" - "strings" "github.com/spf13/cast" "github.com/spf13/viper" lwApi "github.com/liquidweb/go-lwApi" - - "github.com/liquidweb/liquidweb-cli/types/errors" ) func New(viper *viper.Viper) (*LwCliApiClient, error) { - if err := viper.ReadInConfig(); err != nil { - strErr := strings.ToUpper(cast.ToString(err)) - // only consider this an error here if there is a config file - if !strings.Contains(strErr, "CONFIG FILE") && !strings.Contains(strErr, "NOT FOUND IN") { - return &LwCliApiClient{}, fmt.Errorf("%w Raw error: %s", errorTypes.ErrorReadingConfig, strErr) - } - } - // create the object from the current context if there is one. If "auth init" has not yet been ran, // there would be no current context yet. lwCliApiClient := LwCliApiClient{Viper: viper} From 9a3dbc77e7ea054e432af780250f6d27a39997ba Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Tue, 28 Jan 2020 08:23:30 -0500 Subject: [PATCH 22/28] remove building out and returning this struct .. it ended up not being used anyway. --- cmd/authInit.go | 83 +++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 44 deletions(-) diff --git a/cmd/authInit.go b/cmd/authInit.go index b01cbd2..a5a1d8e 100644 --- a/cmd/authInit.go +++ b/cmd/authInit.go @@ -26,7 +26,6 @@ import ( "github.com/spf13/cast" "github.com/spf13/cobra" - "github.com/liquidweb/liquidweb-cli/types/cmd" "github.com/liquidweb/liquidweb-cli/types/errors" "github.com/liquidweb/liquidweb-cli/utils" ) @@ -49,7 +48,7 @@ func init() { } func setAuthDataInteractively() error { - _, writeConfig, err := fetchAuthDataInteractively() + writeConfig, err := fetchAuthDataInteractively() if err != nil { return err } @@ -63,15 +62,15 @@ func setAuthDataInteractively() error { return nil } -func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { - var contexts []cmdTypes.AuthContext - +func fetchAuthDataInteractively() (writeConfig bool, err error) { if !terminal.IsTerminal(0) || !terminal.IsTerminal(1) { - return contexts, false, errorTypes.UnknownTerminal + err = errorTypes.UnknownTerminal + return } - oldState, err := terminal.MakeRaw(0) - if err != nil { - return contexts, false, err + oldState, termMakeErr := terminal.MakeRaw(0) + if termMakeErr != nil { + err = termMakeErr + return } defer terminal.Restore(0, oldState) screen := struct { @@ -87,9 +86,10 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { var haveProceedAnswer bool for !haveProceedAnswer { term.Write([]byte("Warning: This will delete all auth contexts. Continue (yes/[no])?: ")) - proceedBytes, err := term.ReadLine() - if err != nil { - return contexts, false, err + proceedBytes, readErr := term.ReadLine() + if readErr != nil { + err = readErr + return } proceedString := cast.ToString(proceedBytes) if proceedString != "yes" && proceedString != "no" && proceedString != "" { @@ -108,14 +108,15 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { // return if user didnt acknowledge to proceed if !moreAdds { - return contexts, false, nil + return } // if user consented to proceed, clear config writeEmptyConfig() - cfgFile, err := getExpectedConfigPath() - if err != nil { - return contexts, false, err + cfgFile, cfgPathErr := getExpectedConfigPath() + if cfgPathErr != nil { + err = cfgPathErr + return } if utils.FileExists(cfgFile) { if err := os.Remove(cfgFile); err != nil { @@ -148,9 +149,10 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { // context name for !haveContextNameAnswer { term.Write([]byte("Name this context: ")) - contextNameBytes, err := term.ReadLine() - if err != nil { - return contexts, false, err + contextNameBytes, readErr := term.ReadLine() + if readErr != nil { + err = readErr + return } contextNameAnswer = cast.ToString(contextNameBytes) if contextNameAnswer == "" { @@ -165,9 +167,10 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { // username for !haveUsernameAnswer { term.Write([]byte("LiquidWeb username: ")) - usernameBytes, err := term.ReadLine() - if err != nil { - return contexts, false, err + usernameBytes, readErr := term.ReadLine() + if readErr != nil { + err = readErr + return } usernameAnswer = cast.ToString(usernameBytes) if usernameAnswer == "" { @@ -181,9 +184,10 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { // password for !havePasswordAnswer { - passwordBytes, err := term.ReadPassword("LiquidWeb password: ") - if err != nil { - return contexts, false, err + passwordBytes, readErr := term.ReadPassword("LiquidWeb password: ") + if readErr != nil { + err = readErr + return } passwordAnswer = cast.ToString(passwordBytes) if passwordAnswer == "" { @@ -198,9 +202,10 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { // make current context? for !haveMakeCurrentContextAnswer { term.Write([]byte("Make current context? ([yes]/no)")) - makeCurrentContextBytes, err := term.ReadLine() - if err != nil { - return contexts, false, err + makeCurrentContextBytes, readErr := term.ReadLine() + if readErr != nil { + err = readErr + return } makeCurrentContextString := cast.ToString(makeCurrentContextBytes) if makeCurrentContextString != "" && makeCurrentContextString != "yes" && makeCurrentContextString != "no" { @@ -217,9 +222,10 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { // more contexts to add ? for !haveMoreContextsToAddAnswer { term.Write([]byte("Add another context? (yes/[no]): ")) - moreContextsBytes, err := term.ReadLine() - if err != nil { - return contexts, false, err + moreContextsBytes, readErr := term.ReadLine() + if readErr != nil { + err = readErr + return } answer := cast.ToString(moreContextsBytes) @@ -246,21 +252,10 @@ func fetchAuthDataInteractively() ([]cmdTypes.AuthContext, bool, error) { defaultInsecure := false lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.insecure", contextNameAnswer), defaultInsecure) - - // return entry data - context := cmdTypes.AuthContext{ - ContextName: contextNameAnswer, - Username: usernameAnswer, - Password: passwordAnswer, - Url: defaultUrl, - Insecure: defaultInsecure, - Timeout: defaultTimeout, - } - - contexts = append(contexts, context) } - return contexts, true, err + writeConfig = true + return } func getExpectedConfigPath() (string, error) { From 3a6137ae602199ab5806ebe9ae3c5caea3366b0d Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Tue, 28 Jan 2020 09:38:58 -0500 Subject: [PATCH 23/28] use channels in 'auth init' --- cmd/authInit.go | 254 ++++++++++++++++++++++++++++-------------------- 1 file changed, 151 insertions(+), 103 deletions(-) diff --git a/cmd/authInit.go b/cmd/authInit.go index a5a1d8e..73fbdb9 100644 --- a/cmd/authInit.go +++ b/cmd/authInit.go @@ -134,126 +134,174 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { lwCliInst.Viper.ReadConfig(bytes.NewBuffer([]byte{})) } - for moreAdds { - var ( - contextNameAnswer string - haveContextNameAnswer bool - usernameAnswer string - haveUsernameAnswer bool - passwordAnswer string - havePasswordAnswer bool - haveMakeCurrentContextAnswer bool - haveMoreContextsToAddAnswer bool - ) - - // context name - for !haveContextNameAnswer { - term.Write([]byte("Name this context: ")) - contextNameBytes, readErr := term.ReadLine() - if readErr != nil { - err = readErr - return + // create context add loop channels + userInputComplete := make(chan bool) + userInputError := make(chan error) + userInputExitEarly := make(chan bool) + + term.Write([]byte("\nTo exit early, type 'exit' or send EOF (ctrl+d)\n\n")) + + // start context add loop + go func() { + WHILEMOREADDS: + for moreAdds { + var ( + contextNameAnswer string + haveContextNameAnswer bool + usernameAnswer string + haveUsernameAnswer bool + passwordAnswer string + havePasswordAnswer bool + haveMakeCurrentContextAnswer bool + haveMoreContextsToAddAnswer bool + ) + + // context name + for !haveContextNameAnswer { + term.Write([]byte("Name this context: ")) + contextNameBytes, err := term.ReadLine() + if err != nil { + userInputError <- err + break WHILEMOREADDS + } + contextNameAnswer = cast.ToString(contextNameBytes) + if contextNameAnswer == "exit" { + userInputExitEarly <- true + break WHILEMOREADDS + } else if contextNameAnswer == "" { + term.Write([]byte("context name cannot be blank.\n")) + } else { + haveContextNameAnswer = true + lwCliInst.Viper.Set(fmt.Sprintf( + "liquidweb.api.contexts.%s.contextname", contextNameAnswer), contextNameAnswer) + } } - contextNameAnswer = cast.ToString(contextNameBytes) - if contextNameAnswer == "" { - term.Write([]byte("context name cannot be blank.\n")) - } else { - haveContextNameAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf( - "liquidweb.api.contexts.%s.contextname", contextNameAnswer), contextNameAnswer) - } - } - // username - for !haveUsernameAnswer { - term.Write([]byte("LiquidWeb username: ")) - usernameBytes, readErr := term.ReadLine() - if readErr != nil { - err = readErr - return + // username + for !haveUsernameAnswer { + term.Write([]byte("LiquidWeb username: ")) + usernameBytes, err := term.ReadLine() + if err != nil { + userInputError <- err + break WHILEMOREADDS + } + usernameAnswer = cast.ToString(usernameBytes) + if usernameAnswer == "exit" { + userInputExitEarly <- true + break WHILEMOREADDS + } else if usernameAnswer == "" { + term.Write([]byte("username cannot be blank.\n")) + } else { + haveUsernameAnswer = true + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.username", + contextNameAnswer), usernameAnswer) + } } - usernameAnswer = cast.ToString(usernameBytes) - if usernameAnswer == "" { - term.Write([]byte("username cannot be blank.\n")) - } else { - haveUsernameAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.username", - contextNameAnswer), usernameAnswer) - } - } - // password - for !havePasswordAnswer { - passwordBytes, readErr := term.ReadPassword("LiquidWeb password: ") - if readErr != nil { - err = readErr - return + // password + for !havePasswordAnswer { + passwordBytes, err := term.ReadPassword("LiquidWeb password: ") + if err != nil { + userInputError <- err + break WHILEMOREADDS + } + passwordAnswer = cast.ToString(passwordBytes) + if passwordAnswer == "exit" { + userInputExitEarly <- true + break WHILEMOREADDS + } else if passwordAnswer == "" { + term.Write([]byte("password cannot be blank.\n")) + } else { + havePasswordAnswer = true + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.password", + contextNameAnswer), passwordAnswer) + } } - passwordAnswer = cast.ToString(passwordBytes) - if passwordAnswer == "" { - term.Write([]byte("password cannot be blank.\n")) - } else { - havePasswordAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.password", - contextNameAnswer), passwordAnswer) - } - } - // make current context? - for !haveMakeCurrentContextAnswer { - term.Write([]byte("Make current context? ([yes]/no)")) - makeCurrentContextBytes, readErr := term.ReadLine() - if readErr != nil { - err = readErr - return - } - makeCurrentContextString := cast.ToString(makeCurrentContextBytes) - if makeCurrentContextString != "" && makeCurrentContextString != "yes" && makeCurrentContextString != "no" { - term.Write([]byte("invalid input.\n")) - continue + // make current context? + for !haveMakeCurrentContextAnswer { + term.Write([]byte("Make current context? ([yes]/no)")) + makeCurrentContextBytes, err := term.ReadLine() + if err != nil { + userInputError <- err + break WHILEMOREADDS + } + makeCurrentContextString := cast.ToString(makeCurrentContextBytes) + if makeCurrentContextString == "exit" { + userInputExitEarly <- true + break WHILEMOREADDS + } + if makeCurrentContextString != "" && makeCurrentContextString != "yes" && makeCurrentContextString != "no" { + term.Write([]byte("invalid input.\n")) + continue + } + + haveMakeCurrentContextAnswer = true + if makeCurrentContextString == "yes" || makeCurrentContextString == "" { + lwCliInst.Viper.Set("liquidweb.api.current_context", contextNameAnswer) + } } - haveMakeCurrentContextAnswer = true - if makeCurrentContextString == "yes" || makeCurrentContextString == "" { - lwCliInst.Viper.Set("liquidweb.api.current_context", contextNameAnswer) + // more contexts to add ? + for !haveMoreContextsToAddAnswer { + term.Write([]byte("Add another context? (yes/[no]): ")) + moreContextsBytes, err := term.ReadLine() + if err != nil { + userInputError <- err + break WHILEMOREADDS + } + + answer := cast.ToString(moreContextsBytes) + if answer == "exit" { + userInputExitEarly <- true + break WHILEMOREADDS + } + if answer != "" && answer != "yes" && answer != "no" { + term.Write([]byte("invalid input.\n")) + continue + } + + if answer == "no" || answer == "" { + moreAdds = false + } + + haveMoreContextsToAddAnswer = true } + + // if you can't use these defaults, see `auth update-context` to change it later + defaultUrl := "https://api.liquidweb.com" + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.url", + contextNameAnswer), defaultUrl) + defaultTimeout := 90 + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.timeout", + contextNameAnswer), defaultTimeout) + defaultInsecure := false + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.insecure", + contextNameAnswer), defaultInsecure) } - // more contexts to add ? - for !haveMoreContextsToAddAnswer { - term.Write([]byte("Add another context? (yes/[no]): ")) - moreContextsBytes, readErr := term.ReadLine() - if readErr != nil { - err = readErr - return - } + // all done + userInputComplete <- true + }() - answer := cast.ToString(moreContextsBytes) - if answer != "" && answer != "yes" && answer != "no" { - term.Write([]byte("invalid input.\n")) - continue +WAIT: + for { + select { + case exitEarly := <-userInputExitEarly: + if exitEarly { + break WAIT } - - if answer == "no" || answer == "" { - moreAdds = false - haveMoreContextsToAddAnswer = true + case userInputErr := <-userInputError: + err = userInputErr + break WAIT + case complete := <-userInputComplete: + if complete { + break WAIT } - - haveMoreContextsToAddAnswer = true } - - // if you can't use these defaults, see `auth update-context` to change it later - defaultUrl := "https://api.liquidweb.com" - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.url", - contextNameAnswer), defaultUrl) - defaultTimeout := 90 - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.timeout", - contextNameAnswer), defaultTimeout) - defaultInsecure := false - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.insecure", - contextNameAnswer), defaultInsecure) } + // okay to write config if we get here because its already been destroyed per user ack writeConfig = true return } From 154e7b18e3d212b4c5ae6ae2ef2b8d8f94cfa542 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Tue, 28 Jan 2020 10:17:54 -0500 Subject: [PATCH 24/28] 'auth init': only write config if it seems valid --- cmd/authGetContexts.go | 3 ++ cmd/authInit.go | 110 +++++++++++++++++++++++++---------------- types/cmd/cmdTypes.go | 13 ++--- 3 files changed, 78 insertions(+), 48 deletions(-) diff --git a/cmd/authGetContexts.go b/cmd/authGetContexts.go index 76c2096..3479683 100644 --- a/cmd/authGetContexts.go +++ b/cmd/authGetContexts.go @@ -45,6 +45,9 @@ If you've never setup any contexts, check "auth init".`, fmt.Printf("\tInsecure: %t\n", context.Insecure) fmt.Printf("\tTimeout: %d\n", context.Timeout) } + + currentContext := lwCliInst.Viper.GetString("liquidweb.api.current_context") + fmt.Printf("Current context: [%s]\n", currentContext) }, } diff --git a/cmd/authInit.go b/cmd/authInit.go index 73fbdb9..e7547e0 100644 --- a/cmd/authInit.go +++ b/cmd/authInit.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/cast" "github.com/spf13/cobra" + "github.com/liquidweb/liquidweb-cli/types/cmd" "github.com/liquidweb/liquidweb-cli/types/errors" "github.com/liquidweb/liquidweb-cli/utils" ) @@ -111,33 +112,11 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { return } - // if user consented to proceed, clear config - writeEmptyConfig() - cfgFile, cfgPathErr := getExpectedConfigPath() - if cfgPathErr != nil { - err = cfgPathErr - return - } - if utils.FileExists(cfgFile) { - if err := os.Remove(cfgFile); err != nil { - lwCliInst.Die(err) - } - f, err := os.Create(cfgFile) - if err != nil { - lwCliInst.Die(err) - } - f.Close() - if err := os.Chmod(cfgFile, 0600); err != nil { - lwCliInst.Die(err) - } - - lwCliInst.Viper.ReadConfig(bytes.NewBuffer([]byte{})) - } - // create context add loop channels userInputComplete := make(chan bool) userInputError := make(chan error) userInputExitEarly := make(chan bool) + userInputContext := make(chan cmdTypes.AuthContext) term.Write([]byte("\nTo exit early, type 'exit' or send EOF (ctrl+d)\n\n")) @@ -146,6 +125,7 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { WHILEMOREADDS: for moreAdds { var ( + context cmdTypes.AuthContext contextNameAnswer string haveContextNameAnswer bool usernameAnswer string @@ -172,8 +152,7 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { term.Write([]byte("context name cannot be blank.\n")) } else { haveContextNameAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf( - "liquidweb.api.contexts.%s.contextname", contextNameAnswer), contextNameAnswer) + context.ContextName = contextNameAnswer } } @@ -193,8 +172,7 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { term.Write([]byte("username cannot be blank.\n")) } else { haveUsernameAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.username", - contextNameAnswer), usernameAnswer) + context.Username = usernameAnswer } } @@ -213,8 +191,7 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { term.Write([]byte("password cannot be blank.\n")) } else { havePasswordAnswer = true - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.password", - contextNameAnswer), passwordAnswer) + context.Password = passwordAnswer } } @@ -238,7 +215,7 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { haveMakeCurrentContextAnswer = true if makeCurrentContextString == "yes" || makeCurrentContextString == "" { - lwCliInst.Viper.Set("liquidweb.api.current_context", contextNameAnswer) + context.CurrentContext = true } } @@ -268,22 +245,20 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { haveMoreContextsToAddAnswer = true } - // if you can't use these defaults, see `auth update-context` to change it later - defaultUrl := "https://api.liquidweb.com" - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.url", - contextNameAnswer), defaultUrl) - defaultTimeout := 90 - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.timeout", - contextNameAnswer), defaultTimeout) - defaultInsecure := false - lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.insecure", - contextNameAnswer), defaultInsecure) + // when these defaults are unsuitable, see `auth update-context` to change it later + context.Url = "https://api.liquidweb.com" + context.Timeout = 90 + context.Insecure = false + + // send context over + userInputContext <- context } // all done userInputComplete <- true }() + var contexts []cmdTypes.AuthContext WAIT: for { select { @@ -294,15 +269,66 @@ WAIT: case userInputErr := <-userInputError: err = userInputErr break WAIT + case context := <-userInputContext: + contexts = append(contexts, context) case complete := <-userInputComplete: if complete { + // wipe the config for a clean slate. + writeEmptyConfig() + cfgFile, cfgPathErr := getExpectedConfigPath() + if cfgPathErr != nil { + err = cfgPathErr + return + } + if utils.FileExists(cfgFile) { + if err := os.Remove(cfgFile); err != nil { + lwCliInst.Die(err) + } + f, err := os.Create(cfgFile) + if err != nil { + lwCliInst.Die(err) + } + f.Close() + if err := os.Chmod(cfgFile, 0600); err != nil { + lwCliInst.Die(err) + } + + lwCliInst.Viper.ReadConfig(bytes.NewBuffer([]byte{})) + } + + // set Viper config from contexts slice + for _, context := range contexts { + // ContextName + lwCliInst.Viper.Set(fmt.Sprintf( + "liquidweb.api.contexts.%s.contextname", context.ContextName), context.ContextName) + // Username + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.username", + context.ContextName), context.Username) + // Password + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.password", + context.ContextName), context.Password) + // CurrentContext + if context.CurrentContext { + lwCliInst.Viper.Set("liquidweb.api.current_context", context.ContextName) + } + // Url + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.url", + context.ContextName), context.Url) + // Timeout + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.timeout", + context.ContextName), context.Timeout) + // Insecure + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s.insecure", + context.ContextName), context.Insecure) + } + + // no errors or early exits, so signify to write the config just set then break + writeConfig = true break WAIT } } } - // okay to write config if we get here because its already been destroyed per user ack - writeConfig = true return } diff --git a/types/cmd/cmdTypes.go b/types/cmd/cmdTypes.go index ec06f17..827dcc6 100644 --- a/types/cmd/cmdTypes.go +++ b/types/cmd/cmdTypes.go @@ -16,10 +16,11 @@ limitations under the License. package cmdTypes type AuthContext struct { - ContextName string `json:"contextname" mapstructure:"contextname"` - Username string `json:"username" mapstructure:"username"` - Password string `json:"password" mapstructure:"password"` - Url string `json:"url" mapstructure:"url"` - Insecure bool `json:"insecure" mapstructure:"insecure"` - Timeout int `json:"timeout" mapstructure:"timeout"` + CurrentContext bool `json:"currentcontext" mapstructure:"currentcontext"` + ContextName string `json:"contextname" mapstructure:"contextname"` + Username string `json:"username" mapstructure:"username"` + Password string `json:"password" mapstructure:"password"` + Url string `json:"url" mapstructure:"url"` + Insecure bool `json:"insecure" mapstructure:"insecure"` + Timeout int `json:"timeout" mapstructure:"timeout"` } From e11e14da6ecc9e049bba1bd93de5e828584ba13a Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Tue, 28 Jan 2020 10:56:53 -0500 Subject: [PATCH 25/28] validate auth context vals --- cmd/authInit.go | 13 +++++++++++++ cmd/authUpdateContext.go | 18 +++++++++++------- validate/types.go | 34 +++++++++++++++++++++++++++++----- validate/validate.go | 6 ++++++ 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/cmd/authInit.go b/cmd/authInit.go index e7547e0..26e1c2f 100644 --- a/cmd/authInit.go +++ b/cmd/authInit.go @@ -29,6 +29,7 @@ import ( "github.com/liquidweb/liquidweb-cli/types/cmd" "github.com/liquidweb/liquidweb-cli/types/errors" "github.com/liquidweb/liquidweb-cli/utils" + "github.com/liquidweb/liquidweb-cli/validate" ) var authInitCmd = &cobra.Command{ @@ -250,6 +251,18 @@ func fetchAuthDataInteractively() (writeConfig bool, err error) { context.Timeout = 90 context.Insecure = false + validateFields := map[interface{}]interface{}{ + context.Url: "HttpsLiquidwebUrl", + context.Timeout: "PositiveInt", + context.ContextName: "NonEmptyString", + context.Username: "NonEmptyString", + context.Password: "NonEmptyString", + } + if err := validate.Validate(validateFields); err != nil { + userInputError <- err + break WHILEMOREADDS + } + // send context over userInputContext <- context } diff --git a/cmd/authUpdateContext.go b/cmd/authUpdateContext.go index ec4524e..de4833d 100644 --- a/cmd/authUpdateContext.go +++ b/cmd/authUpdateContext.go @@ -17,12 +17,12 @@ package cmd import ( "fmt" - "strings" "github.com/spf13/cobra" "github.com/liquidweb/liquidweb-cli/instance" "github.com/liquidweb/liquidweb-cli/types/cmd" + "github.com/liquidweb/liquidweb-cli/validate" ) var authUpdateContextCmd = &cobra.Command{ @@ -59,23 +59,23 @@ If you've never setup any contexts, check "auth init".`, lwCliInst.Die(err) } + validateFields := map[interface{}]interface{}{} + if username != "" { authContext.Username = username + validateFields[username] = "NonEmptyString" } if password != "" { authContext.Password = password + validateFields[password] = "NonEmptyString" } if url != "" { - if !strings.HasPrefix(url, "https://") { - lwCliInst.Die(fmt.Errorf("given url [%s] appears invalid; should start with 'https://'", url)) - } authContext.Url = url + validateFields[url] = "HttpsLiquidwebUrl" } if timeout != -1 { - if timeout < 1 { - lwCliInst.Die(fmt.Errorf("given timeout [%d] appears invalid; its less than 1", timeout)) - } authContext.Timeout = timeout + validateFields[timeout] = "PositiveInt" } if setSecure { authContext.Insecure = false @@ -84,6 +84,10 @@ If you've never setup any contexts, check "auth init".`, authContext.Insecure = true } + if err := validate.Validate(validateFields); err != nil { + lwCliInst.Die(err) + } + lwCliInst.Viper.Set(fmt.Sprintf("liquidweb.api.contexts.%s", contextName), map[string]interface{}{ "contextname": contextName, "username": authContext.Username, diff --git a/validate/types.go b/validate/types.go index bba0d02..2737923 100644 --- a/validate/types.go +++ b/validate/types.go @@ -18,6 +18,7 @@ package validate import ( "errors" "fmt" + "net/url" "regexp" "strings" @@ -27,11 +28,12 @@ import ( var ValidationFailure = errors.New("validation failed") type InputTypes struct { - UniqId InputTypeUniqId - IP InputTypeIP - PositiveInt64 InputTypePositiveInt64 - PositiveInt InputTypePositiveInt - NonEmptyString InputTypeNonEmptyString + UniqId InputTypeUniqId + IP InputTypeIP + PositiveInt64 InputTypePositiveInt64 + PositiveInt InputTypePositiveInt + NonEmptyString InputTypeNonEmptyString + HttpsLiquidwebUrl InputTypeHttpsLiquidwebUrl } // UniqId @@ -121,3 +123,25 @@ func (x InputTypeNonEmptyString) Validate() error { return nil } + +// HttpsLiquidwebUrl + +type InputTypeHttpsLiquidwebUrl struct { + HttpsLiquidwebUrl string +} + +func (x InputTypeHttpsLiquidwebUrl) Validate() error { + if !strings.HasPrefix(x.HttpsLiquidwebUrl, "https://") { + return fmt.Errorf("given url [%s] appears invalid; should start with 'https://'", x.HttpsLiquidwebUrl) + } + + if !strings.Contains(x.HttpsLiquidwebUrl, "liquidweb.com") { + return fmt.Errorf("given url [%s] appears invalid; should contain 'liquidweb.com'", x.HttpsLiquidwebUrl) + } + + if _, err := url.ParseRequestURI(x.HttpsLiquidwebUrl); err != nil { + return fmt.Errorf("given url [%s] appears invalid; %s", x.HttpsLiquidwebUrl, err) + } + + return nil +} diff --git a/validate/validate.go b/validate/validate.go index 23190b6..540dd1b 100644 --- a/validate/validate.go +++ b/validate/validate.go @@ -127,6 +127,12 @@ func interfaceInputTypeValidate(iface, inputFieldValue interface{}) error { if err := obj.Validate(); err != nil { return err } + case InputTypeHttpsLiquidwebUrl: + var obj InputTypeHttpsLiquidwebUrl + obj.HttpsLiquidwebUrl = cast.ToString(inputFieldValue) + if err := obj.Validate(); err != nil { + return err + } default: return fmt.Errorf("bug: validation missing entry for %s", inputFieldValue) } From c31585ca055d6187a994663ec791cf5c80b87bb6 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Tue, 28 Jan 2020 11:53:59 -0500 Subject: [PATCH 26/28] prompt on destructive ops by default --- cmd/cloudImageDelete.go | 11 +++++++++++ cmd/cloudPrivateParentDelete.go | 11 +++++++++++ cmd/cloudServerDestroy.go | 11 +++++++++++ cmd/cloudStorageBlockVolumeDelete.go | 11 +++++++++++ cmd/cloudStorageObjectDelete.go | 11 +++++++++++ cmd/cloudStorageObjectDeleteKey.go | 11 +++++++++++ cmd/networkIpPoolDelete.go | 11 +++++++++++ cmd/root.go | 29 ++++++++++++++++++++++++++++ 8 files changed, 106 insertions(+) diff --git a/cmd/cloudImageDelete.go b/cmd/cloudImageDelete.go index 742ff1e..8ecebb0 100644 --- a/cmd/cloudImageDelete.go +++ b/cmd/cloudImageDelete.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" + "os" "github.com/spf13/cobra" @@ -29,6 +30,15 @@ var cloudImageDeleteCmd = &cobra.Command{ Long: `Delete a Cloud Image`, Run: func(cmd *cobra.Command, args []string) { imageIdFlag, _ := cmd.Flags().GetInt64("image_id") + forceFlag, _ := cmd.Flags().GetBool("force") + + // if force flag wasn't passed + if !forceFlag { + // exit if user didn't consent + if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + os.Exit(0) + } + } apiArgs := map[string]interface{}{"id": imageIdFlag} @@ -47,5 +57,6 @@ func init() { cloudImageDeleteCmd.Flags().Int64("image_id", -1, "id number of the image (see 'cloud image list')") + cloudImageDeleteCmd.Flags().Bool("force", false, "bypass dialog confirmation") cloudImageDeleteCmd.MarkFlagRequired("image_id") } diff --git a/cmd/cloudPrivateParentDelete.go b/cmd/cloudPrivateParentDelete.go index e1e9669..d9d84f3 100644 --- a/cmd/cloudPrivateParentDelete.go +++ b/cmd/cloudPrivateParentDelete.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" + "os" "github.com/spf13/cobra" @@ -34,6 +35,15 @@ Parents you have total control of how many instances can live on the Private Par as well as how many resources each Cloud Server gets.`, Run: func(cmd *cobra.Command, args []string) { nameFlag, _ := cmd.Flags().GetString("name") + forceFlag, _ := cmd.Flags().GetBool("force") + + // if force flag wasn't passed + if !forceFlag { + // exit if user didn't consent + if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + os.Exit(0) + } + } // if passed a private-parent flag, derive its uniq_id var privateParentUniqId string @@ -60,6 +70,7 @@ func init() { cloudPrivateParentCmd.AddCommand(cloudPrivateParentDeleteCmd) cloudPrivateParentDeleteCmd.Flags().String("name", "", "name or uniq_id of the Private Parent") + cloudPrivateParentDeleteCmd.Flags().Bool("force", false, "bypass dialog confirmation") cloudPrivateParentDeleteCmd.MarkFlagRequired("name") } diff --git a/cmd/cloudServerDestroy.go b/cmd/cloudServerDestroy.go index ed4e700..393398c 100644 --- a/cmd/cloudServerDestroy.go +++ b/cmd/cloudServerDestroy.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" + "os" "github.com/spf13/cobra" @@ -37,6 +38,15 @@ server.`, Run: func(cmd *cobra.Command, args []string) { commentFlag, _ := cmd.Flags().GetString("comment") reasonFlag, _ := cmd.Flags().GetString("reason") + forceFlag, _ := cmd.Flags().GetBool("force") + + // if force flag wasn't passed + if !forceFlag { + // exit if user didn't consent + if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + os.Exit(0) + } + } for _, uniqId := range cloudServerDestroyCmdUniqIdFlag { @@ -78,6 +88,7 @@ func init() { "comment related to the cancellation") cloudServerDestroyCmd.Flags().String("reason", "", "reason for the cancellation (optional)") + cloudServerDestroyCmd.Flags().Bool("force", false, "bypass dialog confirmation") cloudServerDestroyCmd.MarkFlagRequired("uniq_id") } diff --git a/cmd/cloudStorageBlockVolumeDelete.go b/cmd/cloudStorageBlockVolumeDelete.go index 32c56a6..dfabfaf 100644 --- a/cmd/cloudStorageBlockVolumeDelete.go +++ b/cmd/cloudStorageBlockVolumeDelete.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" + "os" "github.com/spf13/cobra" @@ -34,6 +35,15 @@ Once attached, volumes appear as normal block devices, and can be used as such. `, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + forceFlag, _ := cmd.Flags().GetBool("force") + + // if force flag wasn't passed + if !forceFlag { + // exit if user didn't consent + if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + os.Exit(0) + } + } validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", @@ -57,6 +67,7 @@ func init() { cloudStorageBlockVolumeCmd.AddCommand(cloudStorageBlockVolumeDeleteCmd) cloudStorageBlockVolumeDeleteCmd.Flags().String("uniq_id", "", "uniq_id of Cloud Block Storage volume") + cloudStorageBlockVolumeDeleteCmd.Flags().Bool("force", false, "bypass dialog confirmation") cloudStorageBlockVolumeDeleteCmd.MarkFlagRequired("uniq_id") } diff --git a/cmd/cloudStorageObjectDelete.go b/cmd/cloudStorageObjectDelete.go index 2d422ac..d8da27b 100644 --- a/cmd/cloudStorageObjectDelete.go +++ b/cmd/cloudStorageObjectDelete.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" + "os" "github.com/spf13/cobra" @@ -30,6 +31,15 @@ var cloudStorageObjectDeleteCmd = &cobra.Command{ Long: `Delete an Object Store`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + forceFlag, _ := cmd.Flags().GetBool("force") + + // if force flag wasn't passed + if !forceFlag { + // exit if user didn't consent + if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + os.Exit(0) + } + } validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", @@ -54,6 +64,7 @@ func init() { cloudStorageObjectCmd.AddCommand(cloudStorageObjectDeleteCmd) cloudStorageObjectDeleteCmd.Flags().String("uniq_id", "", "uniq_id of object store to delete (see 'cloud storage object list')") + cloudStorageObjectDeleteCmd.Flags().Bool("force", false, "bypass dialog confirmation") cloudStorageObjectDeleteCmd.MarkFlagRequired("uniq_id") } diff --git a/cmd/cloudStorageObjectDeleteKey.go b/cmd/cloudStorageObjectDeleteKey.go index c611d2d..ec3395b 100644 --- a/cmd/cloudStorageObjectDeleteKey.go +++ b/cmd/cloudStorageObjectDeleteKey.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" + "os" "github.com/spf13/cobra" @@ -31,6 +32,15 @@ var cloudStorageObjectDeleteKeyCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") accessKeyFlag, _ := cmd.Flags().GetString("access-key") + forceFlag, _ := cmd.Flags().GetBool("force") + + // if force flag wasn't passed + if !forceFlag { + // exit if user didn't consent + if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + os.Exit(0) + } + } validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", @@ -59,6 +69,7 @@ func init() { cloudStorageObjectCmd.AddCommand(cloudStorageObjectDeleteKeyCmd) cloudStorageObjectDeleteKeyCmd.Flags().String("uniq_id", "", "uniq_id of Object Store") cloudStorageObjectDeleteKeyCmd.Flags().String("access-key", "", "the access key to remove from the Object Store") + cloudStorageObjectDeleteKeyCmd.Flags().Bool("force", false, "bypass dialog confirmation") cloudStorageObjectDeleteKeyCmd.MarkFlagRequired("uniq_id") cloudStorageObjectDeleteKeyCmd.MarkFlagRequired("access-key") diff --git a/cmd/networkIpPoolDelete.go b/cmd/networkIpPoolDelete.go index bb467de..f46a773 100644 --- a/cmd/networkIpPoolDelete.go +++ b/cmd/networkIpPoolDelete.go @@ -17,6 +17,7 @@ package cmd import ( "fmt" + "os" "github.com/spf13/cobra" @@ -33,6 +34,15 @@ An IP Pool is a range of nonintersecting, reusable IP addresses reserved to your account.`, Run: func(cmd *cobra.Command, args []string) { uniqIdFlag, _ := cmd.Flags().GetString("uniq_id") + forceFlag, _ := cmd.Flags().GetBool("force") + + // if force flag wasn't passed + if !forceFlag { + // exit if user didn't consent + if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + os.Exit(0) + } + } validateFields := map[interface{}]interface{}{ uniqIdFlag: "UniqId", @@ -58,6 +68,7 @@ func init() { networkIpPoolCmd.AddCommand(networkIpPoolDeleteCmd) networkIpPoolDeleteCmd.Flags().String("uniq_id", "", "uniq_id of IP Pool") + networkIpPoolDeleteCmd.Flags().Bool("force", false, "bypass dialog confirmation") networkIpPoolDeleteCmd.MarkFlagRequired("uniq_id") } diff --git a/cmd/root.go b/cmd/root.go index d47ae60..9301b7b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,6 +16,7 @@ limitations under the License. package cmd import ( + "bufio" "fmt" "os" "strings" @@ -26,6 +27,7 @@ import ( "github.com/liquidweb/liquidweb-cli/instance" "github.com/liquidweb/liquidweb-cli/types/api" + "github.com/liquidweb/liquidweb-cli/utils" ) var cfgFile string @@ -155,3 +157,30 @@ func derivePrivateParentUniqId(name string) (string, error) { return privateParentUniqId, nil } + +func dialoagDesctructiveConfirmProceed() (proceed bool) { + + var haveConfirmationAnswer bool + utils.PrintTeal("Tip: Avoid future confirmations by passing --force\n\n") + + for !haveConfirmationAnswer { + utils.PrintRed("This is a destructive operation. Continue (yes/[no])?: ") + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + answer := scanner.Text() + + if answer != "" && answer != "yes" && answer != "no" { + utils.PrintYellow("invalid input.\n") + continue + } + + haveConfirmationAnswer = true + if answer == "no" || answer == "" { + proceed = false + } else if answer == "yes" { + proceed = true + } + } + + return +} From 32bcd13a7d8173d336d7faa18df21193564eb1e7 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Tue, 28 Jan 2020 12:27:50 -0500 Subject: [PATCH 27/28] spelling error --- cmd/cloudImageDelete.go | 2 +- cmd/cloudPrivateParentDelete.go | 2 +- cmd/cloudServerDestroy.go | 2 +- cmd/cloudStorageBlockVolumeDelete.go | 2 +- cmd/cloudStorageObjectDelete.go | 2 +- cmd/cloudStorageObjectDeleteKey.go | 2 +- cmd/networkIpPoolDelete.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/cloudImageDelete.go b/cmd/cloudImageDelete.go index 8ecebb0..1b95a2b 100644 --- a/cmd/cloudImageDelete.go +++ b/cmd/cloudImageDelete.go @@ -35,7 +35,7 @@ var cloudImageDeleteCmd = &cobra.Command{ // if force flag wasn't passed if !forceFlag { // exit if user didn't consent - if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + if proceed := dialogDesctructiveConfirmProceed(); !proceed { os.Exit(0) } } diff --git a/cmd/cloudPrivateParentDelete.go b/cmd/cloudPrivateParentDelete.go index d9d84f3..a801b6f 100644 --- a/cmd/cloudPrivateParentDelete.go +++ b/cmd/cloudPrivateParentDelete.go @@ -40,7 +40,7 @@ as well as how many resources each Cloud Server gets.`, // if force flag wasn't passed if !forceFlag { // exit if user didn't consent - if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + if proceed := dialogDesctructiveConfirmProceed(); !proceed { os.Exit(0) } } diff --git a/cmd/cloudServerDestroy.go b/cmd/cloudServerDestroy.go index 393398c..b8f1f97 100644 --- a/cmd/cloudServerDestroy.go +++ b/cmd/cloudServerDestroy.go @@ -43,7 +43,7 @@ server.`, // if force flag wasn't passed if !forceFlag { // exit if user didn't consent - if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + if proceed := dialogDesctructiveConfirmProceed(); !proceed { os.Exit(0) } } diff --git a/cmd/cloudStorageBlockVolumeDelete.go b/cmd/cloudStorageBlockVolumeDelete.go index dfabfaf..bf3384a 100644 --- a/cmd/cloudStorageBlockVolumeDelete.go +++ b/cmd/cloudStorageBlockVolumeDelete.go @@ -40,7 +40,7 @@ Once attached, volumes appear as normal block devices, and can be used as such. // if force flag wasn't passed if !forceFlag { // exit if user didn't consent - if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + if proceed := dialogDesctructiveConfirmProceed(); !proceed { os.Exit(0) } } diff --git a/cmd/cloudStorageObjectDelete.go b/cmd/cloudStorageObjectDelete.go index d8da27b..b4921d2 100644 --- a/cmd/cloudStorageObjectDelete.go +++ b/cmd/cloudStorageObjectDelete.go @@ -36,7 +36,7 @@ var cloudStorageObjectDeleteCmd = &cobra.Command{ // if force flag wasn't passed if !forceFlag { // exit if user didn't consent - if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + if proceed := dialogDesctructiveConfirmProceed(); !proceed { os.Exit(0) } } diff --git a/cmd/cloudStorageObjectDeleteKey.go b/cmd/cloudStorageObjectDeleteKey.go index ec3395b..0bc957c 100644 --- a/cmd/cloudStorageObjectDeleteKey.go +++ b/cmd/cloudStorageObjectDeleteKey.go @@ -37,7 +37,7 @@ var cloudStorageObjectDeleteKeyCmd = &cobra.Command{ // if force flag wasn't passed if !forceFlag { // exit if user didn't consent - if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + if proceed := dialogDesctructiveConfirmProceed(); !proceed { os.Exit(0) } } diff --git a/cmd/networkIpPoolDelete.go b/cmd/networkIpPoolDelete.go index f46a773..b4165d1 100644 --- a/cmd/networkIpPoolDelete.go +++ b/cmd/networkIpPoolDelete.go @@ -39,7 +39,7 @@ your account.`, // if force flag wasn't passed if !forceFlag { // exit if user didn't consent - if proceed := dialoagDesctructiveConfirmProceed(); !proceed { + if proceed := dialogDesctructiveConfirmProceed(); !proceed { os.Exit(0) } } From 7391ce94d96a539898d65a0a02eba2ef60f677e9 Mon Sep 17 00:00:00 2001 From: Scott Sullivan Date: Tue, 28 Jan 2020 12:31:33 -0500 Subject: [PATCH 28/28] missed the func dec --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 9301b7b..e99114e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -158,7 +158,7 @@ func derivePrivateParentUniqId(name string) (string, error) { return privateParentUniqId, nil } -func dialoagDesctructiveConfirmProceed() (proceed bool) { +func dialogDesctructiveConfirmProceed() (proceed bool) { var haveConfirmationAnswer bool utils.PrintTeal("Tip: Avoid future confirmations by passing --force\n\n")