From 5028fcf6c1116f917e3b19e3abd3825c2ed792a6 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Wed, 12 Feb 2025 14:45:11 +0300 Subject: [PATCH] cli/object: Support SearchV2 Add `searchv2` command to the `object` section. Closes #3119. Signed-off-by: Leonard Lyubich --- cmd/neofs-cli/modules/object/root.go | 1 + cmd/neofs-cli/modules/object/search.go | 92 +++++++++++++++++++ cmd/neofs-cli/modules/object/util.go | 41 ++++++--- docs/cli-commands/neofs-cli_object.md | 1 + .../cli-commands/neofs-cli_object_searchv2.md | 46 ++++++++++ 5 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 docs/cli-commands/neofs-cli_object_searchv2.md diff --git a/cmd/neofs-cli/modules/object/root.go b/cmd/neofs-cli/modules/object/root.go index a89c46b814..4864453097 100644 --- a/cmd/neofs-cli/modules/object/root.go +++ b/cmd/neofs-cli/modules/object/root.go @@ -24,6 +24,7 @@ func init() { objectDelCmd, objectGetCmd, objectSearchCmd, + searchV2Cmd, objectHeadCmd, objectHashCmd, objectRangeCmd, diff --git a/cmd/neofs-cli/modules/object/search.go b/cmd/neofs-cli/modules/object/search.go index 6cb208f28f..b88a865a96 100644 --- a/cmd/neofs-cli/modules/object/search.go +++ b/cmd/neofs-cli/modules/object/search.go @@ -7,12 +7,24 @@ import ( "strings" internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" + "github.com/nspcc-dev/neofs-sdk-go/client" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa" "github.com/nspcc-dev/neofs-sdk-go/object" oidSDK "github.com/nspcc-dev/neofs-sdk-go/object/id" + "github.com/nspcc-dev/neofs-sdk-go/session" "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// searchV2Cmd flags. +var ( + searchAttributesFlag = flag[[]string]{f: "attributes"} + searchCountFlag = flag[uint16]{f: "count"} + searchCursorFlag = flag[string]{f: "cursor"} ) var ( @@ -25,23 +37,44 @@ var ( Args: cobra.NoArgs, RunE: searchObject, } + searchV2Cmd = &cobra.Command{ + Use: objectSearchCmd.Use + "v2", + Short: objectSearchCmd.Short + " (new)", // TODO: drop suffix on old search deprecation + Long: objectSearchCmd.Long + " (new)", // TODO: desc in details + Args: objectSearchCmd.Args, + RunE: searchV2, + } ) func initObjectSearchCmd() { commonflags.Init(objectSearchCmd) + commonflags.Init(searchV2Cmd) initFlagSession(objectSearchCmd, "SEARCH") + initFlagSession(searchV2Cmd, "SEARCH") flags := objectSearchCmd.Flags() + flags2 := searchV2Cmd.Flags() flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage) _ = objectSearchCmd.MarkFlagRequired(commonflags.CIDFlag) + flags2.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage) + _ = searchV2Cmd.MarkFlagRequired(commonflags.CIDFlag) flags.StringSliceVarP(&searchFilters, "filters", "f", nil, "Repeated filter expressions or files with protobuf JSON") + flags2.StringSliceVarP(&searchFilters, "filters", "f", nil, + "Repeated filter expressions or files with protobuf JSON") flags.Bool("root", false, "Search for user objects") + flags2.Bool("root", false, "Search for user objects") flags.Bool("phy", false, "Search physically stored objects") + flags2.Bool("phy", false, "Search physically stored objects") flags.String(commonflags.OIDFlag, "", "Search object by identifier") + flags2.String(commonflags.OIDFlag, "", "Search object by identifier") + + flags2.StringSliceVar(&searchAttributesFlag.v, searchAttributesFlag.f, nil, "Additional attributes to display for suitable objects") + flags2.Uint16Var(&searchCountFlag.v, searchCountFlag.f, 0, "Max number of resulting items. Must not exceed 1000") + flags2.StringVar(&searchCursorFlag.v, searchCursorFlag.f, "", "Cursor to continue previous search") } func searchObject(cmd *cobra.Command, _ []string) error { @@ -175,3 +208,62 @@ func parseSearchFilters(cmd *cobra.Command) (object.SearchFilters, error) { return fs, nil } + +func searchV2(cmd *cobra.Command, _ []string) error { + var cnr cid.ID + if err := readCID(cmd, &cnr); err != nil { + return err + } + fs, err := parseSearchFilters(cmd) + if err != nil { + return err + } + pk, err := key.GetOrGenerate(cmd) + if err != nil { + return err + } + bt, err := common.ReadBearerToken(cmd, bearerTokenFlag) + if err != nil { + return err + } + st, err := getVerifiedSession(cmd, session.VerbObjectSearch, pk, cnr) + if err != nil { + return err + } + + ctx, cancel := commonflags.GetCommandContext(cmd) + defer cancel() + cli, err := internalclient.GetSDKClientByFlag(ctx, commonflags.RPC) + if err != nil { + return err + } + + var opts client.SearchObjectsOptions + opts.SetCount(uint32(searchCountFlag.v)) + opts.WithXHeaders(parseXHeaders(cmd)...) + if viper.GetUint32(commonflags.TTL) == 1 { + opts.DisableForwarding() + } + if bt != nil { + opts.WithBearerToken(*bt) + } + if st != nil { + opts.WithSessionToken(*st) + } + res, cursor, err := cli.SearchObjects(ctx, cnr, fs, searchAttributesFlag.v, searchCursorFlag.v, neofsecdsa.Signer(*pk), opts) + if err != nil { + return fmt.Errorf("rpc error: %w", err) + } + + cmd.Printf("Found %d objects.\n", len(res)) + for i := range res { + cmd.Println(res[i].ID) + for j := range searchAttributesFlag.v { + fmt.Printf("\t%s: %s\n", searchAttributesFlag.v[j], res[i].Attributes[j]) + } + } + if cursor != "" { + cmd.Printf("Cursor: %s\n", cursor) + } + return nil +} diff --git a/cmd/neofs-cli/modules/object/util.go b/cmd/neofs-cli/modules/object/util.go index a765a2288f..c8a40700b7 100644 --- a/cmd/neofs-cli/modules/object/util.go +++ b/cmd/neofs-cli/modules/object/util.go @@ -24,6 +24,11 @@ import ( "github.com/spf13/viper" ) +type flag[T any] struct { + f string + v T +} + const ( bearerTokenFlag = "bearer" @@ -203,27 +208,15 @@ func _readVerifiedSession(cmd *cobra.Command, dst SessionPrm, key *ecdsa.Private cmdVerb = session.VerbObjectRangeHash } - tok, err := getSession(cmd) - if err != nil { + tok, err := getVerifiedSession(cmd, cmdVerb, key, cnr) + if err != nil || tok == nil { return err } - if tok == nil { - return nil - } common.PrintVerbose(cmd, "Checking session correctness...") - switch false { - case tok.AssertContainer(cnr): - return errors.New("unrelated container in the session") - case obj == nil || tok.AssertObject(*obj): + if obj != nil && !tok.AssertObject(*obj) { return errors.New("unrelated object in the session") - case tok.AssertVerb(cmdVerb): - return errors.New("wrong verb of the session") - case tok.AssertAuthKey((*neofsecdsa.PublicKey)(&key.PublicKey)): - return errors.New("unrelated key in the session") - case tok.VerifySignature(): - return errors.New("invalid signature of the session data") } common.PrintVerbose(cmd, "Session is correct.") @@ -232,6 +225,24 @@ func _readVerifiedSession(cmd *cobra.Command, dst SessionPrm, key *ecdsa.Private return nil } +func getVerifiedSession(cmd *cobra.Command, cmdVerb session.ObjectVerb, key *ecdsa.PrivateKey, cnr cid.ID) (*session.Object, error) { + tok, err := getSession(cmd) + if err != nil || tok == nil { + return tok, err + } + switch false { + case tok.AssertContainer(cnr): + return nil, errors.New("unrelated container in the session") + case tok.AssertVerb(cmdVerb): + return nil, errors.New("wrong verb of the session") + case tok.AssertAuthKey((*neofsecdsa.PublicKey)(&key.PublicKey)): + return nil, errors.New("unrelated key in the session") + case tok.VerifySignature(): + return nil, errors.New("invalid signature of the session data") + } + return tok, nil +} + // ReadOrOpenSession opens client connection and calls ReadOrOpenSessionViaClient with it. func ReadOrOpenSession(ctx context.Context, cmd *cobra.Command, dst SessionPrm, key *ecdsa.PrivateKey, cnr cid.ID, objs ...oid.ID) error { cli, err := internal.GetSDKClientByFlag(ctx, commonflags.RPC) diff --git a/docs/cli-commands/neofs-cli_object.md b/docs/cli-commands/neofs-cli_object.md index b9a2b7117f..c631356407 100644 --- a/docs/cli-commands/neofs-cli_object.md +++ b/docs/cli-commands/neofs-cli_object.md @@ -31,4 +31,5 @@ Operations with Objects * [neofs-cli object put](neofs-cli_object_put.md) - Put object to NeoFS * [neofs-cli object range](neofs-cli_object_range.md) - Get payload range data of an object * [neofs-cli object search](neofs-cli_object_search.md) - Search object +* [neofs-cli object searchv2](neofs-cli_object_searchv2.md) - Search object (new) diff --git a/docs/cli-commands/neofs-cli_object_searchv2.md b/docs/cli-commands/neofs-cli_object_searchv2.md new file mode 100644 index 0000000000..50b6769711 --- /dev/null +++ b/docs/cli-commands/neofs-cli_object_searchv2.md @@ -0,0 +1,46 @@ +## neofs-cli object searchv2 + +Search object (new) + +### Synopsis + +Search object (new) + +``` +neofs-cli object searchv2 [flags] +``` + +### Options + +``` + --address string Address of wallet account + --attributes strings Additional attributes to display for suitable objects + --bearer string File with signed JSON or binary encoded bearer token + --cid string Container ID. + --count uint16 Max number of resulting items. Must not exceed 1000 + --cursor string Cursor to continue previous search + -f, --filters strings Repeated filter expressions or files with protobuf JSON + -g, --generate-key Generate new private key + -h, --help help for searchv2 + --oid string Search object by identifier + --phy Search physically stored objects + --root Search for user objects + -r, --rpc-endpoint string Remote node address (as 'multiaddr' or ':') + --session string Filepath to a JSON- or binary-encoded token of the object SEARCH session + -t, --timeout duration Timeout for the operation (default 15s) + --ttl uint32 TTL value in request meta header (default 2) + -w, --wallet string Path to the wallet + -x, --xhdr strings Request X-Headers in form of Key=Value +``` + +### Options inherited from parent commands + +``` + -c, --config string Config file (default is $HOME/.config/neofs-cli/config.yaml) + -v, --verbose Verbose output +``` + +### SEE ALSO + +* [neofs-cli object](neofs-cli_object.md) - Operations with Objects +