diff --git a/cmd/neofs-cli/internal/client/client.go b/cmd/neofs-cli/internal/client/client.go new file mode 100644 index 000000000..6a8177032 --- /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 000000000..73660997c --- /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 000000000..37c13cfd8 --- /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 5c4c90426..c13c18587 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 56e040bac..c3468f9f6 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 2364e7d41..ddd544447 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 785c3501b..bfa21ee94 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 e41e2bcb5..d6a8da910 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 2bf17e383..aff0ccce6 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()) }