From bbc2b873aba96741728b7f32e3395e893a75e058 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Thu, 28 Oct 2021 17:48:46 +0300 Subject: [PATCH] [#950] cli: Refactor usage of NeoFS API client The client needs of the CLI application are limited and change not often. Interface changes of the client library should not affect the operation of various application packages, if they do not change their requirements for the provided functionality. To localize the use of the base client and facilitate further support, an auxiliary package is implemented that will only be used by the CLI application. Signed-off-by: Leonard Lyubich --- cmd/neofs-cli/internal/client/client.go | 614 ++++++++++++++++++++++++ cmd/neofs-cli/internal/client/doc.go | 12 + cmd/neofs-cli/internal/client/prm.go | 114 +++++ cmd/neofs-cli/modules/accounting.go | 21 +- cmd/neofs-cli/modules/container.go | 163 +++---- cmd/neofs-cli/modules/netmap.go | 32 +- cmd/neofs-cli/modules/object.go | 345 ++++++------- cmd/neofs-cli/modules/root.go | 49 +- cmd/neofs-cli/modules/storagegroup.go | 151 ++---- 9 files changed, 1098 insertions(+), 403 deletions(-) create mode 100644 cmd/neofs-cli/internal/client/client.go create mode 100644 cmd/neofs-cli/internal/client/doc.go create mode 100644 cmd/neofs-cli/internal/client/prm.go diff --git a/cmd/neofs-cli/internal/client/client.go b/cmd/neofs-cli/internal/client/client.go new file mode 100644 index 00000000..6a817703 --- /dev/null +++ b/cmd/neofs-cli/internal/client/client.go @@ -0,0 +1,614 @@ +package internal + +import ( + "context" + "crypto/sha256" + "io" + "math" + + "github.com/nspcc-dev/neofs-api-go/pkg" + "github.com/nspcc-dev/neofs-api-go/pkg/accounting" + "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" + "github.com/nspcc-dev/neofs-api-go/pkg/client" + "github.com/nspcc-dev/neofs-api-go/pkg/container" + cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" + "github.com/nspcc-dev/neofs-api-go/pkg/netmap" + "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/nspcc-dev/neofs-api-go/pkg/session" +) + +// BalanceOfPrm groups parameters of BalanceOf operation. +type BalanceOfPrm struct { + commonPrm + ownerIDPrm +} + +// BalanceOfRes groups resulting values of BalanceOf operation. +type BalanceOfRes struct { + cliRes *accounting.Decimal +} + +// Balance returns current balance. +func (x BalanceOfRes) Balance() *accounting.Decimal { + return x.cliRes +} + +// BalanceOf requests current balance of NeoFS user. +func BalanceOf(prm BalanceOfPrm) (res BalanceOfRes, err error) { + res.cliRes, err = prm.cli.GetBalance(context.Background(), prm.ownerID, + client.WithKey(prm.privKey), + ) + + return +} + +// ListContainersPrm groups parameters of ListContainers operation. +type ListContainersPrm struct { + commonPrm + ownerIDPrm +} + +// ListContainersRes groups resulting values of ListContainers operation. +type ListContainersRes struct { + cliRes []*cid.ID +} + +// IDList returns list of identifiers of user's containers. +func (x ListContainersRes) IDList() []*cid.ID { + return x.cliRes +} + +// ListContainers requests list of NeoFS user's containers. +func ListContainers(prm ListContainersPrm) (res ListContainersRes, err error) { + res.cliRes, err = prm.cli.ListContainers(context.Background(), prm.ownerID, + client.WithKey(prm.privKey), + ) + + return +} + +// PutContainerPrm groups parameters of PutContainer operation. +type PutContainerPrm struct { + commonPrm + sessionTokenPrm + + cnr *container.Container +} + +// SetContainer sets container structure. +func (x *PutContainerPrm) SetContainer(cnr *container.Container) { + x.cnr = cnr +} + +// PutContainerRes groups resulting values of PutContainer operation. +type PutContainerRes struct { + cliRes *cid.ID +} + +// ID returns identifier of the created container. +func (x PutContainerRes) ID() *cid.ID { + return x.cliRes +} + +// PutContainer sends request to save container in NeoFS. +// +// Operation is asynchronous and no guaranteed even in the absence of errors. +// The required time is also not predictable. +// +// Success can be verified by reading by identifier. +func PutContainer(prm PutContainerPrm) (res PutContainerRes, err error) { + res.cliRes, err = prm.cli.PutContainer(context.Background(), prm.cnr, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + ) + + return +} + +// GetContainerPrm groups parameters of GetContainer operation. +type GetContainerPrm struct { + commonPrm + containerIDPrm +} + +// GetContainerRes groups resulting values of GetContainer operation. +type GetContainerRes struct { + cliRes *container.Container +} + +// Container returns structured of the requested container. +func (x GetContainerRes) Container() *container.Container { + return x.cliRes +} + +// GetContainer reads container from NeoFS by ID. +func GetContainer(prm GetContainerPrm) (res GetContainerRes, err error) { + res.cliRes, err = prm.cli.GetContainer(context.Background(), prm.cnrID, + client.WithKey(prm.privKey), + ) + + return +} + +// DeleteContainerPrm groups parameters of DeleteContainerPrm operation. +type DeleteContainerPrm struct { + commonPrm + sessionTokenPrm + containerIDPrm +} + +// DeleteContainerRes groups resulting values of DeleteContainer operation. +type DeleteContainerRes struct{} + +// DeleteContainer sends request to remove container from NeoFS by ID. +// +// Operation is asynchronous and no guaranteed even in the absence of errors. +// The required time is also not predictable. +// +// Success can be verified by reading by identifier. +func DeleteContainer(prm DeleteContainerPrm) (res DeleteContainerRes, err error) { + err = prm.cli.DeleteContainer(context.Background(), prm.cnrID, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + ) + + return +} + +// EACLPrm groups parameters of EACL operation. +type EACLPrm struct { + commonPrm + containerIDPrm +} + +// EACLRes groups resulting values of EACL operation. +type EACLRes struct { + cliRes *client.EACLWithSignature +} + +// EACL returns requested eACL table. +func (x EACLRes) EACL() *eacl.Table { + return x.cliRes.EACL() +} + +// EACL reads eACL table from NeoFS by container ID. +func EACL(prm EACLPrm) (res EACLRes, err error) { + res.cliRes, err = prm.cli.GetEACL(context.Background(), prm.cnrID, + client.WithKey(prm.privKey), + ) + + return +} + +// SetEACLPrm groups parameters of SetEACL operation. +type SetEACLPrm struct { + commonPrm + sessionTokenPrm + + eaclTable *eacl.Table +} + +// SetEACLTable sets eACL table structure. +func (x *SetEACLPrm) SetEACLTable(table *eacl.Table) { + x.eaclTable = table +} + +// SetEACLRes groups resulting values of SetEACL operation. +type SetEACLRes struct{} + +// SetEACL requests to save eACL table in NeoFS. +// +// Operation is asynchronous and no guaranteed even in the absence of errors. +// The required time is also not predictable. +// +// Success can be verified by reading by container identifier. +func SetEACL(prm SetEACLPrm) (res SetEACLRes, err error) { + err = prm.cli.SetEACL(context.Background(), prm.eaclTable, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + ) + + return +} + +// NetworkInfoPrm groups parameters of NetworkInfo operation. +type NetworkInfoPrm struct { + commonPrm +} + +// NetworkInfoRes groups resulting values of NetworkInfo operation. +type NetworkInfoRes struct { + cliRes *netmap.NetworkInfo +} + +// NetworkInfo returns structured information about the NeoFS network. +func (x NetworkInfoRes) NetworkInfo() *netmap.NetworkInfo { + return x.cliRes +} + +// NetworkInfo reads information about the NeoFS network. +func NetworkInfo(prm NetworkInfoPrm) (res NetworkInfoRes, err error) { + res.cliRes, err = prm.cli.NetworkInfo(context.Background(), + client.WithKey(prm.privKey), + ) + + return +} + +// NodeInfoPrm groups parameters of NodeInfo operation. +type NodeInfoPrm struct { + commonPrm +} + +// NodeInfoRes groups resulting values of NodeInfo operation. +type NodeInfoRes struct { + cliRes *client.EndpointInfo +} + +// NodeInfo returns information about the node from netmap. +func (x NodeInfoRes) NodeInfo() *netmap.NodeInfo { + return x.cliRes.NodeInfo() +} + +// LatestVersion returns latest NeoFS API version in use. +func (x NodeInfoRes) LatestVersion() *pkg.Version { + return x.cliRes.LatestVersion() +} + +// NodeInfo requests information about the remote server from NeoFS netmap. +func NodeInfo(prm NodeInfoPrm) (res NodeInfoRes, err error) { + res.cliRes, err = prm.cli.EndpointInfo(context.Background(), + client.WithKey(prm.privKey), + ) + + return +} + +// CreateSessionPrm groups parameters of CreateSession operation. +type CreateSessionPrm struct { + commonPrm +} + +// CreateSessionRes groups resulting values of CreateSession operation. +type CreateSessionRes struct { + cliRes *session.Token +} + +// ID returns session identifier. +func (x CreateSessionRes) ID() []byte { + return x.cliRes.ID() +} + +// SessionKey returns public session key in a binary format. +func (x CreateSessionRes) SessionKey() []byte { + return x.cliRes.SessionKey() +} + +// CreateSession opens new unlimited session with the remote node. +func CreateSession(prm CreateSessionPrm) (res CreateSessionRes, err error) { + res.cliRes, err = prm.cli.CreateSession(context.Background(), math.MaxUint64, + client.WithKey(prm.privKey), + ) + + return +} + +// PutObjectPrm groups parameters of PutObject operation. +type PutObjectPrm struct { + commonObjectPrm + + hdr *object.Object + + rdr io.Reader +} + +// SetHeader sets object header. +func (x *PutObjectPrm) SetHeader(hdr *object.Object) { + x.hdr = hdr +} + +// SetPayloadReader sets reader of the object payload. +func (x *PutObjectPrm) SetPayloadReader(rdr io.Reader) { + x.rdr = rdr +} + +// PutObjectRes groups resulting values of PutObject operation. +type PutObjectRes struct { + cliRes *object.ID +} + +// ID returns identifier of the created object. +func (x PutObjectRes) ID() *object.ID { + return x.cliRes +} + +// PutObject saves the object in NeoFS network. +func PutObject(prm PutObjectPrm) (res PutObjectRes, err error) { + var putPrm client.PutObjectParams + + putPrm.WithObject(prm.hdr) + putPrm.WithPayloadReader(prm.rdr) + + res.cliRes, err = prm.cli.PutObject(context.Background(), &putPrm, append(prm.opts, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + client.WithBearer(prm.bearerToken), + )...) + + return +} + +// DeleteObjectPrm groups parameters of DeleteObject operation. +type DeleteObjectPrm struct { + commonObjectPrm + objectAddressPrm +} + +// DeleteObjectRes groups resulting values of DeleteObject operation. +type DeleteObjectRes struct { + cliRes *object.Address +} + +// TombstoneAddress returns address of the created object with tombstone. +func (x DeleteObjectRes) TombstoneAddress() *object.Address { + return x.cliRes +} + +// DeleteObject marks object to be removed from NeoFS through tombstone placement. +func DeleteObject(prm DeleteObjectPrm) (res DeleteObjectRes, err error) { + var delPrm client.DeleteObjectParams + + delPrm.WithAddress(prm.objAddr) + + res.cliRes, err = client.DeleteObject(context.Background(), prm.cli, &delPrm, append(prm.opts, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + client.WithBearer(prm.bearerToken), + )...) + + return +} + +// GetObjectPrm groups parameters of GetObject operation. +type GetObjectPrm struct { + commonObjectPrm + objectAddressPrm + rawPrm + payloadWriterPrm +} + +// GetObjectRes groups resulting values of GetObject operation. +type GetObjectRes struct { + cliRes *object.Object +} + +// Object returns header of the request object. +func (x GetObjectRes) Header() *object.Object { + return x.cliRes +} + +// GetObject reads the object by address. +// +// Interrupts on any writer error. If successful, payload is written to writer. +func GetObject(prm GetObjectPrm) (res GetObjectRes, err error) { + var getPrm client.GetObjectParams + + getPrm.WithAddress(prm.objAddr) + getPrm.WithPayloadWriter(prm.wrt) + getPrm.WithRawFlag(prm.raw) + + res.cliRes, err = prm.cli.GetObject(context.Background(), &getPrm, append(prm.opts, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + client.WithBearer(prm.bearerToken), + )...) + + return +} + +// HeadObjectPrm groups parameters of HeadObject operation. +type HeadObjectPrm struct { + commonObjectPrm + objectAddressPrm + rawPrm + + mainOnly bool +} + +// SetMainOnlyFlag sets flag to get only main fields of object header in terms of NeoFS API. +func (x *HeadObjectPrm) SetMainOnlyFlag(v bool) { + x.mainOnly = v +} + +// HeadObjectRes groups resulting values of HeadObject operation. +type HeadObjectRes struct { + cliRes *object.Object +} + +// Header returns requested object header. +func (x HeadObjectRes) Header() *object.Object { + return x.cliRes +} + +// HeadObject reads object header by address. +// +// For raw reading, returns *object.SplitInfoError error if object is virtual. +func HeadObject(prm HeadObjectPrm) (res HeadObjectRes, err error) { + var cliPrm client.ObjectHeaderParams + + cliPrm.WithAddress(prm.objAddr) + cliPrm.WithRawFlag(prm.raw) + + if prm.mainOnly { + cliPrm.WithMainFields() + } else { + cliPrm.WithAllFields() + } + + res.cliRes, err = prm.cli.GetObjectHeader(context.Background(), &cliPrm, append(prm.opts, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + client.WithBearer(prm.bearerToken), + )...) + + return +} + +// SearchObjectsPrm groups parameters of SearchObjects operation. +type SearchObjectsPrm struct { + commonObjectPrm + containerIDPrm + + filters object.SearchFilters +} + +// SetFilters sets search filters. +func (x *SearchObjectsPrm) SetFilters(filters object.SearchFilters) { + x.filters = filters +} + +// SearchObjectsRes groups resulting values of SearchObjects operation. +type SearchObjectsRes struct { + cliRes []*object.ID +} + +// IDList returns identifiers of the matched objects. +func (x SearchObjectsRes) IDList() []*object.ID { + return x.cliRes +} + +// SearchObjects selects objects from container which match the filters. +func SearchObjects(prm SearchObjectsPrm) (res SearchObjectsRes, err error) { + var cliPrm client.SearchObjectParams + + cliPrm.WithSearchFilters(prm.filters) + cliPrm.WithContainerID(prm.cnrID) + + res.cliRes, err = prm.cli.SearchObject(context.Background(), &cliPrm, append(prm.opts, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + client.WithBearer(prm.bearerToken), + )...) + + return +} + +// HashPayloadRangesPrm groups parameters of HashPayloadRanges operation. +type HashPayloadRangesPrm struct { + commonObjectPrm + objectAddressPrm + + tz bool + + rngs []*object.Range + + salt []byte +} + +// TZ sets flag to request Tillich-Zemor hashes. +func (x *HashPayloadRangesPrm) TZ() { + x.tz = true +} + +// SetRanges sets list of payload ranges to hash. +func (x *HashPayloadRangesPrm) SetRanges(rngs []*object.Range) { + x.rngs = rngs +} + +// SetSalt sets data for each range to be XOR'ed with. +func (x *HashPayloadRangesPrm) SetSalt(salt []byte) { + x.salt = salt +} + +// HashPayloadRangesRes groups resulting values of HashPayloadRanges operation. +type HashPayloadRangesRes struct { + cliRes [][]byte +} + +// HashList returns list of hashes of the payload ranges keeping order. +func (x HashPayloadRangesRes) HashList() [][]byte { + return x.cliRes +} + +// HashPayloadRanges requests hashes (by default SHA256) of the object payload ranges. +// +// Returns an error if number of received hashes differs with the number of requested ranges. +func HashPayloadRanges(prm HashPayloadRangesPrm) (res HashPayloadRangesRes, err error) { + var cliPrm client.RangeChecksumParams + + cliPrm.WithAddress(prm.objAddr) + cliPrm.WithSalt(prm.salt) + cliPrm.WithRangeList(prm.rngs...) + + if prm.tz { + var hs [][sha256.Size]byte + + hs, err = prm.cli.ObjectPayloadRangeSHA256(context.Background(), &cliPrm, append(prm.opts, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + client.WithBearer(prm.bearerToken), + )...) + if err == nil { + res.cliRes = make([][]byte, 0, len(hs)) + + for i := range hs { + res.cliRes = append(res.cliRes, hs[i][:]) + } + } + } else { + var hs [][client.TZSize]byte + + hs, err = prm.cli.ObjectPayloadRangeTZ(context.Background(), &cliPrm, append(prm.opts, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + client.WithBearer(prm.bearerToken), + )...) + if err == nil { + res.cliRes = make([][]byte, 0, len(hs)) + + for i := range hs { + res.cliRes = append(res.cliRes, hs[i][:]) + } + } + } + + return +} + +// PayloadRangePrm groups parameters of PayloadRange operation. +type PayloadRangePrm struct { + commonObjectPrm + objectAddressPrm + rawPrm + payloadWriterPrm + + rng *object.Range +} + +// SetRange sets payload range to read. +func (x *PayloadRangePrm) SetRange(rng *object.Range) { + x.rng = rng +} + +// PayloadRangeRes groups resulting values of PayloadRange operation. +type PayloadRangeRes struct{} + +// PayloadRange reads object payload range from NeoFS and writes it to specified writer. +// +// Interrupts on any writer error. +func PayloadRange(prm PayloadRangePrm) (res PayloadRangeRes, err error) { + var cliPrm client.RangeDataParams + + cliPrm.WithRaw(prm.raw) + cliPrm.WithAddress(prm.objAddr) + cliPrm.WithDataWriter(prm.wrt) + cliPrm.WithRange(prm.rng) + + _, err = prm.cli.ObjectPayloadRangeData(context.Background(), &cliPrm, + client.WithKey(prm.privKey), + client.WithSession(prm.sessionToken), + client.WithBearer(prm.bearerToken), + ) + + return +} diff --git a/cmd/neofs-cli/internal/client/doc.go b/cmd/neofs-cli/internal/client/doc.go new file mode 100644 index 00000000..73660997 --- /dev/null +++ b/cmd/neofs-cli/internal/client/doc.go @@ -0,0 +1,12 @@ +// Package internal provides functionality for NeoFS CLI application communication with NeoFS network. +// +// The base client for accessing remote nodes via NeoFS API is a NeoFS SDK Go API client. +// However, although it encapsulates a useful piece of business logic (e.g. the signature mechanism), +// the NeoFS CLI application does not fully use the client's flexible interface. +// +// In this regard, this package provides functions over base API client necessary for the application. +// This allows you to concentrate the entire spectrum of the client's use in one place (this will be convenient +// both when updating the base client and for evaluating the UX of SDK library). So it is expected that all +// application packages will be limited to this package for the development of functionality requiring +// NeoFS API communication. +package internal diff --git a/cmd/neofs-cli/internal/client/prm.go b/cmd/neofs-cli/internal/client/prm.go new file mode 100644 index 00000000..37c13cfd --- /dev/null +++ b/cmd/neofs-cli/internal/client/prm.go @@ -0,0 +1,114 @@ +package internal + +import ( + "crypto/ecdsa" + "io" + + "github.com/nspcc-dev/neofs-api-go/pkg" + "github.com/nspcc-dev/neofs-api-go/pkg/client" + cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" + "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-api-go/pkg/session" + "github.com/nspcc-dev/neofs-api-go/pkg/token" +) + +// here are small structures with public setters to share between parameter structures + +type commonPrm struct { + cli client.Client + + privKey *ecdsa.PrivateKey +} + +// SetClient sets base client for NeoFS API communication. +func (x *commonPrm) SetClient(cli client.Client) { + x.cli = cli +} + +// SetKey sets private key to sign the request(s). +func (x *commonPrm) SetKey(key *ecdsa.PrivateKey) { + x.privKey = key +} + +type ownerIDPrm struct { + ownerID *owner.ID +} + +// SetOwner sets identifier of NeoFS user. +func (x *ownerIDPrm) SetOwner(id *owner.ID) { + x.ownerID = id +} + +type containerIDPrm struct { + cnrID *cid.ID +} + +// SetContainerID sets container identifier. +func (x *containerIDPrm) SetContainerID(id *cid.ID) { + x.cnrID = id +} + +type sessionTokenPrm struct { + sessionToken *session.Token +} + +// SetSessionToken sets token of the session within which request should be sent. +func (x *sessionTokenPrm) SetSessionToken(tok *session.Token) { + x.sessionToken = tok +} + +type bearerTokenPrm struct { + bearerToken *token.BearerToken +} + +// SetBearerToken sets bearer token to be attached to the request. +func (x *bearerTokenPrm) SetBearerToken(tok *token.BearerToken) { + x.bearerToken = tok +} + +type objectAddressPrm struct { + objAddr *object.Address +} + +func (x *objectAddressPrm) SetAddress(addr *object.Address) { + x.objAddr = addr +} + +type rawPrm struct { + raw bool +} + +// SetRawFlag sets flag of raw request. +func (x *rawPrm) SetRawFlag(raw bool) { + x.raw = raw +} + +type payloadWriterPrm struct { + wrt io.Writer +} + +// SetPayloadWriter sets writer of the object payload. +func (x *payloadWriterPrm) SetPayloadWriter(wrt io.Writer) { + x.wrt = wrt +} + +type commonObjectPrm struct { + commonPrm + sessionTokenPrm + bearerTokenPrm + + opts []client.CallOption +} + +// SetTTL sets request TTL value. +func (x *commonObjectPrm) SetTTL(ttl uint32) { + x.opts = append(x.opts, client.WithTTL(ttl)) +} + +// SetXHeaders sets request X-Headers. +func (x *commonObjectPrm) SetXHeaders(xhdrs []*pkg.XHeader) { + for _, xhdr := range xhdrs { + x.opts = append(x.opts, client.WithXHeader(xhdr)) + } +} diff --git a/cmd/neofs-cli/modules/accounting.go b/cmd/neofs-cli/modules/accounting.go index 5c4c9042..c13c1858 100644 --- a/cmd/neofs-cli/modules/accounting.go +++ b/cmd/neofs-cli/modules/accounting.go @@ -1,12 +1,12 @@ package cmd import ( - "context" "fmt" "math" "github.com/nspcc-dev/neofs-api-go/pkg/accounting" "github.com/nspcc-dev/neofs-api-go/pkg/owner" + internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -37,19 +37,11 @@ var accountingBalanceCmd = &cobra.Command{ Short: "Get internal balance of NeoFS account", Long: `Get internal balance of NeoFS account`, Run: func(cmd *cobra.Command, args []string) { - var ( - response *accounting.Decimal - oid *owner.ID - - ctx = context.Background() - ) + var oid *owner.ID key, err := getKey() exitOnErr(cmd, err) - cli, err := getSDKClient(key) - exitOnErr(cmd, err) - if balanceOwner == "" { wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey) exitOnErr(cmd, err) @@ -60,11 +52,16 @@ var accountingBalanceCmd = &cobra.Command{ exitOnErr(cmd, err) } - response, err = cli.GetBalance(ctx, oid, globalCallOptions()...) + var prm internalclient.BalanceOfPrm + + prepareAPIClientWithKey(cmd, key, &prm) + prm.SetOwner(oid) + + res, err := internalclient.BalanceOf(prm) exitOnErr(cmd, errf("rpc error: %w", err)) // print to stdout - prettyPrintDecimal(cmd, response) + prettyPrintDecimal(cmd, res.Balance()) }, } diff --git a/cmd/neofs-cli/modules/container.go b/cmd/neofs-cli/modules/container.go index 56e040ba..c3468f9f 100644 --- a/cmd/neofs-cli/modules/container.go +++ b/cmd/neofs-cli/modules/container.go @@ -2,11 +2,9 @@ package cmd import ( "bytes" - "context" "encoding/json" "errors" "fmt" - "math" "os" "strconv" "strings" @@ -16,13 +14,13 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg" "github.com/nspcc-dev/neofs-api-go/pkg/acl" "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" - "github.com/nspcc-dev/neofs-api-go/pkg/client" "github.com/nspcc-dev/neofs-api-go/pkg/container" cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" "github.com/nspcc-dev/neofs-api-go/pkg/netmap" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/nspcc-dev/neofs-api-go/pkg/session" + internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" "github.com/nspcc-dev/neofs-node/pkg/core/version" "github.com/nspcc-dev/neofs-sdk-go/pkg/policy" "github.com/spf13/cobra" @@ -92,19 +90,11 @@ var listContainersCmd = &cobra.Command{ Short: "List all created containers", Long: "List all created containers", Run: func(cmd *cobra.Command, args []string) { - var ( - response []*cid.ID - oid *owner.ID - - ctx = context.Background() - ) + var oid *owner.ID key, err := getKey() exitOnErr(cmd, err) - cli, err := getSDKClient(key) - exitOnErr(cmd, err) - if containerOwner == "" { wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey) exitOnErr(cmd, err) @@ -115,11 +105,16 @@ var listContainersCmd = &cobra.Command{ exitOnErr(cmd, err) } - response, err = cli.ListContainers(ctx, oid, globalCallOptions()...) + var prm internalclient.ListContainersPrm + + prepareAPIClientWithKey(cmd, key, &prm) + prm.SetOwner(oid) + + res, err := internalclient.ListContainers(prm) exitOnErr(cmd, errf("rpc error: %w", err)) // print to stdout - prettyPrintContainerList(cmd, response) + prettyPrintContainerList(cmd, res.IDList()) }, } @@ -129,14 +124,6 @@ var createContainerCmd = &cobra.Command{ Long: `Create new container and register it in the NeoFS. It will be stored in sidechain when inner ring will accepts it.`, Run: func(cmd *cobra.Command, args []string) { - ctx := context.Background() - - key, err := getKey() - exitOnErr(cmd, err) - - cli, err := getSDKClient(key) - exitOnErr(cmd, err) - placementPolicy, err := parseContainerPolicy(containerPolicy) exitOnErr(cmd, err) @@ -160,18 +147,31 @@ It will be stored in sidechain when inner ring will accepts it.`, cnr.SetSessionToken(tok) cnr.SetOwnerID(tok.OwnerID()) - id, err := cli.PutContainer(ctx, cnr, globalCallOptions()...) + var ( + putPrm internalclient.PutContainerPrm + getPrm internalclient.GetContainerPrm + ) + + prepareAPIClient(cmd, &putPrm, &getPrm) + putPrm.SetContainer(cnr) + putPrm.SetSessionToken(tok) + + res, err := internalclient.PutContainer(putPrm) exitOnErr(cmd, errf("rpc error: %w", err)) + id := res.ID() + cmd.Println("container ID:", id) if containerAwait { cmd.Println("awaiting...") + getPrm.SetContainerID(id) + for i := 0; i < awaitTimeout; i++ { time.Sleep(1 * time.Second) - _, err := cli.GetContainer(ctx, id, globalCallOptions()...) + _, err := internalclient.GetContainer(getPrm) if err == nil { cmd.Println("container has been persisted on sidechain") return @@ -189,27 +189,22 @@ var deleteContainerCmd = &cobra.Command{ Long: `Delete existing container. Only owner of the container has a permission to remove container.`, Run: func(cmd *cobra.Command, args []string) { - ctx := context.Background() - - key, err := getKey() - exitOnErr(cmd, err) - - cli, err := getSDKClient(key) - exitOnErr(cmd, err) - id, err := parseContainerID(containerID) exitOnErr(cmd, err) tok, err := getSessionToken(sessionTokenPath) exitOnErr(cmd, err) - callOpts := globalCallOptions() + var ( + delPrm internalclient.DeleteContainerPrm + getPrm internalclient.GetContainerPrm + ) - if tok != nil { - callOpts = append(callOpts, client.WithSession(tok)) - } + prepareAPIClient(cmd, &delPrm, &getPrm) + delPrm.SetContainerID(id) + delPrm.SetSessionToken(tok) - err = cli.DeleteContainer(ctx, id, callOpts...) + _, err = internalclient.DeleteContainer(delPrm) exitOnErr(cmd, errf("rpc error: %w", err)) cmd.Println("container delete method invoked") @@ -217,10 +212,12 @@ Only owner of the container has a permission to remove container.`, if containerAwait { cmd.Println("awaiting...") + getPrm.SetContainerID(id) + for i := 0; i < awaitTimeout; i++ { time.Sleep(1 * time.Second) - _, err := cli.GetContainer(ctx, id, globalCallOptions()...) + _, err := internalclient.GetContainer(getPrm) if err != nil { cmd.Println("container has been removed:", containerID) return @@ -237,34 +234,24 @@ var listContainerObjectsCmd = &cobra.Command{ Short: "List existing objects in container", Long: `List existing objects in container`, Run: func(cmd *cobra.Command, args []string) { - ctx := context.Background() - - key, err := getKey() - exitOnErr(cmd, err) - - cli, err := getSDKClient(key) - exitOnErr(cmd, err) - id, err := parseContainerID(containerID) exitOnErr(cmd, err) - sessionToken, err := cli.CreateSession(ctx, math.MaxUint64) - exitOnErr(cmd, errf("can't create session token: %w", err)) - filters := new(object.SearchFilters) filters.AddRootFilter() // search only user created objects - searchQuery := new(client.SearchObjectParams) - searchQuery.WithContainerID(id) - searchQuery.WithSearchFilters(*filters) + var prm internalclient.SearchObjectsPrm - objectIDs, err := cli.SearchObject(ctx, searchQuery, - append(globalCallOptions(), - client.WithSession(sessionToken), - )..., - ) + prepareSessionPrm(cmd, &prm) + prepareObjectPrm(cmd, &prm) + prm.SetContainerID(id) + prm.SetFilters(*filters) + + res, err := internalclient.SearchObjects(prm) exitOnErr(cmd, errf("rpc error: %w", err)) + objectIDs := res.IDList() + for i := range objectIDs { cmd.Println(objectIDs[i]) } @@ -276,11 +263,7 @@ var getContainerInfoCmd = &cobra.Command{ Short: "Get container field info", Long: `Get container field info`, Run: func(cmd *cobra.Command, args []string) { - var ( - cnr *container.Container - - ctx = context.Background() - ) + var cnr *container.Container if containerPathFrom != "" { data, err := os.ReadFile(containerPathFrom) @@ -290,17 +273,18 @@ var getContainerInfoCmd = &cobra.Command{ err = cnr.Unmarshal(data) exitOnErr(cmd, errf("can't unmarshal container: %w", err)) } else { - key, err := getKey() - exitOnErr(cmd, err) - - cli, err := getSDKClient(key) - exitOnErr(cmd, err) - id, err := parseContainerID(containerID) exitOnErr(cmd, err) - cnr, err = cli.GetContainer(ctx, id, globalCallOptions()...) + var prm internalclient.GetContainerPrm + + prepareAPIClient(cmd, &prm) + prm.SetContainerID(id) + + res, err := internalclient.GetContainer(prm) exitOnErr(cmd, errf("rpc error: %w", err)) + + cnr = res.Container() } prettyPrintContainer(cmd, cnr, containerJSON) @@ -330,21 +314,19 @@ var getExtendedACLCmd = &cobra.Command{ Short: "Get extended ACL table of container", Long: `Get extended ACL talbe of container`, Run: func(cmd *cobra.Command, args []string) { - ctx := context.Background() - - key, err := getKey() - exitOnErr(cmd, err) - - cli, err := getSDKClient(key) - exitOnErr(cmd, err) - id, err := parseContainerID(containerID) exitOnErr(cmd, err) - res, err := cli.GetEACL(ctx, id, globalCallOptions()...) + var eaclPrm internalclient.EACLPrm + + prepareAPIClient(cmd, &eaclPrm) + eaclPrm.SetContainerID(id) + + res, err := internalclient.EACL(eaclPrm) exitOnErr(cmd, errf("rpc error: %w", err)) eaclTable := res.EACL() + sig := eaclTable.Signature() if containerPathTo == "" { @@ -383,14 +365,6 @@ var setExtendedACLCmd = &cobra.Command{ Long: `Set new extended ACL table for container. Container ID in EACL table will be substituted with ID from the CLI.`, Run: func(cmd *cobra.Command, args []string) { - ctx := context.Background() - - key, err := getKey() - exitOnErr(cmd, err) - - cli, err := getSDKClient(key) - exitOnErr(cmd, err) - id, err := parseContainerID(containerID) exitOnErr(cmd, err) @@ -403,7 +377,16 @@ Container ID in EACL table will be substituted with ID from the CLI.`, eaclTable.SetCID(id) eaclTable.SetSessionToken(tok) - err = cli.SetEACL(ctx, eaclTable, globalCallOptions()...) + var ( + setEACLPrm internalclient.SetEACLPrm + getEACLPrm internalclient.EACLPrm + ) + + prepareAPIClient(cmd, &setEACLPrm, &getEACLPrm) + setEACLPrm.SetSessionToken(tok) + setEACLPrm.SetEACLTable(eaclTable) + + _, err = internalclient.SetEACL(setEACLPrm) exitOnErr(cmd, errf("rpc error: %w", err)) if containerAwait { @@ -412,13 +395,15 @@ Container ID in EACL table will be substituted with ID from the CLI.`, cmd.Println("awaiting...") + getEACLPrm.SetContainerID(id) + for i := 0; i < awaitTimeout; i++ { time.Sleep(1 * time.Second) - tableSig, err := cli.GetEACL(ctx, id, globalCallOptions()...) + res, err := internalclient.EACL(getEACLPrm) if err == nil { // compare binary values because EACL could have been set already - got, err := tableSig.EACL().Marshal() + got, err := res.EACL().Marshal() if err != nil { continue } diff --git a/cmd/neofs-cli/modules/netmap.go b/cmd/neofs-cli/modules/netmap.go index 2364e7d4..ddd54444 100644 --- a/cmd/neofs-cli/modules/netmap.go +++ b/cmd/neofs-cli/modules/netmap.go @@ -1,13 +1,13 @@ package cmd import ( - "context" "encoding/hex" "time" "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neofs-api-go/pkg/netmap" + internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap/wrapper" "github.com/nspcc-dev/neofs-node/pkg/services/control" "github.com/spf13/cobra" @@ -61,15 +61,15 @@ var getEpochCmd = &cobra.Command{ Short: "Get current epoch number", Long: "Get current epoch number", Run: func(cmd *cobra.Command, args []string) { - key, err := getKey() - exitOnErr(cmd, err) + var prm internalclient.NetworkInfoPrm - cli, err := getSDKClient(key) - exitOnErr(cmd, err) + prepareAPIClient(cmd, &prm) - netInfo, err := cli.NetworkInfo(context.Background(), globalCallOptions()...) + res, err := internalclient.NetworkInfo(prm) exitOnErr(cmd, errf("rpc error: %w", err)) + netInfo := res.NetworkInfo() + cmd.Println(netInfo.CurrentEpoch()) }, } @@ -79,16 +79,14 @@ var localNodeInfoCmd = &cobra.Command{ Short: "Get local node info", Long: `Get local node info`, Run: func(cmd *cobra.Command, args []string) { - key, err := getKey() - exitOnErr(cmd, err) + var prm internalclient.NodeInfoPrm - cli, err := getSDKClient(key) - exitOnErr(cmd, err) + prepareAPIClient(cmd, &prm) - nodeInfo, err := cli.EndpointInfo(context.Background(), globalCallOptions()...) + res, err := internalclient.NodeInfo(prm) exitOnErr(cmd, errf("rpc error: %w", err)) - prettyPrintNodeInfo(cmd, nodeInfo.NodeInfo(), nodeInfoJSON) + prettyPrintNodeInfo(cmd, res.NodeInfo(), nodeInfoJSON) }, } @@ -153,15 +151,15 @@ var netInfoCmd = &cobra.Command{ Short: "Get information about NeoFS network", Long: "Get information about NeoFS network", Run: func(cmd *cobra.Command, args []string) { - key, err := getKey() - exitOnErr(cmd, err) + var prm internalclient.NetworkInfoPrm - cli, err := getSDKClient(key) - exitOnErr(cmd, err) + prepareAPIClient(cmd, &prm) - netInfo, err := cli.NetworkInfo(context.Background(), globalCallOptions()...) + res, err := internalclient.NetworkInfo(prm) exitOnErr(cmd, errf("rpc error: %w", err)) + netInfo := res.NetworkInfo() + cmd.Printf("Epoch: %d\n", netInfo.CurrentEpoch()) magic := netInfo.MagicNumber() diff --git a/cmd/neofs-cli/modules/object.go b/cmd/neofs-cli/modules/object.go index 785c3501..bfa21ee9 100644 --- a/cmd/neofs-cli/modules/object.go +++ b/cmd/neofs-cli/modules/object.go @@ -1,26 +1,25 @@ package cmd import ( - "context" "crypto/ecdsa" "encoding/hex" "errors" "fmt" "io" - "math" "os" "path/filepath" "strconv" "strings" "time" - "github.com/nspcc-dev/neofs-api-go/pkg/client" + "github.com/nspcc-dev/neofs-api-go/pkg" cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/nspcc-dev/neofs-api-go/pkg/session" "github.com/nspcc-dev/neofs-api-go/pkg/token" objectV2 "github.com/nspcc-dev/neofs-api-go/v2/object" + internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" "github.com/spf13/cobra" ) @@ -36,6 +35,8 @@ const ( getRangeHashSaltFlag = "salt" ) +const bearerTokenFlag = "bearer" + var ( // objectCmd represents the object command objectCmd = &cobra.Command{ @@ -249,7 +250,7 @@ func init() { for _, objCommand := range objectChildCommands { flags := objCommand.Flags() - flags.String("bearer", "", "File with signed JSON or binary encoded bearer token") + flags.String(bearerTokenFlag, "", "File with signed JSON or binary encoded bearer token") flags.StringSliceVarP(&xHeaders, xHeadersKey, xHeadersShorthand, xHeadersDefault, xHeadersUsage) flags.Uint32P(ttl, ttlShorthand, ttlDefault, ttlUsage) } @@ -273,16 +274,78 @@ func init() { initObjectRangeCmd() } -func initSession(ctx context.Context, key *ecdsa.PrivateKey) (client.Client, *session.Token, error) { - cli, err := getSDKClient(key) - if err != nil { - return nil, nil, fmt.Errorf("can't create client: %w", err) +type clientKeySession interface { + clientWithKey + SetSessionToken(*session.Token) +} + +func prepareSessionPrm(cmd *cobra.Command, prms ...clientKeySession) { + key, err := getKey() + exitOnErr(cmd, errf("get private key: %w", err)) + + prepareSessionPrmWithKey(cmd, key, prms...) +} + +func prepareSessionPrmWithKey(cmd *cobra.Command, key *ecdsa.PrivateKey, prms ...clientKeySession) { + ownerID, err := getOwnerID(key) + exitOnErr(cmd, errf("owner ID from key: %w", err)) + + prepareSessionPrmWithOwner(cmd, key, ownerID, prms...) +} + +func prepareSessionPrmWithOwner( + cmd *cobra.Command, + key *ecdsa.PrivateKey, + ownerID *owner.ID, + prms ...clientKeySession, +) { + var sessionPrm internalclient.CreateSessionPrm + + cws := make([]clientWithKey, 1, len(prms)+1) + cws[0] = &sessionPrm + + for i := range prms { + cws = append(cws, prms[i]) } - tok, err := cli.CreateSession(ctx, math.MaxUint64) - if err != nil { - return nil, nil, fmt.Errorf("can't create session: %w", err) + + prepareAPIClientWithKey(cmd, key, cws...) + + sessionRes, err := internalclient.CreateSession(sessionPrm) + exitOnErr(cmd, errf("open session: %w", err)) + + tok := session.NewToken() + tok.SetID(sessionRes.ID()) + tok.SetSessionKey(sessionRes.SessionKey()) + tok.SetOwnerID(ownerID) + + for i := range prms { + prms[i].SetSessionToken(tok) } - return cli, tok, nil +} + +type objectPrm interface { + bearerPrm + SetTTL(uint32) + SetXHeaders([]*pkg.XHeader) +} + +func prepareObjectPrm(cmd *cobra.Command, prms ...objectPrm) { + for i := range prms { + prepareBearerPrm(cmd, prms[i]) + + prms[i].SetTTL(getTTL()) + prms[i].SetXHeaders(parseXHeaders()) + } +} + +func prepareObjectPrmRaw(cmd *cobra.Command, prm interface { + objectPrm + SetRawFlag(bool) +}) { + prepareObjectPrm(cmd, prm) + + raw, _ := cmd.Flags().GetBool(rawFlag) + prm.SetRawFlag(raw) } func putObject(cmd *cobra.Command, _ []string) { @@ -328,56 +391,40 @@ func putObject(cmd *cobra.Command, _ []string) { obj.SetOwnerID(ownerID) obj.SetAttributes(attrs...) - ctx := context.Background() - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) - btok, err := getBearerToken(cmd, "bearer") - exitOnErr(cmd, err) - oid, err := cli.PutObject(ctx, - new(client.PutObjectParams). - WithObject(obj.Object()). - WithPayloadReader(f), - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(btok), - )..., - ) + var prm internalclient.PutObjectPrm + + prepareSessionPrmWithOwner(cmd, key, ownerID, &prm) + prepareObjectPrm(cmd, &prm) + prm.SetHeader(obj.Object()) + prm.SetPayloadReader(f) + + res, err := internalclient.PutObject(prm) exitOnErr(cmd, errf("rpc error: %w", err)) cmd.Printf("[%s] Object successfully stored\n", filename) - cmd.Printf(" ID: %s\n CID: %s\n", oid, cid) + cmd.Printf(" ID: %s\n CID: %s\n", res.ID(), cid) } func deleteObject(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, err) - objAddr, err := getObjectAddress(cmd) exitOnErr(cmd, err) - ctx := context.Background() - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) - btok, err := getBearerToken(cmd, "bearer") - exitOnErr(cmd, err) + var prm internalclient.DeleteObjectPrm - tombstoneAddr, err := client.DeleteObject(ctx, cli, - new(client.DeleteObjectParams).WithAddress(objAddr), - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(btok), - )..., - ) + prepareSessionPrm(cmd, &prm) + prepareObjectPrm(cmd, &prm) + prm.SetAddress(objAddr) + + res, err := internalclient.DeleteObject(prm) exitOnErr(cmd, errf("rpc error: %w", err)) + tombstoneAddr := res.TombstoneAddress() + cmd.Println("Object removed successfully.") cmd.Printf(" ID: %s\n CID: %s\n", tombstoneAddr.ObjectID(), tombstoneAddr.ContainerID()) } func getObject(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, err) - objAddr, err := getObjectAddress(cmd) exitOnErr(cmd, err) @@ -396,24 +443,14 @@ func getObject(cmd *cobra.Command, _ []string) { out = f } - ctx := context.Background() - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) - btok, err := getBearerToken(cmd, "bearer") - exitOnErr(cmd, err) + var prm internalclient.GetObjectPrm - raw, _ := cmd.Flags().GetBool(rawFlag) + prepareSessionPrm(cmd, &prm) + prepareObjectPrmRaw(cmd, &prm) + prm.SetAddress(objAddr) + prm.SetPayloadWriter(out) - obj, err := cli.GetObject(ctx, - new(client.GetObjectParams). - WithAddress(objAddr). - WithPayloadWriter(out). - WithRawFlag(raw), - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(btok), - )..., - ) + res, err := internalclient.GetObject(prm) if err != nil { if ok := printSplitInfoErr(cmd, err); ok { return @@ -429,37 +466,25 @@ func getObject(cmd *cobra.Command, _ []string) { // Print header only if file is not streamed to stdout. hdrFile := cmd.Flag("header").Value.String() if filename != "" || hdrFile != "" { - err = saveAndPrintHeader(cmd, obj, hdrFile) + err = saveAndPrintHeader(cmd, res.Header(), hdrFile) exitOnErr(cmd, err) } } func getObjectHeader(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, err) - objAddr, err := getObjectAddress(cmd) exitOnErr(cmd, err) - ctx := context.Background() - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) - btok, err := getBearerToken(cmd, "bearer") - exitOnErr(cmd, err) - ps := new(client.ObjectHeaderParams).WithAddress(objAddr) - if ok, _ := cmd.Flags().GetBool("main-only"); ok { - ps = ps.WithMainFields() - } + mainOnly, _ := cmd.Flags().GetBool("main-only") - raw, _ := cmd.Flags().GetBool(rawFlag) - ps.WithRawFlag(raw) + var prm internalclient.HeadObjectPrm - obj, err := cli.GetObjectHeader(ctx, ps, - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(btok), - )..., - ) + prepareSessionPrm(cmd, &prm) + prepareObjectPrmRaw(cmd, &prm) + prm.SetAddress(objAddr) + prm.SetMainOnlyFlag(mainOnly) + + res, err := internalclient.HeadObject(prm) if err != nil { if ok := printSplitInfoErr(cmd, err); ok { return @@ -468,33 +493,29 @@ func getObjectHeader(cmd *cobra.Command, _ []string) { exitOnErr(cmd, errf("rpc error: %w", err)) } - err = saveAndPrintHeader(cmd, obj, cmd.Flag("file").Value.String()) + err = saveAndPrintHeader(cmd, res.Header(), cmd.Flag("file").Value.String()) exitOnErr(cmd, err) } func searchObject(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, err) - cid, err := getCID(cmd) exitOnErr(cmd, err) sf, err := parseSearchFilters(cmd) exitOnErr(cmd, err) - ctx := context.Background() - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) - btok, err := getBearerToken(cmd, "bearer") - exitOnErr(cmd, err) - ps := new(client.SearchObjectParams).WithContainerID(cid).WithSearchFilters(sf) - ids, err := cli.SearchObject(ctx, ps, - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(btok), - )..., - ) + var prm internalclient.SearchObjectsPrm + + prepareSessionPrm(cmd, &prm) + prepareObjectPrm(cmd, &prm) + prm.SetContainerID(cid) + prm.SetFilters(sf) + + res, err := internalclient.SearchObjects(prm) exitOnErr(cmd, errf("rpc error: %w", err)) + + ids := res.IDList() + cmd.Printf("Found %d objects.\n", len(ids)) for _, id := range ids { cmd.Println(id) @@ -502,9 +523,6 @@ func searchObject(cmd *cobra.Command, _ []string) { } func getObjectHash(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, errf("can't fetch private key: %w", err)) - objAddr, err := getObjectAddress(cmd) exitOnErr(cmd, err) ranges, err := getRangeList(cmd) @@ -517,59 +535,61 @@ func getObjectHash(cmd *cobra.Command, _ []string) { salt, err := hex.DecodeString(strSalt) exitOnErr(cmd, errf("could not decode salt: %w", err)) - ctx := context.Background() - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) - btok, err := getBearerToken(cmd, "bearer") - exitOnErr(cmd, err) - if len(ranges) == 0 { // hash of full payload - obj, err := cli.GetObjectHeader(ctx, - new(client.ObjectHeaderParams).WithAddress(objAddr), - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(btok), - )..., - ) + var ( + hashPrm internalclient.HashPayloadRangesPrm + headPrm internalclient.HeadObjectPrm + + sesPrms = []clientKeySession{&hashPrm} + objPrms = []objectPrm{&hashPrm} + ) + + fullHash := len(ranges) == 0 + if fullHash { + sesPrms = append(sesPrms, &headPrm) + objPrms = append(objPrms, &headPrm) + } + + prepareSessionPrm(cmd, sesPrms...) + prepareObjectPrm(cmd, objPrms...) + + tz := typ == hashTz + + if fullHash { + headPrm.SetAddress(objAddr) + + // get hash of full payload through HEAD (may be user can do it through dedicated command?) + res, err := internalclient.HeadObject(headPrm) exitOnErr(cmd, errf("rpc error: %w", err)) - switch typ { - case hashSha256: - cmd.Println(hex.EncodeToString(obj.PayloadChecksum().Sum())) - case hashTz: - cmd.Println(hex.EncodeToString(obj.PayloadHomomorphicHash().Sum())) + + var cs *pkg.Checksum + + if tz { + cs = res.Header().PayloadHomomorphicHash() + } else { + cs = res.Header().PayloadChecksum() } + + cmd.Println(hex.EncodeToString(cs.Sum())) + return } - ps := new(client.RangeChecksumParams). - WithAddress(objAddr). - WithRangeList(ranges...). - WithSalt(salt) + hashPrm.SetAddress(objAddr) + hashPrm.SetSalt(salt) + hashPrm.SetRanges(ranges) - switch typ { - case hashSha256: - res, err := cli.ObjectPayloadRangeSHA256(ctx, ps, - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(btok), - )..., - ) - exitOnErr(cmd, errf("rpc error: %w", err)) - for i := range res { - cmd.Printf("Offset=%d (Length=%d)\t: %s\n", ranges[i].GetOffset(), ranges[i].GetLength(), - hex.EncodeToString(res[i][:])) - } - case hashTz: - res, err := cli.ObjectPayloadRangeTZ(ctx, ps, - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(btok), - )..., - ) - exitOnErr(cmd, errf("rpc error: %w", err)) - for i := range res { - cmd.Printf("Offset=%d (Length=%d)\t: %s\n", ranges[i].GetOffset(), ranges[i].GetLength(), - hex.EncodeToString(res[i][:])) - } + if tz { + hashPrm.TZ() + } + + res, err := internalclient.HashPayloadRanges(hashPrm) + exitOnErr(cmd, errf("rpc error: %w", err)) + + hs := res.HashList() + + for i := range hs { + cmd.Printf("Offset=%d (Length=%d)\t: %s\n", ranges[i].GetOffset(), ranges[i].GetLength(), + hex.EncodeToString(hs[i])) } } @@ -899,9 +919,6 @@ func getBearerToken(cmd *cobra.Command, flagname string) (*token.BearerToken, er } func getObjectRange(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, errf("can't fetch private key: %w", err)) - objAddr, err := getObjectAddress(cmd) exitOnErr(cmd, err) @@ -928,27 +945,15 @@ func getObjectRange(cmd *cobra.Command, _ []string) { out = f } - ctx := context.Background() + var prm internalclient.PayloadRangePrm - c, sessionToken, err := initSession(ctx, key) - exitOnErr(cmd, err) + prepareSessionPrm(cmd, &prm) + prepareObjectPrmRaw(cmd, &prm) + prm.SetAddress(objAddr) + prm.SetRange(ranges[0]) + prm.SetPayloadWriter(out) - bearerToken, err := getBearerToken(cmd, "bearer") - exitOnErr(cmd, err) - - raw, _ := cmd.Flags().GetBool(rawFlag) - - _, err = c.ObjectPayloadRangeData(ctx, - new(client.RangeDataParams). - WithAddress(objAddr). - WithRange(ranges[0]). - WithDataWriter(out). - WithRaw(raw), - append(globalCallOptions(), - client.WithSession(sessionToken), - client.WithBearer(bearerToken), - )..., - ) + _, err = internalclient.PayloadRange(prm) if err != nil { if ok := printSplitInfoErr(cmd, err); ok { return diff --git a/cmd/neofs-cli/modules/root.go b/cmd/neofs-cli/modules/root.go index e41e2bcb..d6a8da91 100644 --- a/cmd/neofs-cli/modules/root.go +++ b/cmd/neofs-cli/modules/root.go @@ -18,6 +18,7 @@ import ( "github.com/nspcc-dev/neofs-api-go/pkg" "github.com/nspcc-dev/neofs-api-go/pkg/client" "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-api-go/pkg/token" "github.com/nspcc-dev/neofs-node/misc" "github.com/nspcc-dev/neofs-node/pkg/network" "github.com/spf13/cobra" @@ -287,6 +288,41 @@ func getEndpointAddress(endpointFlag string) (addr network.Address, err error) { return } +type clientWithKey interface { + SetClient(client.Client) + SetKey(*ecdsa.PrivateKey) +} + +// reads private key from command args and call prepareAPIClientWithKey with it. +func prepareAPIClient(cmd *cobra.Command, dst ...clientWithKey) { + key, err := getKey() + exitOnErr(cmd, errf("get private key: %w", err)) + + prepareAPIClientWithKey(cmd, key, dst...) +} + +// creates NeoFS API client and writes it to target along with the private key. +func prepareAPIClientWithKey(cmd *cobra.Command, key *ecdsa.PrivateKey, dst ...clientWithKey) { + cli, err := getSDKClient(key) + exitOnErr(cmd, errf("create API client: %w", err)) + + for _, d := range dst { + d.SetClient(cli) + d.SetKey(key) + } +} + +type bearerPrm interface { + SetBearerToken(prm *token.BearerToken) +} + +func prepareBearerPrm(cmd *cobra.Command, prm bearerPrm) { + btok, err := getBearerToken(cmd, bearerTokenFlag) + exitOnErr(cmd, errf("bearer token: %w", err)) + + prm.SetBearerToken(btok) +} + // getSDKClient returns default neofs-api-go sdk client. Consider using // opts... to provide TTL or other global configuration flags. func getSDKClient(key *ecdsa.PrivateKey) (client.Client, error) { @@ -356,19 +392,6 @@ func parseXHeaders() []*pkg.XHeader { return xs } -func globalCallOptions() []client.CallOption { - xHdrs := parseXHeaders() - - opts := make([]client.CallOption, 0, len(xHdrs)+1) // + TTL - opts = append(opts, client.WithTTL(getTTL())) - - for i := range xHdrs { - opts = append(opts, client.WithXHeader(xHdrs[i])) - } - - return opts -} - // add common flags to the command: // - key; // - wallet; diff --git a/cmd/neofs-cli/modules/storagegroup.go b/cmd/neofs-cli/modules/storagegroup.go index 2bf17e38..aff0ccce 100644 --- a/cmd/neofs-cli/modules/storagegroup.go +++ b/cmd/neofs-cli/modules/storagegroup.go @@ -1,15 +1,13 @@ package cmd import ( - "context" + "bytes" "errors" "fmt" - "github.com/nspcc-dev/neofs-api-go/pkg/client" objectSDK "github.com/nspcc-dev/neofs-api-go/pkg/object" - "github.com/nspcc-dev/neofs-api-go/pkg/session" storagegroupAPI "github.com/nspcc-dev/neofs-api-go/pkg/storagegroup" - "github.com/nspcc-dev/neofs-api-go/pkg/token" + internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" "github.com/nspcc-dev/neofs-node/pkg/core/object" "github.com/nspcc-dev/neofs-node/pkg/services/object_manager/storagegroup" "github.com/spf13/cobra" @@ -59,7 +57,6 @@ var sgDelCmd = &cobra.Command{ const ( sgMembersFlag = "members" sgIDFlag = "id" - sgBearerFlag = "bearer" ) var ( @@ -124,7 +121,7 @@ func init() { for _, sgCommand := range storageGroupChildCommands { flags := sgCommand.Flags() - flags.String(sgBearerFlag, "", "File with signed JSON or binary encoded bearer token") + flags.String(bearerTokenFlag, "", "File with signed JSON or binary encoded bearer token") flags.StringSliceVarP(&xHeaders, xHeadersKey, xHeadersShorthand, xHeadersDefault, xHeadersUsage) flags.Uint32P(ttl, ttlShorthand, ttlDefault, ttlUsage) } @@ -136,24 +133,13 @@ func init() { } type sgHeadReceiver struct { - ctx context.Context - - tok *session.Token - - c client.Client - - bearerToken *token.BearerToken + prm internalclient.HeadObjectPrm } -func (c *sgHeadReceiver) Head(addr *objectSDK.Address) (interface{}, error) { - obj, err := c.c.GetObjectHeader(c.ctx, - new(client.ObjectHeaderParams). - WithAddress(addr). - WithRawFlag(true), - client.WithTTL(2), - client.WithSession(c.tok), - client.WithBearer(c.bearerToken), - ) +func (c sgHeadReceiver) Head(addr *objectSDK.Address) (interface{}, error) { + c.prm.SetAddress(addr) + + res, err := internalclient.HeadObject(c.prm) var errSplitInfo *objectSDK.SplitInfoError @@ -161,16 +147,12 @@ func (c *sgHeadReceiver) Head(addr *objectSDK.Address) (interface{}, error) { default: return nil, err case err == nil: - return object.NewFromSDK(obj), nil + return object.NewFromSDK(res.Header()), nil case errors.As(err, &errSplitInfo): return errSplitInfo.SplitInfo(), nil } } -func sgBearerToken(cmd *cobra.Command) (*token.BearerToken, error) { - return getBearerToken(cmd, sgBearerFlag) -} - func putSG(cmd *cobra.Command, _ []string) { key, err := getKey() exitOnErr(cmd, err) @@ -192,19 +174,18 @@ func putSG(cmd *cobra.Command, _ []string) { members = append(members, id) } - bearerToken, err := sgBearerToken(cmd) - exitOnErr(cmd, err) + var ( + headPrm internalclient.HeadObjectPrm + putPrm internalclient.PutObjectPrm + ) - ctx := context.Background() + prepareSessionPrmWithOwner(cmd, key, ownerID, &headPrm, &putPrm) + prepareObjectPrm(cmd, &headPrm, &putPrm) - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) + headPrm.SetRawFlag(true) - sg, err := storagegroup.CollectMembers(&sgHeadReceiver{ - ctx: ctx, - tok: tok, - c: cli, - bearerToken: bearerToken, + sg, err := storagegroup.CollectMembers(sgHeadReceiver{ + prm: headPrm, }, cid, members) exitOnErr(cmd, errf("could not collect storage group members: %w", err)) @@ -215,20 +196,15 @@ func putSG(cmd *cobra.Command, _ []string) { obj.SetContainerID(cid) obj.SetOwnerID(ownerID) obj.SetType(objectSDK.TypeStorageGroup) - obj.SetPayload(sgContent) - oid, err := cli.PutObject(ctx, - new(client.PutObjectParams). - WithObject(obj.Object()), - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(bearerToken), - )..., - ) + putPrm.SetHeader(obj.Object()) + putPrm.SetPayloadReader(bytes.NewReader(sgContent)) + + res, err := internalclient.PutObject(putPrm) exitOnErr(cmd, errf("rpc error: %w", err)) cmd.Println("Storage group successfully stored") - cmd.Printf(" ID: %s\n CID: %s\n", oid, cid) + cmd.Printf(" ID: %s\n CID: %s\n", res.ID(), cid) } func getSGID() (*objectSDK.ID, error) { @@ -242,40 +218,31 @@ func getSGID() (*objectSDK.ID, error) { } func getSG(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, err) - cid, err := getCID(cmd) exitOnErr(cmd, err) id, err := getSGID() exitOnErr(cmd, err) - bearerToken, err := sgBearerToken(cmd) - exitOnErr(cmd, err) - addr := objectSDK.NewAddress() addr.SetContainerID(cid) addr.SetObjectID(id) - ctx := context.Background() + buf := bytes.NewBuffer(nil) - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) + var prm internalclient.GetObjectPrm - obj, err := cli.GetObject(ctx, - new(client.GetObjectParams). - WithAddress(addr), - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(bearerToken), - )..., - ) + prepareSessionPrm(cmd, &prm) + prepareObjectPrmRaw(cmd, &prm) + prm.SetAddress(addr) + prm.SetPayloadWriter(buf) + + _, err = internalclient.GetObject(prm) exitOnErr(cmd, errf("rpc error: %w", err)) sg := storagegroupAPI.New() - err = sg.Unmarshal(obj.Payload()) + err = sg.Unmarshal(buf.Bytes()) exitOnErr(cmd, errf("could not unmarshal storage group: %w", err)) cmd.Printf("Expiration epoch: %d\n", sg.ExpirationEpoch()) @@ -292,31 +259,21 @@ func getSG(cmd *cobra.Command, _ []string) { } func listSG(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, err) - cid, err := getCID(cmd) exitOnErr(cmd, err) - bearerToken, err := sgBearerToken(cmd) - exitOnErr(cmd, err) + var prm internalclient.SearchObjectsPrm - ctx := context.Background() + prepareSessionPrm(cmd, &prm) + prepareObjectPrm(cmd, &prm) + prm.SetContainerID(cid) + prm.SetFilters(storagegroup.SearchQuery()) - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) - - ids, err := cli.SearchObject(ctx, - new(client.SearchObjectParams). - WithContainerID(cid). - WithSearchFilters(storagegroup.SearchQuery()), - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(bearerToken), - )..., - ) + res, err := internalclient.SearchObjects(prm) exitOnErr(cmd, errf("rpc error: %w", err)) + ids := res.IDList() + cmd.Printf("Found %d storage groups.\n", len(ids)) for _, id := range ids { @@ -325,37 +282,27 @@ func listSG(cmd *cobra.Command, _ []string) { } func delSG(cmd *cobra.Command, _ []string) { - key, err := getKey() - exitOnErr(cmd, err) - cid, err := getCID(cmd) exitOnErr(cmd, err) id, err := getSGID() exitOnErr(cmd, err) - bearerToken, err := sgBearerToken(cmd) - exitOnErr(cmd, err) - - ctx := context.Background() - - cli, tok, err := initSession(ctx, key) - exitOnErr(cmd, err) - addr := objectSDK.NewAddress() addr.SetContainerID(cid) addr.SetObjectID(id) - tombstone, err := client.DeleteObject(ctx, cli, - new(client.DeleteObjectParams). - WithAddress(addr), - append(globalCallOptions(), - client.WithSession(tok), - client.WithBearer(bearerToken), - )..., - ) + var prm internalclient.DeleteObjectPrm + + prepareSessionPrm(cmd, &prm) + prepareObjectPrm(cmd, &prm) + prm.SetAddress(addr) + + res, err := internalclient.DeleteObject(prm) exitOnErr(cmd, errf("rpc error: %w", err)) + tombstone := res.TombstoneAddress() + cmd.Println("Storage group removed successfully.") cmd.Printf(" Tombstone: %s\n", tombstone.ObjectID()) }