From 8c59ade4edf19ad21615e5d4f82126c46331d1d2 Mon Sep 17 00:00:00 2001 From: Pavel Karpy Date: Mon, 27 Sep 2021 18:36:14 +0300 Subject: [PATCH] [#854] cli: Do not use global flags Also delete `ttl` and `xhdr` flags from `accounting balance` command and refactor command initialization. Signed-off-by: Pavel Karpy --- cmd/neofs-cli/modules/accounting.go | 28 ++++- cmd/neofs-cli/modules/container.go | 140 ++++++++++++++------- cmd/neofs-cli/modules/control.go | 57 ++++++--- cmd/neofs-cli/modules/netmap.go | 25 +++- cmd/neofs-cli/modules/object.go | 172 +++++++++++++++++++------- cmd/neofs-cli/modules/root.go | 135 +++++++++++++------- cmd/neofs-cli/modules/storagegroup.go | 75 ++++++++--- cmd/neofs-cli/modules/util.go | 149 +++++++++++++++------- 8 files changed, 562 insertions(+), 219 deletions(-) diff --git a/cmd/neofs-cli/modules/accounting.go b/cmd/neofs-cli/modules/accounting.go index ba88d8a7..5c4c9042 100644 --- a/cmd/neofs-cli/modules/accounting.go +++ b/cmd/neofs-cli/modules/accounting.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg/accounting" "github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var ( @@ -19,6 +20,16 @@ var accountingCmd = &cobra.Command{ Use: "accounting", Short: "Operations with accounts and balances", Long: `Operations with accounts and balances`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + flags := cmd.Flags() + + _ = viper.BindPFlag(binaryKey, flags.Lookup(binaryKey)) + _ = viper.BindPFlag(walletPath, flags.Lookup(walletPath)) + _ = viper.BindPFlag(wif, flags.Lookup(wif)) + _ = viper.BindPFlag(address, flags.Lookup(address)) + _ = viper.BindPFlag(rpc, flags.Lookup(rpc)) + _ = viper.BindPFlag(verbose, flags.Lookup(verbose)) + }, } var accountingBalanceCmd = &cobra.Command{ @@ -57,6 +68,19 @@ var accountingBalanceCmd = &cobra.Command{ }, } +func initAccountingBalanceCmd() { + ff := accountingBalanceCmd.Flags() + + ff.StringP(binaryKey, binaryKeyShorthand, binaryKeyDefault, binaryKeyUsage) + ff.StringP(walletPath, walletPathShorthand, walletPathDefault, walletPathUsage) + ff.StringP(wif, wifShorthand, wifDefault, wifUsage) + ff.StringP(address, addressShorthand, addressDefault, addressUsage) + ff.StringP(rpc, rpcShorthand, rpcDefault, rpcUsage) + ff.BoolP(verbose, verboseShorthand, verboseDefault, verboseUsage) + + accountingBalanceCmd.Flags().StringVar(&balanceOwner, "owner", "", "owner of balance account (omit to use owner from private key)") +} + func init() { rootCmd.AddCommand(accountingCmd) accountingCmd.AddCommand(accountingBalanceCmd) @@ -71,7 +95,7 @@ func init() { // is called directly, e.g.: // accountingCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - accountingBalanceCmd.Flags().StringVar(&balanceOwner, "owner", "", "owner of balance account (omit to use owner from private key)") + initAccountingBalanceCmd() } func prettyPrintDecimal(cmd *cobra.Command, decimal *accounting.Decimal) { @@ -79,7 +103,7 @@ func prettyPrintDecimal(cmd *cobra.Command, decimal *accounting.Decimal) { return } - if verbose { + if viper.GetBool(verbose) { cmd.Println("value:", decimal.Value()) cmd.Println("precision:", decimal.Precision()) } else { diff --git a/cmd/neofs-cli/modules/container.go b/cmd/neofs-cli/modules/container.go index 7e3f0547..8d630d9a 100644 --- a/cmd/neofs-cli/modules/container.go +++ b/cmd/neofs-cli/modules/container.go @@ -79,6 +79,11 @@ var containerCmd = &cobra.Command{ Use: "container", Short: "Operations with containers", Long: "Operations with containers", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // bind exactly that cmd's flags to + // the viper before execution + bindCommonFlags(cmd) + }, } var listContainersCmd = &cobra.Command{ @@ -429,15 +434,89 @@ Container ID in EACL table will be substituted with ID from the CLI.`, }, } +func initContainerListContainersCmd() { + initCommonFlags(listContainersCmd) + + flags := listContainersCmd.Flags() + + flags.StringVar(&containerOwner, "owner", "", "owner of containers (omit to use owner from private key)") +} + +func initContainerCreateCmd() { + initCommonFlags(createContainerCmd) + + flags := createContainerCmd.Flags() + + flags.StringVar(&containerACL, "basic-acl", basicACLPrivate, fmt.Sprintf("hex encoded basic ACL value or keywords '%s', '%s', '%s'", basicACLPublic, basicACLPrivate, basicACLReadOnly)) + flags.StringVarP(&containerPolicy, "policy", "p", "", "QL-encoded or JSON-encoded placement policy or path to file with it") + flags.StringSliceVarP(&containerAttributes, "attributes", "a", nil, "comma separated pairs of container attributes in form of Key1=Value1,Key2=Value2") + flags.StringVarP(&containerNonce, "nonce", "n", "", "UUIDv4 nonce value for container") + flags.BoolVar(&containerAwait, "await", false, "block execution until container is persisted") + flags.StringVar(&containerName, "name", "", "container name attribute") + flags.BoolVar(&containerNoTimestamp, "disable-timestamp", false, "disable timestamp container attribute") +} + +func initContainerDeleteCmd() { + initCommonFlags(deleteContainerCmd) + + flags := deleteContainerCmd.Flags() + + flags.StringVar(&containerID, "cid", "", "container ID") + flags.BoolVar(&containerAwait, "await", false, "block execution until container is removed") +} + +func initContainerListObjectsCmd() { + initCommonFlags(listContainerObjectsCmd) + + flags := listContainerObjectsCmd.Flags() + + flags.StringVar(&containerID, "cid", "", "container ID") +} + +func initContainerInfoCmd() { + initCommonFlags(getContainerInfoCmd) + + flags := getContainerInfoCmd.Flags() + + flags.StringVar(&containerID, "cid", "", "container ID") + flags.StringVar(&containerPathTo, "to", "", "path to dump encoded container") + flags.StringVar(&containerPathFrom, "from", "", "path to file with encoded container") + flags.BoolVar(&containerJSON, "json", false, "print or dump container in JSON format") +} + +func initContainerGetEACLCmd() { + initCommonFlags(getExtendedACLCmd) + + flags := getExtendedACLCmd.Flags() + + flags.StringVar(&containerID, "cid", "", "container ID") + flags.StringVar(&containerPathTo, "to", "", "path to dump encoded container (default: binary encoded)") + flags.BoolVar(&containerJSON, "json", false, "encode EACL table in json format") +} + +func initContainerSetEACLCmd() { + initCommonFlags(setExtendedACLCmd) + + flags := setExtendedACLCmd.Flags() + flags.StringVar(&containerID, "cid", "", "container ID") + flags.StringVar(&eaclPathFrom, "table", "", "path to file with JSON or binary encoded EACL table") + flags.BoolVar(&containerAwait, "await", false, "block execution until EACL is persisted") +} + func init() { + containerChildCommand := []*cobra.Command{ + listContainersCmd, + createContainerCmd, + deleteContainerCmd, + listContainerObjectsCmd, + getContainerInfoCmd, + getExtendedACLCmd, + setExtendedACLCmd, + } + rootCmd.AddCommand(containerCmd) - containerCmd.AddCommand(listContainersCmd) - containerCmd.AddCommand(createContainerCmd) - containerCmd.AddCommand(deleteContainerCmd) - containerCmd.AddCommand(listContainerObjectsCmd) - containerCmd.AddCommand(getContainerInfoCmd) - containerCmd.AddCommand(getExtendedACLCmd) - containerCmd.AddCommand(setExtendedACLCmd) + + containerCmd.AddCommand(containerChildCommand...) // Here you will define your flags and configuration settings. @@ -449,43 +528,20 @@ func init() { // is called directly, e.g.: // containerCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - // container list - listContainersCmd.Flags().StringVar(&containerOwner, "owner", "", "owner of containers (omit to use owner from private key)") + initContainerListContainersCmd() + initContainerCreateCmd() + initContainerDeleteCmd() + initContainerListObjectsCmd() + initContainerInfoCmd() + initContainerGetEACLCmd() + initContainerSetEACLCmd() - // container create - createContainerCmd.Flags().StringVar(&containerACL, "basic-acl", basicACLPrivate, - fmt.Sprintf("hex encoded basic ACL value or keywords '%s', '%s', '%s'", basicACLPublic, basicACLPrivate, basicACLReadOnly)) - createContainerCmd.Flags().StringVarP(&containerPolicy, "policy", "p", "", - "QL-encoded or JSON-encoded placement policy or path to file with it") - createContainerCmd.Flags().StringSliceVarP(&containerAttributes, "attributes", "a", nil, - "comma separated pairs of container attributes in form of Key1=Value1,Key2=Value2") - createContainerCmd.Flags().StringVarP(&containerNonce, "nonce", "n", "", "UUIDv4 nonce value for container") - createContainerCmd.Flags().BoolVar(&containerAwait, "await", false, "block execution until container is persisted") - createContainerCmd.Flags().StringVar(&containerName, "name", "", "container name attribute") - createContainerCmd.Flags().BoolVar(&containerNoTimestamp, "disable-timestamp", false, "disable timestamp container attribute") + for _, containerCommand := range containerChildCommand { + flags := containerCommand.Flags() - // container delete - deleteContainerCmd.Flags().StringVar(&containerID, "cid", "", "container ID") - deleteContainerCmd.Flags().BoolVar(&containerAwait, "await", false, "block execution until container is removed") - - // container list-object - listContainerObjectsCmd.Flags().StringVar(&containerID, "cid", "", "container ID") - - // container get - getContainerInfoCmd.Flags().StringVar(&containerID, "cid", "", "container ID") - getContainerInfoCmd.Flags().StringVar(&containerPathTo, "to", "", "path to dump encoded container") - getContainerInfoCmd.Flags().StringVar(&containerPathFrom, "from", "", "path to file with encoded container") - getContainerInfoCmd.Flags().BoolVar(&containerJSON, "json", false, "print or dump container in JSON format") - - // container get-eacl - getExtendedACLCmd.Flags().StringVar(&containerID, "cid", "", "container ID") - getExtendedACLCmd.Flags().StringVar(&containerPathTo, "to", "", "path to dump encoded container (default: binary encoded)") - getExtendedACLCmd.Flags().BoolVar(&containerJSON, "json", false, "encode EACL table in json format") - - // container set-eacl - setExtendedACLCmd.Flags().StringVar(&containerID, "cid", "", "container ID") - setExtendedACLCmd.Flags().StringVar(&eaclPathFrom, "table", "", "path to file with JSON or binary encoded EACL table") - setExtendedACLCmd.Flags().BoolVar(&containerAwait, "await", false, "block execution until EACL is persisted") + flags.StringSliceVarP(&xHeaders, xHeadersKey, xHeadersShorthand, xHeadersDefault, xHeadersUsage) + flags.Uint32P(ttl, ttlShorthand, ttlDefault, ttlUsage) + } for _, cmd := range []*cobra.Command{ createContainerCmd, diff --git a/cmd/neofs-cli/modules/control.go b/cmd/neofs-cli/modules/control.go index 3aa227d1..8be2e881 100644 --- a/cmd/neofs-cli/modules/control.go +++ b/cmd/neofs-cli/modules/control.go @@ -53,6 +53,41 @@ var ( var netmapStatus string +func initControlHealthCheckCmd() { + initCommonFlags(healthCheckCmd) + + healthCheckCmd.Flags().BoolVar(&healthCheckIRVar, healthcheckIRFlag, false, "Communicate with IR node") +} + +func initControlSetNetmapStatusCmd() { + initCommonFlags(setNetmapStatusCmd) + + setNetmapStatusCmd.Flags().StringVarP(&netmapStatus, netmapStatusFlag, "", "", + fmt.Sprintf("new netmap status keyword ('%s', '%s')", + netmapStatusOnline, + netmapStatusOffline, + ), + ) + + _ = setNetmapStatusCmd.MarkFlagRequired(netmapStatusFlag) +} + +func initControlDropObjectsCmd() { + initCommonFlags(dropObjectsCmd) + + dropObjectsCmd.Flags().StringSliceVarP(&dropObjectsList, dropObjectsFlag, "o", nil, + "List of object addresses to be removed in string format") + + _ = dropObjectsCmd.MarkFlagRequired(dropObjectsFlag) +} + +func initControlSnapshotCmd() { + initCommonFlags(snapshotCmd) + + snapshotCmd.Flags().BoolVar(&netmapSnapshotJSON, "json", false, + "print netmap structure in JSON format") +} + func init() { rootCmd.AddCommand(controlCmd) @@ -63,24 +98,10 @@ func init() { snapshotCmd, ) - setNetmapStatusCmd.Flags().StringVarP(&netmapStatus, netmapStatusFlag, "", "", - fmt.Sprintf("new netmap status keyword ('%s', '%s')", - netmapStatusOnline, - netmapStatusOffline, - ), - ) - - _ = setNetmapStatusCmd.MarkFlagRequired(netmapStatusFlag) - - dropObjectsCmd.Flags().StringSliceVarP(&dropObjectsList, dropObjectsFlag, "o", nil, - "List of object addresses to be removed in string format") - - _ = dropObjectsCmd.MarkFlagRequired(dropObjectsFlag) - - healthCheckCmd.Flags().BoolVar(&healthCheckIRVar, healthcheckIRFlag, false, "Communicate with IR node") - - snapshotCmd.Flags().BoolVar(&netmapSnapshotJSON, "json", false, - "print netmap structure in JSON format") + initControlHealthCheckCmd() + initControlSetNetmapStatusCmd() + initControlDropObjectsCmd() + initControlSnapshotCmd() } func healthCheck(cmd *cobra.Command, _ []string) { diff --git a/cmd/neofs-cli/modules/netmap.go b/cmd/neofs-cli/modules/netmap.go index 1be83c6a..79e1ad2d 100644 --- a/cmd/neofs-cli/modules/netmap.go +++ b/cmd/neofs-cli/modules/netmap.go @@ -22,18 +22,35 @@ var netmapCmd = &cobra.Command{ Use: "netmap", Short: "Operations with Network Map", Long: `Operations with Network Map`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // bind exactly that cmd's flags to + // the viper before execution + bindCommonFlags(cmd) + }, } func init() { - rootCmd.AddCommand(netmapCmd) - - netmapCmd.AddCommand( + netmapChildCommands := []*cobra.Command{ getEpochCmd, localNodeInfoCmd, netInfoCmd, - ) + } + rootCmd.AddCommand(netmapCmd) + netmapCmd.AddCommand(netmapChildCommands...) + + initCommonFlags(getEpochCmd) + initCommonFlags(netInfoCmd) + + initCommonFlags(localNodeInfoCmd) localNodeInfoCmd.Flags().BoolVar(&nodeInfoJSON, "json", false, "print node info in JSON format") + + for _, netmapCommand := range netmapChildCommands { + flags := netmapCommand.Flags() + + flags.StringSliceVarP(&xHeaders, xHeadersKey, xHeadersShorthand, xHeadersDefault, xHeadersUsage) + flags.Uint32P(ttl, ttlShorthand, ttlDefault, ttlUsage) + } } var getEpochCmd = &cobra.Command{ diff --git a/cmd/neofs-cli/modules/object.go b/cmd/neofs-cli/modules/object.go index 53602a8d..c69a02a9 100644 --- a/cmd/neofs-cli/modules/object.go +++ b/cmd/neofs-cli/modules/object.go @@ -42,6 +42,11 @@ var ( Use: "object", Short: "Operations with Objects", Long: `Operations with Objects`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // bind exactly that cmd's flags to + // the viper before execution + bindCommonFlags(cmd) + }, } objectPutCmd = &cobra.Command{ @@ -114,74 +119,139 @@ const putExpiresOnFlag = "expires-on" var putExpiredOn uint64 -func init() { - rootCmd.AddCommand(objectCmd) - objectCmd.PersistentFlags().String("bearer", "", "File with signed JSON or binary encoded bearer token") +func initObjectPutCmd() { + initCommonFlags(objectPutCmd) - objectCmd.AddCommand(objectPutCmd) - objectPutCmd.Flags().String("file", "", "File with object payload") + flags := objectPutCmd.Flags() + + flags.String("file", "", "File with object payload") _ = objectPutCmd.MarkFlagFilename("file") _ = objectPutCmd.MarkFlagRequired("file") - objectPutCmd.Flags().String("cid", "", "Container ID") + + flags.String("cid", "", "Container ID") _ = objectPutCmd.MarkFlagRequired("cid") - objectPutCmd.Flags().String("attributes", "", "User attributes in form of Key1=Value1,Key2=Value2") - objectPutCmd.Flags().Bool("disable-filename", false, "Do not set well-known filename attribute") - objectPutCmd.Flags().Bool("disable-timestamp", false, "Do not set well-known timestamp attribute") - objectPutCmd.Flags().Uint64VarP(&putExpiredOn, putExpiresOnFlag, "e", 0, - "Last epoch in the life of the object") - objectCmd.AddCommand(objectDelCmd) - objectDelCmd.Flags().String("cid", "", "Container ID") + flags.String("attributes", "", "User attributes in form of Key1=Value1,Key2=Value2") + flags.Bool("disable-filename", false, "Do not set well-known filename attribute") + flags.Bool("disable-timestamp", false, "Do not set well-known timestamp attribute") + flags.Uint64VarP(&putExpiredOn, putExpiresOnFlag, "e", 0, "Last epoch in the life of the object") +} + +func initObjectDeleteCmd() { + initCommonFlags(objectDelCmd) + + flags := objectDelCmd.Flags() + + flags.String("cid", "", "Container ID") _ = objectDelCmd.MarkFlagRequired("cid") - objectDelCmd.Flags().String("oid", "", "Object ID") + + flags.String("oid", "", "Object ID") _ = objectDelCmd.MarkFlagRequired("oid") +} - objectCmd.AddCommand(objectGetCmd) - objectGetCmd.Flags().String("file", "", "File to write object payload to. Default: stdout.") - objectGetCmd.Flags().String("header", "", "File to write header to. Default: stdout.") - objectGetCmd.Flags().String("cid", "", "Container ID") +func initObjectGetCmd() { + initCommonFlags(objectGetCmd) + + flags := objectGetCmd.Flags() + + flags.String("cid", "", "Container ID") _ = objectGetCmd.MarkFlagRequired("cid") - objectGetCmd.Flags().String("oid", "", "Object ID") + + flags.String("oid", "", "Object ID") _ = objectGetCmd.MarkFlagRequired("oid") - objectGetCmd.Flags().Bool(rawFlag, false, rawFlagDesc) - objectCmd.AddCommand(objectSearchCmd) - objectSearchCmd.Flags().String("cid", "", "Container ID") + flags.String("file", "", "File to write object payload to. Default: stdout.") + flags.String("header", "", "File to write header to. Default: stdout.") + flags.Bool(rawFlag, false, rawFlagDesc) +} + +func initObjectSearchCmd() { + initCommonFlags(objectSearchCmd) + + flags := objectSearchCmd.Flags() + + flags.String("cid", "", "Container ID") _ = objectSearchCmd.MarkFlagRequired("cid") - objectSearchCmd.Flags().StringSliceVarP(&searchFilters, "filters", "f", nil, + + flags.StringSliceVarP(&searchFilters, "filters", "f", nil, "Repeated filter expressions or files with protobuf JSON") - objectSearchCmd.Flags().Bool("root", false, "Search for user objects") - objectSearchCmd.Flags().Bool("phy", false, "Search physically stored objects") - objectSearchCmd.Flags().String(searchOIDFlag, "", "Search object by identifier") - objectCmd.AddCommand(objectHeadCmd) - objectHeadCmd.Flags().String("file", "", "File to write header to. Default: stdout.") - objectHeadCmd.Flags().String("cid", "", "Container ID") + flags.Bool("root", false, "Search for user objects") + flags.Bool("phy", false, "Search physically stored objects") + flags.String(searchOIDFlag, "", "Search object by identifier") +} + +func initObjectHeadCmd() { + initCommonFlags(objectHeadCmd) + + flags := objectHeadCmd.Flags() + + flags.String("cid", "", "Container ID") _ = objectHeadCmd.MarkFlagRequired("cid") - objectHeadCmd.Flags().String("oid", "", "Object ID") + + flags.String("oid", "", "Object ID") _ = objectHeadCmd.MarkFlagRequired("oid") - objectHeadCmd.Flags().Bool("main-only", false, "Return only main fields") - objectHeadCmd.Flags().Bool("json", false, "Marshal output in JSON") - objectHeadCmd.Flags().Bool("proto", false, "Marshal output in Protobuf") - objectHeadCmd.Flags().Bool(rawFlag, false, rawFlagDesc) - objectCmd.AddCommand(objectHashCmd) - objectHashCmd.Flags().String("cid", "", "Container ID") + flags.String("file", "", "File to write header to. Default: stdout.") + flags.Bool("main-only", false, "Return only main fields") + flags.Bool("json", false, "Marshal output in JSON") + flags.Bool("proto", false, "Marshal output in Protobuf") + flags.Bool(rawFlag, false, rawFlagDesc) +} + +func initObjectHashCmd() { + initCommonFlags(objectHashCmd) + + flags := objectHashCmd.Flags() + + flags.String("cid", "", "Container ID") _ = objectHashCmd.MarkFlagRequired("cid") - objectHashCmd.Flags().String("oid", "", "Object ID") - _ = objectHashCmd.MarkFlagRequired("oid") - objectHashCmd.Flags().String("range", "", "Range to take hash from in the form offset1:length1,...") - objectHashCmd.Flags().String("type", hashSha256, "Hash type. Either 'sha256' or 'tz'") - objectHashCmd.Flags().String(getRangeHashSaltFlag, "", "Salt in hex format") - objectCmd.AddCommand(objectRangeCmd) - objectRangeCmd.Flags().String("cid", "", "Container ID") + flags.String("oid", "", "Object ID") + _ = objectHashCmd.MarkFlagRequired("oid") + + flags.String("range", "", "Range to take hash from in the form offset1:length1,...") + flags.String("type", hashSha256, "Hash type. Either 'sha256' or 'tz'") + flags.String(getRangeHashSaltFlag, "", "Salt in hex format") +} + +func initObjectRangeCmd() { + initCommonFlags(objectRangeCmd) + + flags := objectRangeCmd.Flags() + + flags.String("cid", "", "Container ID") _ = objectRangeCmd.MarkFlagRequired("cid") - objectRangeCmd.Flags().String("oid", "", "Object ID") + + flags.String("oid", "", "Object ID") _ = objectRangeCmd.MarkFlagRequired("oid") - objectRangeCmd.Flags().String("range", "", "Range to take data from in the form offset:length") - objectRangeCmd.Flags().String("file", "", "File to write object payload to. Default: stdout.") - objectRangeCmd.Flags().Bool(rawFlag, false, rawFlagDesc) + + flags.String("range", "", "Range to take data from in the form offset:length") + flags.String("file", "", "File to write object payload to. Default: stdout.") + flags.Bool(rawFlag, false, rawFlagDesc) +} + +func init() { + objectChildCommands := []*cobra.Command{ + objectPutCmd, + objectDelCmd, + objectGetCmd, + objectSearchCmd, + objectHeadCmd, + objectHashCmd, + objectRangeCmd, + } + + rootCmd.AddCommand(objectCmd) + objectCmd.AddCommand(objectChildCommands...) + + for _, objCommand := range objectChildCommands { + flags := objCommand.Flags() + + flags.String("bearer", "", "File with signed JSON or binary encoded bearer token") + flags.StringSliceVarP(&xHeaders, xHeadersKey, xHeadersShorthand, xHeadersDefault, xHeadersUsage) + flags.Uint32P(ttl, ttlShorthand, ttlDefault, ttlUsage) + } // Here you will define your flags and configuration settings. @@ -192,6 +262,14 @@ func init() { // Cobra supports local flags which will only run when this command // is called directly, e.g.: // objectCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + + initObjectPutCmd() + initObjectDeleteCmd() + initObjectGetCmd() + initObjectSearchCmd() + initObjectHeadCmd() + initObjectHashCmd() + initObjectRangeCmd() } func initSession(ctx context.Context, key *ecdsa.PrivateKey) (client.Client, *session.Token, error) { diff --git a/cmd/neofs-cli/modules/root.go b/cmd/neofs-cli/modules/root.go index c88e3170..7cf1bb68 100644 --- a/cmd/neofs-cli/modules/root.go +++ b/cmd/neofs-cli/modules/root.go @@ -26,8 +26,6 @@ import ( const ( envPrefix = "NEOFS_CLI" - - ttlDefaultValue = 2 ) const xHeadersFlag = "xhdr" @@ -37,9 +35,58 @@ var xHeaders []string // Global scope flags. var ( cfgFile string - verbose bool ) +const ( + // Common CLI flag keys, shorthands, default + // values and their usage descriptions. + generateKey = "generate-key" + generateKeyShorthand = "" + generateKeyDefault = false + generateKeyUsage = "generate new private key" + + binaryKey = "binary-key" + binaryKeyShorthand = "" + binaryKeyDefault = "" + binaryKeyUsage = "path to the raw private key file" + + walletPath = "wallet" + walletPathShorthand = "w" + walletPathDefault = "" + walletPathUsage = "path to the wallet" + + wif = "wif" + wifShorthand = "" + wifDefault = "" + wifUsage = "WIF or NEP-2" + + address = "address" + addressShorthand = "" + addressDefault = "" + addressUsage = "address of wallet account" + + rpc = "rpc-endpoint" + rpcShorthand = "r" + rpcDefault = "" + rpcUsage = "remote node address (as 'multiaddr' or ':')" + + verbose = "verbose" + verboseShorthand = "v" + verboseDefault = false + verboseUsage = "verbose output" + + ttl = "ttl" + ttlShorthand = "" + ttlDefault = 2 + ttlUsage = "TTL value in request meta header" + + xHeadersKey = "xhdr" + xHeadersShorthand = "x" + xHeadersUsage = "Request X-Headers in form of Key=Value" +) + +var xHeadersDefault []string + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "neofs-cli", @@ -71,42 +118,14 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - // use stdout as default output for cmd.Print() rootCmd.SetOut(os.Stdout) + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.config/neofs-cli/config.yaml)") - // Key options. - rootCmd.PersistentFlags().BoolP("generate-key", "", false, "generate new private key") - _ = viper.BindPFlag("generate-key", rootCmd.PersistentFlags().Lookup("generate-key")) - - rootCmd.PersistentFlags().StringP("binary-key", "", "", "path to the raw private key file") - _ = viper.BindPFlag("binary-key", rootCmd.PersistentFlags().Lookup("binary-key")) - - rootCmd.PersistentFlags().StringP("wif", "", "", "WIF or NEP-2") - _ = viper.BindPFlag("wif", rootCmd.PersistentFlags().Lookup("wif")) - - rootCmd.PersistentFlags().StringP("wallet", "w", "", "path to the wallet") - _ = viper.BindPFlag("wallet", rootCmd.PersistentFlags().Lookup("wallet")) - rootCmd.PersistentFlags().StringP("address", "", "", "address of wallet account") - _ = viper.BindPFlag("address", rootCmd.PersistentFlags().Lookup("address")) - - rootCmd.PersistentFlags().StringP("rpc-endpoint", "r", "", "remote node address (as 'multiaddr' or ':')") - _ = viper.BindPFlag("rpc", rootCmd.PersistentFlags().Lookup("rpc-endpoint")) - - rootCmd.PersistentFlags().Uint32("ttl", ttlDefaultValue, "TTL value in request meta header") - _ = viper.BindPFlag("ttl", rootCmd.PersistentFlags().Lookup("ttl")) - - rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") - - rootCmd.PersistentFlags().StringSliceVarP(&xHeaders, xHeadersFlag, "x", nil, - "Request X-Headers in form of Key=Value") - _ = viper.BindPFlag(xHeadersFlag, rootCmd.PersistentFlags().Lookup(xHeadersFlag)) - // Cobra also supports local flags, which will only run // when this action is called directly. rootCmd.Flags().Bool("version", false, "Application version and NeoFS API compatibility") @@ -157,7 +176,7 @@ const nep2Base58Length = 58 // getKey returns private key that was provided in global arguments. func getKey() (*ecdsa.PrivateKey, error) { - if viper.GetBool("generate-key") { + if viper.GetBool(generateKey) { priv, err := keys.NewPrivateKey() if err != nil { return nil, errCantGenerateKey @@ -165,19 +184,19 @@ func getKey() (*ecdsa.PrivateKey, error) { return &priv.PrivateKey, nil } - if keyPath := viper.GetString("binary-key"); keyPath != "" { + if keyPath := viper.GetString(binaryKey); keyPath != "" { return getKeyFromFile(keyPath) } - if walletPath := viper.GetString("wallet"); walletPath != "" { + if walletPath := viper.GetString(walletPath); walletPath != "" { w, err := wallet.NewWalletFromFile(walletPath) if err != nil { return nil, fmt.Errorf("%w: %v", errInvalidKey, err) } - return getKeyFromWallet(w, viper.GetString("address")) + return getKeyFromWallet(w, viper.GetString(address)) } - wif := viper.GetString("wif") + wif := viper.GetString(wif) if len(wif) == nep2Base58Length { return getKeyFromNEP2(wif) } @@ -260,7 +279,7 @@ func getKeyFromWallet(w *wallet.Wallet, addrStr string) (*ecdsa.PrivateKey, erro // getEndpointAddress returns network address structure that stores multiaddr // inside, parsed from global arguments. func getEndpointAddress() (addr network.Address, err error) { - endpoint := viper.GetString("rpc") + endpoint := viper.GetString(rpc) err = addr.FromString(endpoint) if err != nil { @@ -296,7 +315,7 @@ func getSDKClient(key *ecdsa.PrivateKey) (client.Client, error) { } func getTTL() uint32 { - ttl := viper.GetUint32("ttl") + ttl := viper.GetUint32(ttl) printVerbose("TTL: %d", ttl) return ttl @@ -315,7 +334,7 @@ func ownerFromString(s string) (*owner.ID, error) { } func printVerbose(format string, a ...interface{}) { - if verbose { + if viper.GetBool(verbose) { fmt.Printf(format+"\n", a...) } } @@ -351,3 +370,35 @@ func globalCallOptions() []client.CallOption { return opts } + +// add common flags to the command: +// - key; +// - wallet; +// - WIF; +// - address; +// - RPC; +// - verbose; +func initCommonFlags(cmd *cobra.Command) { + ff := cmd.Flags() + + ff.BoolP(generateKey, generateKeyShorthand, generateKeyDefault, generateKeyUsage) + ff.StringP(binaryKey, binaryKeyShorthand, binaryKeyDefault, binaryKeyUsage) + ff.StringP(walletPath, walletPathShorthand, walletPathDefault, walletPathUsage) + ff.StringP(wif, wifShorthand, wifDefault, wifUsage) + ff.StringP(address, addressShorthand, addressDefault, addressUsage) + ff.StringP(rpc, rpcShorthand, rpcDefault, rpcUsage) + ff.BoolP(verbose, verboseShorthand, verboseDefault, verboseUsage) +} + +// bind common command flags to the viper +func bindCommonFlags(cmd *cobra.Command) { + ff := cmd.Flags() + + _ = viper.BindPFlag(generateKey, ff.Lookup(generateKey)) + _ = viper.BindPFlag(binaryKey, ff.Lookup(binaryKey)) + _ = viper.BindPFlag(walletPath, ff.Lookup(walletPath)) + _ = viper.BindPFlag(wif, ff.Lookup(wif)) + _ = viper.BindPFlag(address, ff.Lookup(address)) + _ = viper.BindPFlag(rpc, ff.Lookup(rpc)) + _ = viper.BindPFlag(verbose, ff.Lookup(verbose)) +} diff --git a/cmd/neofs-cli/modules/storagegroup.go b/cmd/neofs-cli/modules/storagegroup.go index 10044f7e..72de1ccd 100644 --- a/cmd/neofs-cli/modules/storagegroup.go +++ b/cmd/neofs-cli/modules/storagegroup.go @@ -20,6 +20,11 @@ var storagegroupCmd = &cobra.Command{ Use: "storagegroup", Short: "Operations with Storage Groups", Long: `Operations with Storage Groups`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + // bind exactly that cmd's flags to + // the viper before execution + bindCommonFlags(cmd) + }, } var sgPutCmd = &cobra.Command{ @@ -61,36 +66,74 @@ var ( sgID string ) -func init() { - rootCmd.AddCommand(storagegroupCmd) +func initSGPutCmd() { + initCommonFlags(sgPutCmd) - storagegroupCmd.PersistentFlags().String(sgBearerFlag, "", - "File with signed JSON or binary encoded bearer token") + flags := sgPutCmd.Flags() - storagegroupCmd.AddCommand(sgPutCmd) - sgPutCmd.Flags().String("cid", "", "Container ID") + flags.String("cid", "", "Container ID") _ = sgPutCmd.MarkFlagRequired("cid") - sgPutCmd.Flags().StringSliceVarP(&sgMembers, sgMembersFlag, "m", nil, - "ID list of storage group members") + + flags.StringSliceVarP(&sgMembers, sgMembersFlag, "m", nil, "ID list of storage group members") _ = sgPutCmd.MarkFlagRequired(sgMembersFlag) +} - storagegroupCmd.AddCommand(sgGetCmd) - sgGetCmd.Flags().String("cid", "", "Container ID") +func initSGGetCmd() { + initCommonFlags(sgGetCmd) + + flags := sgGetCmd.Flags() + + flags.String("cid", "", "Container ID") _ = sgGetCmd.MarkFlagRequired("cid") - sgGetCmd.Flags().StringVarP(&sgID, sgIDFlag, "", "", "storage group identifier") - _ = sgGetCmd.MarkFlagRequired(sgIDFlag) - storagegroupCmd.AddCommand(sgListCmd) + flags.StringVarP(&sgID, sgIDFlag, "", "", "storage group identifier") + _ = sgGetCmd.MarkFlagRequired(sgIDFlag) +} + +func initSGListCmd() { + initCommonFlags(sgListCmd) + sgListCmd.Flags().String("cid", "", "Container ID") _ = sgListCmd.MarkFlagRequired("cid") +} - storagegroupCmd.AddCommand(sgDelCmd) - sgDelCmd.Flags().String("cid", "", "Container ID") +func initSGDeleteCmd() { + initCommonFlags(sgDelCmd) + + flags := sgDelCmd.Flags() + + flags.String("cid", "", "Container ID") _ = sgDelCmd.MarkFlagRequired("cid") - sgDelCmd.Flags().StringVarP(&sgID, sgIDFlag, "", "", "storage group identifier") + + flags.StringVarP(&sgID, sgIDFlag, "", "", "storage group identifier") _ = sgDelCmd.MarkFlagRequired(sgIDFlag) } +func init() { + storageGroupChildCommands := []*cobra.Command{ + sgPutCmd, + sgGetCmd, + sgListCmd, + sgDelCmd, + } + + rootCmd.AddCommand(storagegroupCmd) + storagegroupCmd.AddCommand(storageGroupChildCommands...) + + for _, sgCommand := range storageGroupChildCommands { + flags := sgCommand.Flags() + + flags.String(sgBearerFlag, "", "File with signed JSON or binary encoded bearer token") + flags.StringSliceVarP(&xHeaders, xHeadersKey, xHeadersShorthand, xHeadersDefault, xHeadersUsage) + flags.Uint32P(ttl, ttlShorthand, ttlDefault, ttlUsage) + } + + initSGPutCmd() + initSGGetCmd() + initSGListCmd() + initSGDeleteCmd() +} + type sgHeadReceiver struct { ctx context.Context diff --git a/cmd/neofs-cli/modules/util.go b/cmd/neofs-cli/modules/util.go index 14647902..c7a335fb 100644 --- a/cmd/neofs-cli/modules/util.go +++ b/cmd/neofs-cli/modules/util.go @@ -19,6 +19,7 @@ import ( continentsdb "github.com/nspcc-dev/neofs-node/pkg/util/locode/db/continents/geojson" csvlocode "github.com/nspcc-dev/neofs-node/pkg/util/locode/table/csv" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var errKeyerSingleArgument = errors.New("pass only one argument at a time") @@ -27,6 +28,16 @@ var ( utilCmd = &cobra.Command{ Use: "util", Short: "Utility operations", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + flags := cmd.Flags() + + _ = viper.BindPFlag(generateKey, flags.Lookup(generateKey)) + _ = viper.BindPFlag(binaryKey, flags.Lookup(binaryKey)) + _ = viper.BindPFlag(walletPath, flags.Lookup(walletPath)) + _ = viper.BindPFlag(wif, flags.Lookup(wif)) + _ = viper.BindPFlag(address, flags.Lookup(address)) + _ = viper.BindPFlag(verbose, flags.Lookup(verbose)) + }, } signCmd = &cobra.Command{ @@ -171,74 +182,116 @@ var ( } ) -func init() { - rootCmd.AddCommand(utilCmd) - - utilCmd.AddCommand(signCmd) - utilCmd.AddCommand(convertCmd) - utilCmd.AddCommand(keyerCmd) - utilCmd.AddCommand(locodeCmd) - - signCmd.AddCommand(signBearerCmd) - signBearerCmd.Flags().String("from", "", "File with JSON or binary encoded bearer token to sign") - _ = signBearerCmd.MarkFlagFilename("from") - _ = signBearerCmd.MarkFlagRequired("from") - signBearerCmd.Flags().String("to", "", "File to dump signed bearer token (default: binary encoded)") - signBearerCmd.Flags().Bool("json", false, "Dump bearer token in JSON encoding") - - signCmd.AddCommand(signSessionCmd) - signSessionCmd.Flags().String("from", "", "File with JSON encoded session token to sign") - _ = signSessionCmd.MarkFlagFilename("from") - _ = signSessionCmd.MarkFlagRequired("from") - signSessionCmd.Flags().String("to", "", "File to save signed session token (optional)") - - convertCmd.AddCommand(convertEACLCmd) - convertEACLCmd.Flags().String("from", "", "File with JSON or binary encoded extended ACL table") - _ = convertEACLCmd.MarkFlagFilename("from") - _ = convertEACLCmd.MarkFlagRequired("from") - convertEACLCmd.Flags().String("to", "", "File to dump extended ACL table (default: binary encoded)") - convertEACLCmd.Flags().Bool("json", false, "Dump extended ACL table in JSON encoding") - +func initUtilKeyerCmd() { keyerCmd.Flags().BoolP("generate", "g", false, "generate new private key") keyerCmd.Flags().Bool("hex", false, "print all values in hex encoding") keyerCmd.Flags().BoolP("uncompressed", "u", false, "use uncompressed public key format") keyerCmd.Flags().BoolP("multisig", "m", false, "calculate multisig address from public keys") +} - locodeCmd.AddCommand(locodeGenerateCmd) +func initCommonFlagsWithoutRPC(cmd *cobra.Command) { + flags := cmd.Flags() - locodeGenerateCmd.Flags().StringSliceVar(&locodeGenerateInPaths, locodeGenerateInputFlag, nil, - "List of paths to UN/LOCODE tables (csv)") + flags.BoolP(generateKey, generateKeyShorthand, generateKeyDefault, generateKeyUsage) + flags.StringP(binaryKey, binaryKeyShorthand, binaryKeyDefault, binaryKeyUsage) + flags.StringP(walletPath, walletPathShorthand, walletPathDefault, walletPathUsage) + flags.StringP(wif, wifShorthand, wifDefault, wifUsage) + flags.StringP(address, addressShorthand, addressDefault, addressUsage) + flags.StringP(rpc, rpcShorthand, rpcDefault, rpcUsage) + flags.BoolP(verbose, verboseShorthand, verboseDefault, verboseUsage) +} + +func initUtilSignBearerCmd() { + initCommonFlagsWithoutRPC(signBearerCmd) + + flags := signBearerCmd.Flags() + + flags.String("from", "", "File with JSON or binary encoded bearer token to sign") + _ = signBearerCmd.MarkFlagFilename("from") + _ = signBearerCmd.MarkFlagRequired("from") + + flags.String("to", "", "File to dump signed bearer token (default: binary encoded)") + flags.Bool("json", false, "Dump bearer token in JSON encoding") +} + +func initUtilSignSessionCmd() { + initCommonFlagsWithoutRPC(signSessionCmd) + + flags := signSessionCmd.Flags() + + flags.String("from", "", "File with JSON encoded session token to sign") + _ = signSessionCmd.MarkFlagFilename("from") + _ = signSessionCmd.MarkFlagRequired("from") + + flags.String("to", "", "File to save signed session token (optional)") +} + +func initUtilConvertEACLCmd() { + flags := convertEACLCmd.Flags() + + flags.String("from", "", "File with JSON or binary encoded extended ACL table") + _ = convertEACLCmd.MarkFlagFilename("from") + _ = convertEACLCmd.MarkFlagRequired("from") + + flags.String("to", "", "File to dump extended ACL table (default: binary encoded)") + flags.Bool("json", false, "Dump extended ACL table in JSON encoding") +} + +func initUtilLocodeGenerateCmd() { + flags := locodeGenerateCmd.Flags() + + flags.StringSliceVar(&locodeGenerateInPaths, locodeGenerateInputFlag, nil, "List of paths to UN/LOCODE tables (csv)") _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateInputFlag) - locodeGenerateCmd.Flags().StringVar(&locodeGenerateSubDivPath, locodeGenerateSubDivFlag, "", - "Path to UN/LOCODE subdivision database (csv)") + flags.StringVar(&locodeGenerateSubDivPath, locodeGenerateSubDivFlag, "", "Path to UN/LOCODE subdivision database (csv)") _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateSubDivFlag) - locodeGenerateCmd.Flags().StringVar(&locodeGenerateAirportsPath, locodeGenerateAirportsFlag, "", - "Path to OpenFlights airport database (csv)") + flags.StringVar(&locodeGenerateAirportsPath, locodeGenerateAirportsFlag, "", "Path to OpenFlights airport database (csv)") _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateAirportsFlag) - locodeGenerateCmd.Flags().StringVar(&locodeGenerateCountriesPath, locodeGenerateCountriesFlag, "", - "Path to OpenFlights country database (csv)") + flags.StringVar(&locodeGenerateCountriesPath, locodeGenerateCountriesFlag, "", "Path to OpenFlights country database (csv)") _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateCountriesFlag) - locodeGenerateCmd.Flags().StringVar(&locodeGenerateContinentsPath, locodeGenerateContinentsFlag, "", - "Path to continent polygons (GeoJSON)") + flags.StringVar(&locodeGenerateContinentsPath, locodeGenerateContinentsFlag, "", "Path to continent polygons (GeoJSON)") _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateContinentsFlag) - locodeGenerateCmd.Flags().StringVar(&locodeGenerateOutPath, locodeGenerateOutputFlag, "", - "Target path for generated database") + flags.StringVar(&locodeGenerateOutPath, locodeGenerateOutputFlag, "", "Target path for generated database") _ = locodeGenerateCmd.MarkFlagRequired(locodeGenerateOutputFlag) +} - locodeCmd.AddCommand(locodeInfoCmd) +func initUtilLocodeInfoCmd() { + flags := locodeInfoCmd.Flags() - locodeInfoCmd.Flags().StringVar(&locodeInfoDBPath, locodeInfoDBFlag, "", - "Path to NeoFS UN/LOCODE database") - _ = locodeGenerateCmd.MarkFlagRequired(locodeInfoDBFlag) + flags.StringVar(&locodeInfoDBPath, locodeInfoDBFlag, "", "Path to NeoFS UN/LOCODE database") + _ = locodeInfoCmd.MarkFlagRequired(locodeInfoDBFlag) - locodeInfoCmd.Flags().StringVar(&locodeInfoCode, locodeInfoCodeFlag, "", - "UN/LOCODE") - _ = locodeGenerateCmd.MarkFlagRequired(locodeInfoCodeFlag) + flags.StringVar(&locodeInfoCode, locodeInfoCodeFlag, "", "UN/LOCODE") + _ = locodeInfoCmd.MarkFlagRequired(locodeInfoCodeFlag) +} + +func init() { + rootCmd.AddCommand(utilCmd) + + utilCmd.AddCommand( + signCmd, + convertCmd, + keyerCmd, + locodeCmd, + ) + + signCmd.AddCommand(signBearerCmd, signSessionCmd) + convertCmd.AddCommand(convertEACLCmd) + locodeCmd.AddCommand(locodeGenerateCmd, locodeInfoCmd) + + initUtilKeyerCmd() + + initUtilSignBearerCmd() + initUtilSignSessionCmd() + + initUtilConvertEACLCmd() + + initUtilLocodeInfoCmd() + initUtilLocodeGenerateCmd() } func signBearerToken(cmd *cobra.Command, _ []string) {