Move to frostfs-node
Signed-off-by: Pavel Karpy <p.karpy@yadro.com>
This commit is contained in:
parent
42554a9298
commit
923f84722a
934 changed files with 3470 additions and 3451 deletions
892
cmd/frostfs-cli/internal/client/client.go
Normal file
892
cmd/frostfs-cli/internal/client/client.go
Normal file
|
@ -0,0 +1,892 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/accounting"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/client"
|
||||
containerSDK "github.com/TrueCloudLab/frostfs-sdk-go/container"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/version"
|
||||
)
|
||||
|
||||
// BalanceOfPrm groups parameters of BalanceOf operation.
|
||||
type BalanceOfPrm struct {
|
||||
commonPrm
|
||||
client.PrmBalanceGet
|
||||
}
|
||||
|
||||
// BalanceOfRes groups the resulting values of BalanceOf operation.
|
||||
type BalanceOfRes struct {
|
||||
cliRes *client.ResBalanceGet
|
||||
}
|
||||
|
||||
// Balance returns the current balance.
|
||||
func (x BalanceOfRes) Balance() accounting.Decimal {
|
||||
return x.cliRes.Amount()
|
||||
}
|
||||
|
||||
// BalanceOf requests the current balance of a NeoFS user.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func BalanceOf(prm BalanceOfPrm) (res BalanceOfRes, err error) {
|
||||
res.cliRes, err = prm.cli.BalanceGet(context.Background(), prm.PrmBalanceGet)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ListContainersPrm groups parameters of ListContainers operation.
|
||||
type ListContainersPrm struct {
|
||||
commonPrm
|
||||
client.PrmContainerList
|
||||
}
|
||||
|
||||
// ListContainersRes groups the resulting values of ListContainers operation.
|
||||
type ListContainersRes struct {
|
||||
cliRes *client.ResContainerList
|
||||
}
|
||||
|
||||
// IDList returns list of identifiers of user's containers.
|
||||
func (x ListContainersRes) IDList() []cid.ID {
|
||||
return x.cliRes.Containers()
|
||||
}
|
||||
|
||||
// ListContainers requests a list of NeoFS user's containers.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func ListContainers(prm ListContainersPrm) (res ListContainersRes, err error) {
|
||||
res.cliRes, err = prm.cli.ContainerList(context.Background(), prm.PrmContainerList)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PutContainerPrm groups parameters of PutContainer operation.
|
||||
type PutContainerPrm struct {
|
||||
commonPrm
|
||||
client.PrmContainerPut
|
||||
}
|
||||
|
||||
// PutContainerRes groups the resulting values of PutContainer operation.
|
||||
type PutContainerRes struct {
|
||||
cnr cid.ID
|
||||
}
|
||||
|
||||
// ID returns identifier of the created container.
|
||||
func (x PutContainerRes) ID() cid.ID {
|
||||
return x.cnr
|
||||
}
|
||||
|
||||
// PutContainer sends a request to save the container in NeoFS.
|
||||
//
|
||||
// Operation is asynchronous and not guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// Success can be verified by reading by identifier.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func PutContainer(prm PutContainerPrm) (res PutContainerRes, err error) {
|
||||
cliRes, err := prm.cli.ContainerPut(context.Background(), prm.PrmContainerPut)
|
||||
if err == nil {
|
||||
res.cnr = cliRes.ID()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetContainerPrm groups parameters of GetContainer operation.
|
||||
type GetContainerPrm struct {
|
||||
commonPrm
|
||||
cliPrm client.PrmContainerGet
|
||||
}
|
||||
|
||||
// SetContainer sets identifier of the container to be read.
|
||||
func (x *GetContainerPrm) SetContainer(id cid.ID) {
|
||||
x.cliPrm.SetContainer(id)
|
||||
}
|
||||
|
||||
// GetContainerRes groups the resulting values of GetContainer operation.
|
||||
type GetContainerRes struct {
|
||||
cliRes *client.ResContainerGet
|
||||
}
|
||||
|
||||
// Container returns structured of the requested container.
|
||||
func (x GetContainerRes) Container() containerSDK.Container {
|
||||
return x.cliRes.Container()
|
||||
}
|
||||
|
||||
// GetContainer reads a container from NeoFS by ID.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func GetContainer(prm GetContainerPrm) (res GetContainerRes, err error) {
|
||||
res.cliRes, err = prm.cli.ContainerGet(context.Background(), prm.cliPrm)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsACLExtendable checks if ACL of the container referenced by the given identifier
|
||||
// can be extended. Client connection MUST BE correctly established in advance.
|
||||
func IsACLExtendable(c *client.Client, cnr cid.ID) (bool, error) {
|
||||
var prm GetContainerPrm
|
||||
prm.SetClient(c)
|
||||
prm.SetContainer(cnr)
|
||||
|
||||
res, err := GetContainer(prm)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("get container from the NeoFS: %w", err)
|
||||
}
|
||||
|
||||
return res.Container().BasicACL().Extendable(), nil
|
||||
}
|
||||
|
||||
// DeleteContainerPrm groups parameters of DeleteContainerPrm operation.
|
||||
type DeleteContainerPrm struct {
|
||||
commonPrm
|
||||
client.PrmContainerDelete
|
||||
}
|
||||
|
||||
// DeleteContainerRes groups the resulting values of DeleteContainer operation.
|
||||
type DeleteContainerRes struct{}
|
||||
|
||||
// DeleteContainer sends a request to remove a container from NeoFS by ID.
|
||||
//
|
||||
// Operation is asynchronous and not guaranteed even in the absence of errors.
|
||||
// The required time is also not predictable.
|
||||
//
|
||||
// Success can be verified by reading by identifier.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func DeleteContainer(prm DeleteContainerPrm) (res DeleteContainerRes, err error) {
|
||||
_, err = prm.cli.ContainerDelete(context.Background(), prm.PrmContainerDelete)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EACLPrm groups parameters of EACL operation.
|
||||
type EACLPrm struct {
|
||||
commonPrm
|
||||
client.PrmContainerEACL
|
||||
}
|
||||
|
||||
// EACLRes groups the resulting values of EACL operation.
|
||||
type EACLRes struct {
|
||||
cliRes *client.ResContainerEACL
|
||||
}
|
||||
|
||||
// EACL returns requested eACL table.
|
||||
func (x EACLRes) EACL() eacl.Table {
|
||||
return x.cliRes.Table()
|
||||
}
|
||||
|
||||
// EACL reads eACL table from NeoFS by container ID.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func EACL(prm EACLPrm) (res EACLRes, err error) {
|
||||
res.cliRes, err = prm.cli.ContainerEACL(context.Background(), prm.PrmContainerEACL)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetEACLPrm groups parameters of SetEACL operation.
|
||||
type SetEACLPrm struct {
|
||||
commonPrm
|
||||
client.PrmContainerSetEACL
|
||||
}
|
||||
|
||||
// SetEACLRes groups the resulting values of SetEACL operation.
|
||||
type SetEACLRes struct{}
|
||||
|
||||
// SetEACL requests to save an 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.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func SetEACL(prm SetEACLPrm) (res SetEACLRes, err error) {
|
||||
_, err = prm.cli.ContainerSetEACL(context.Background(), prm.PrmContainerSetEACL)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NetworkInfoPrm groups parameters of NetworkInfo operation.
|
||||
type NetworkInfoPrm struct {
|
||||
commonPrm
|
||||
client.PrmNetworkInfo
|
||||
}
|
||||
|
||||
// NetworkInfoRes groups the resulting values of NetworkInfo operation.
|
||||
type NetworkInfoRes struct {
|
||||
cliRes *client.ResNetworkInfo
|
||||
}
|
||||
|
||||
// NetworkInfo returns structured information about the NeoFS network.
|
||||
func (x NetworkInfoRes) NetworkInfo() netmap.NetworkInfo {
|
||||
return x.cliRes.Info()
|
||||
}
|
||||
|
||||
// NetworkInfo reads information about the NeoFS network.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NetworkInfo(prm NetworkInfoPrm) (res NetworkInfoRes, err error) {
|
||||
res.cliRes, err = prm.cli.NetworkInfo(context.Background(), prm.PrmNetworkInfo)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NodeInfoPrm groups parameters of NodeInfo operation.
|
||||
type NodeInfoPrm struct {
|
||||
commonPrm
|
||||
client.PrmEndpointInfo
|
||||
}
|
||||
|
||||
// NodeInfoRes groups the resulting values of NodeInfo operation.
|
||||
type NodeInfoRes struct {
|
||||
cliRes *client.ResEndpointInfo
|
||||
}
|
||||
|
||||
// NodeInfo returns information about the node from netmap.
|
||||
func (x NodeInfoRes) NodeInfo() netmap.NodeInfo {
|
||||
return x.cliRes.NodeInfo()
|
||||
}
|
||||
|
||||
// LatestVersion returns the latest NeoFS API version in use.
|
||||
func (x NodeInfoRes) LatestVersion() version.Version {
|
||||
return x.cliRes.LatestVersion()
|
||||
}
|
||||
|
||||
// NodeInfo requests information about the remote server from NeoFS netmap.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NodeInfo(prm NodeInfoPrm) (res NodeInfoRes, err error) {
|
||||
res.cliRes, err = prm.cli.EndpointInfo(context.Background(), prm.PrmEndpointInfo)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NetMapSnapshotPrm groups parameters of NetMapSnapshot operation.
|
||||
type NetMapSnapshotPrm struct {
|
||||
commonPrm
|
||||
}
|
||||
|
||||
// NetMapSnapshotRes groups the resulting values of NetMapSnapshot operation.
|
||||
type NetMapSnapshotRes struct {
|
||||
cliRes *client.ResNetMapSnapshot
|
||||
}
|
||||
|
||||
// NetMap returns current local snapshot of the NeoFS network map.
|
||||
func (x NetMapSnapshotRes) NetMap() netmap.NetMap {
|
||||
return x.cliRes.NetMap()
|
||||
}
|
||||
|
||||
// NetMapSnapshot requests current network view of the remote server.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func NetMapSnapshot(prm NetMapSnapshotPrm) (res NetMapSnapshotRes, err error) {
|
||||
res.cliRes, err = prm.cli.NetMapSnapshot(context.Background(), client.PrmNetMapSnapshot{})
|
||||
return
|
||||
}
|
||||
|
||||
// CreateSessionPrm groups parameters of CreateSession operation.
|
||||
type CreateSessionPrm struct {
|
||||
commonPrm
|
||||
client.PrmSessionCreate
|
||||
}
|
||||
|
||||
// CreateSessionRes groups the resulting values of CreateSession operation.
|
||||
type CreateSessionRes struct {
|
||||
cliRes *client.ResSessionCreate
|
||||
}
|
||||
|
||||
// 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.PublicKey()
|
||||
}
|
||||
|
||||
// CreateSession opens a new unlimited session with the remote node.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func CreateSession(prm CreateSessionPrm) (res CreateSessionRes, err error) {
|
||||
res.cliRes, err = prm.cli.SessionCreate(context.Background(), prm.PrmSessionCreate)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PutObjectPrm groups parameters of PutObject operation.
|
||||
type PutObjectPrm struct {
|
||||
commonObjectPrm
|
||||
|
||||
hdr *object.Object
|
||||
|
||||
rdr io.Reader
|
||||
|
||||
headerCallback func(*object.Object)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// SetHeaderCallback sets callback which is called on the object after the header is received
|
||||
// but before the payload is written.
|
||||
func (x *PutObjectPrm) SetHeaderCallback(f func(*object.Object)) {
|
||||
x.headerCallback = f
|
||||
}
|
||||
|
||||
// PutObjectRes groups the resulting values of PutObject operation.
|
||||
type PutObjectRes struct {
|
||||
id oid.ID
|
||||
}
|
||||
|
||||
// ID returns identifier of the created object.
|
||||
func (x PutObjectRes) ID() oid.ID {
|
||||
return x.id
|
||||
}
|
||||
|
||||
// PutObject saves the object in NeoFS network.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func PutObject(prm PutObjectPrm) (*PutObjectRes, error) {
|
||||
var putPrm client.PrmObjectPutInit
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
putPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
putPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
putPrm.MarkLocal()
|
||||
}
|
||||
|
||||
putPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
wrt, err := prm.cli.ObjectPutInit(context.Background(), putPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object writing: %w", err)
|
||||
}
|
||||
|
||||
if wrt.WriteHeader(*prm.hdr) {
|
||||
if prm.headerCallback != nil {
|
||||
prm.headerCallback(prm.hdr)
|
||||
}
|
||||
|
||||
sz := prm.hdr.PayloadSize()
|
||||
|
||||
if data := prm.hdr.Payload(); len(data) > 0 {
|
||||
if prm.rdr != nil {
|
||||
prm.rdr = io.MultiReader(bytes.NewReader(data), prm.rdr)
|
||||
} else {
|
||||
prm.rdr = bytes.NewReader(data)
|
||||
sz = uint64(len(data))
|
||||
}
|
||||
}
|
||||
|
||||
if prm.rdr != nil {
|
||||
// TODO: (neofs-node#1198) explore better values or configure it
|
||||
const defaultBufferSizePut = 4096
|
||||
|
||||
if sz == 0 || sz > defaultBufferSizePut {
|
||||
sz = defaultBufferSizePut
|
||||
}
|
||||
|
||||
buf := make([]byte, sz)
|
||||
|
||||
var n int
|
||||
|
||||
for {
|
||||
n, err = prm.rdr.Read(buf)
|
||||
if n > 0 {
|
||||
if !wrt.WritePayloadChunk(buf[:n]) {
|
||||
break
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("read payload: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cliRes, err := wrt.Close()
|
||||
if err != nil { // here err already carries both status and client errors
|
||||
return nil, fmt.Errorf("client failure: %w", err)
|
||||
}
|
||||
|
||||
return &PutObjectRes{
|
||||
id: cliRes.StoredObjectID(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteObjectPrm groups parameters of DeleteObject operation.
|
||||
type DeleteObjectPrm struct {
|
||||
commonObjectPrm
|
||||
objectAddressPrm
|
||||
}
|
||||
|
||||
// DeleteObjectRes groups the resulting values of DeleteObject operation.
|
||||
type DeleteObjectRes struct {
|
||||
tomb oid.ID
|
||||
}
|
||||
|
||||
// Tombstone returns the ID of the created object with tombstone.
|
||||
func (x DeleteObjectRes) Tombstone() oid.ID {
|
||||
return x.tomb
|
||||
}
|
||||
|
||||
// DeleteObject marks an object to be removed from NeoFS through tombstone placement.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func DeleteObject(prm DeleteObjectPrm) (*DeleteObjectRes, error) {
|
||||
var delPrm client.PrmObjectDelete
|
||||
delPrm.FromContainer(prm.objAddr.Container())
|
||||
delPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
delPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
delPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
delPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
cliRes, err := prm.cli.ObjectDelete(context.Background(), delPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("remove object via client: %w", err)
|
||||
}
|
||||
|
||||
return &DeleteObjectRes{
|
||||
tomb: cliRes.Tombstone(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetObjectPrm groups parameters of GetObject operation.
|
||||
type GetObjectPrm struct {
|
||||
commonObjectPrm
|
||||
objectAddressPrm
|
||||
rawPrm
|
||||
payloadWriterPrm
|
||||
headerCallback func(*object.Object)
|
||||
}
|
||||
|
||||
// SetHeaderCallback sets callback which is called on the object after the header is received
|
||||
// but before the payload is written.
|
||||
func (p *GetObjectPrm) SetHeaderCallback(f func(*object.Object)) {
|
||||
p.headerCallback = f
|
||||
}
|
||||
|
||||
// GetObjectRes groups the resulting values of GetObject operation.
|
||||
type GetObjectRes struct {
|
||||
hdr *object.Object
|
||||
}
|
||||
|
||||
// Header returns the header of the request object.
|
||||
func (x GetObjectRes) Header() *object.Object {
|
||||
return x.hdr
|
||||
}
|
||||
|
||||
// GetObject reads an object by address.
|
||||
//
|
||||
// Interrupts on any writer error. If successful, payload is written to the writer.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func GetObject(prm GetObjectPrm) (*GetObjectRes, error) {
|
||||
var getPrm client.PrmObjectGet
|
||||
getPrm.FromContainer(prm.objAddr.Container())
|
||||
getPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
getPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
getPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.raw {
|
||||
getPrm.MarkRaw()
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
getPrm.MarkLocal()
|
||||
}
|
||||
|
||||
getPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
rdr, err := prm.cli.ObjectGetInit(context.Background(), getPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object reading on client: %w", err)
|
||||
}
|
||||
|
||||
var hdr object.Object
|
||||
|
||||
if !rdr.ReadHeader(&hdr) {
|
||||
_, err = rdr.Close()
|
||||
return nil, fmt.Errorf("read object header: %w", err)
|
||||
}
|
||||
if prm.headerCallback != nil {
|
||||
prm.headerCallback(&hdr)
|
||||
}
|
||||
|
||||
_, err = io.Copy(prm.wrt, rdr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copy payload: %w", err)
|
||||
}
|
||||
|
||||
return &GetObjectRes{
|
||||
hdr: &hdr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HeadObjectPrm groups parameters of HeadObject operation.
|
||||
type HeadObjectPrm struct {
|
||||
commonObjectPrm
|
||||
objectAddressPrm
|
||||
rawPrm
|
||||
|
||||
mainOnly bool
|
||||
}
|
||||
|
||||
// SetMainOnlyFlag sets flag to get only main fields of an object header in terms of NeoFS API.
|
||||
func (x *HeadObjectPrm) SetMainOnlyFlag(v bool) {
|
||||
x.mainOnly = v
|
||||
}
|
||||
|
||||
// HeadObjectRes groups the resulting values of HeadObject operation.
|
||||
type HeadObjectRes struct {
|
||||
hdr *object.Object
|
||||
}
|
||||
|
||||
// Header returns the requested object header.
|
||||
func (x HeadObjectRes) Header() *object.Object {
|
||||
return x.hdr
|
||||
}
|
||||
|
||||
// HeadObject reads an object header by address.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func HeadObject(prm HeadObjectPrm) (*HeadObjectRes, error) {
|
||||
var cliPrm client.PrmObjectHead
|
||||
cliPrm.FromContainer(prm.objAddr.Container())
|
||||
cliPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
cliPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.raw {
|
||||
cliPrm.MarkRaw()
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
cliPrm.MarkLocal()
|
||||
}
|
||||
|
||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
res, err := prm.cli.ObjectHead(context.Background(), cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read object header via client: %w", err)
|
||||
}
|
||||
|
||||
var hdr object.Object
|
||||
|
||||
if !res.ReadHeader(&hdr) {
|
||||
return nil, fmt.Errorf("missing header in response")
|
||||
}
|
||||
|
||||
return &HeadObjectRes{
|
||||
hdr: &hdr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 the resulting values of SearchObjects operation.
|
||||
type SearchObjectsRes struct {
|
||||
ids []oid.ID
|
||||
}
|
||||
|
||||
// IDList returns identifiers of the matched objects.
|
||||
func (x SearchObjectsRes) IDList() []oid.ID {
|
||||
return x.ids
|
||||
}
|
||||
|
||||
// SearchObjects selects objects from the container which match the filters.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
func SearchObjects(prm SearchObjectsPrm) (*SearchObjectsRes, error) {
|
||||
var cliPrm client.PrmObjectSearch
|
||||
cliPrm.InContainer(prm.cnrID)
|
||||
cliPrm.SetFilters(prm.filters)
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
cliPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
cliPrm.MarkLocal()
|
||||
}
|
||||
|
||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
rdr, err := prm.cli.ObjectSearchInit(context.Background(), cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init object search: %w", err)
|
||||
}
|
||||
|
||||
buf := make([]oid.ID, 10)
|
||||
var list []oid.ID
|
||||
var n int
|
||||
var ok bool
|
||||
|
||||
for {
|
||||
n, ok = rdr.Read(buf)
|
||||
for i := 0; i < n; i++ {
|
||||
list = append(list, buf[i])
|
||||
}
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_, err = rdr.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read object list: %w", err)
|
||||
}
|
||||
|
||||
return &SearchObjectsRes{
|
||||
ids: list,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 a 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 the resulting values of HashPayloadRanges operation.
|
||||
type HashPayloadRangesRes struct {
|
||||
cliRes *client.ResObjectHash
|
||||
}
|
||||
|
||||
// HashList returns a list of hashes of the payload ranges keeping order.
|
||||
func (x HashPayloadRangesRes) HashList() [][]byte {
|
||||
return x.cliRes.Checksums()
|
||||
}
|
||||
|
||||
// HashPayloadRanges requests hashes (by default SHA256) of the object payload ranges.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// Returns an error if number of received hashes differs with the number of requested ranges.
|
||||
func HashPayloadRanges(prm HashPayloadRangesPrm) (*HashPayloadRangesRes, error) {
|
||||
var cliPrm client.PrmObjectHash
|
||||
cliPrm.FromContainer(prm.objAddr.Container())
|
||||
cliPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
if prm.local {
|
||||
cliPrm.MarkLocal()
|
||||
}
|
||||
|
||||
cliPrm.UseSalt(prm.salt)
|
||||
|
||||
rngs := make([]uint64, 2*len(prm.rngs))
|
||||
|
||||
for i := range prm.rngs {
|
||||
rngs[2*i] = prm.rngs[i].GetOffset()
|
||||
rngs[2*i+1] = prm.rngs[i].GetLength()
|
||||
}
|
||||
|
||||
cliPrm.SetRangeList(rngs...)
|
||||
|
||||
if prm.tz {
|
||||
cliPrm.TillichZemorAlgo()
|
||||
}
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
cliPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
res, err := prm.cli.ObjectHash(context.Background(), cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read payload hashes via client: %w", err)
|
||||
}
|
||||
|
||||
return &HashPayloadRangesRes{
|
||||
cliRes: res,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 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 the resulting values of PayloadRange operation.
|
||||
type PayloadRangeRes struct{}
|
||||
|
||||
// PayloadRange reads object payload range from NeoFS and writes it to the specified writer.
|
||||
//
|
||||
// Interrupts on any writer error.
|
||||
//
|
||||
// Returns any error which prevented the operation from completing correctly in error return.
|
||||
// For raw reading, returns *object.SplitInfoError error if object is virtual.
|
||||
func PayloadRange(prm PayloadRangePrm) (*PayloadRangeRes, error) {
|
||||
var cliPrm client.PrmObjectRange
|
||||
cliPrm.FromContainer(prm.objAddr.Container())
|
||||
cliPrm.ByID(prm.objAddr.Object())
|
||||
|
||||
if prm.sessionToken != nil {
|
||||
cliPrm.WithinSession(*prm.sessionToken)
|
||||
}
|
||||
|
||||
if prm.bearerToken != nil {
|
||||
cliPrm.WithBearerToken(*prm.bearerToken)
|
||||
}
|
||||
|
||||
if prm.raw {
|
||||
cliPrm.MarkRaw()
|
||||
}
|
||||
|
||||
if prm.local {
|
||||
cliPrm.MarkLocal()
|
||||
}
|
||||
|
||||
cliPrm.SetOffset(prm.rng.GetOffset())
|
||||
cliPrm.SetLength(prm.rng.GetLength())
|
||||
|
||||
cliPrm.WithXHeaders(prm.xHeaders...)
|
||||
|
||||
rdr, err := prm.cli.ObjectRangeInit(context.Background(), cliPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("init payload reading: %w", err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(prm.wrt, rdr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("copy payload: %w", err)
|
||||
}
|
||||
|
||||
return new(PayloadRangeRes), nil
|
||||
}
|
||||
|
||||
// SyncContainerPrm groups parameters of SyncContainerSettings operation.
|
||||
type SyncContainerPrm struct {
|
||||
commonPrm
|
||||
c *containerSDK.Container
|
||||
}
|
||||
|
||||
// SetContainer sets a container that is required to be synced.
|
||||
func (s *SyncContainerPrm) SetContainer(c *containerSDK.Container) {
|
||||
s.c = c
|
||||
}
|
||||
|
||||
// SyncContainerRes groups resulting values of SyncContainerSettings
|
||||
// operation.
|
||||
type SyncContainerRes struct{}
|
||||
|
||||
// SyncContainerSettings reads global network config from NeoFS and
|
||||
// syncs container settings with it.
|
||||
//
|
||||
// Interrupts on any writer error.
|
||||
//
|
||||
// Panics if a container passed as a parameter is nil.
|
||||
func SyncContainerSettings(prm SyncContainerPrm) (*SyncContainerRes, error) {
|
||||
if prm.c == nil {
|
||||
panic("sync container settings with the network: nil container")
|
||||
}
|
||||
|
||||
err := client.SyncContainerWithNetwork(context.Background(), prm.c, prm.cli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return new(SyncContainerRes), nil
|
||||
}
|
12
cmd/frostfs-cli/internal/client/doc.go
Normal file
12
cmd/frostfs-cli/internal/client/doc.go
Normal file
|
@ -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
|
92
cmd/frostfs-cli/internal/client/prm.go
Normal file
92
cmd/frostfs-cli/internal/client/prm.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/client"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/session"
|
||||
)
|
||||
|
||||
// here are small structures with public setters to share between parameter structures
|
||||
|
||||
type commonPrm struct {
|
||||
cli *client.Client
|
||||
}
|
||||
|
||||
// SetClient sets the base client for NeoFS API communication.
|
||||
func (x *commonPrm) SetClient(cli *client.Client) {
|
||||
x.cli = cli
|
||||
}
|
||||
|
||||
type containerIDPrm struct {
|
||||
cnrID cid.ID
|
||||
}
|
||||
|
||||
// SetContainerID sets the container identifier.
|
||||
func (x *containerIDPrm) SetContainerID(id cid.ID) {
|
||||
x.cnrID = id
|
||||
}
|
||||
|
||||
type bearerTokenPrm struct {
|
||||
bearerToken *bearer.Token
|
||||
}
|
||||
|
||||
// SetBearerToken sets the bearer token to be attached to the request.
|
||||
func (x *bearerTokenPrm) SetBearerToken(tok *bearer.Token) {
|
||||
x.bearerToken = tok
|
||||
}
|
||||
|
||||
type objectAddressPrm struct {
|
||||
objAddr oid.Address
|
||||
}
|
||||
|
||||
func (x *objectAddressPrm) SetAddress(addr oid.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 the writer of the object payload.
|
||||
func (x *payloadWriterPrm) SetPayloadWriter(wrt io.Writer) {
|
||||
x.wrt = wrt
|
||||
}
|
||||
|
||||
type commonObjectPrm struct {
|
||||
commonPrm
|
||||
bearerTokenPrm
|
||||
|
||||
sessionToken *session.Object
|
||||
|
||||
local bool
|
||||
|
||||
xHeaders []string
|
||||
}
|
||||
|
||||
// SetTTL sets request TTL value.
|
||||
func (x *commonObjectPrm) SetTTL(ttl uint32) {
|
||||
x.local = ttl < 2
|
||||
}
|
||||
|
||||
// SetXHeaders sets request X-Headers.
|
||||
func (x *commonObjectPrm) SetXHeaders(hs []string) {
|
||||
x.xHeaders = hs
|
||||
}
|
||||
|
||||
// SetSessionToken sets the token of the session within which the request should be sent.
|
||||
func (x *commonObjectPrm) SetSessionToken(tok *session.Object) {
|
||||
x.sessionToken = tok
|
||||
}
|
95
cmd/frostfs-cli/internal/client/sdk.go
Normal file
95
cmd/frostfs-cli/internal/client/sdk.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/network"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/client"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var errInvalidEndpoint = errors.New("provided RPC endpoint is incorrect")
|
||||
|
||||
// GetSDKClientByFlag returns default frostfs-sdk-go client using the specified flag for the address.
|
||||
// On error, outputs to stderr of cmd and exits with non-zero code.
|
||||
func GetSDKClientByFlag(cmd *cobra.Command, key *ecdsa.PrivateKey, endpointFlag string) *client.Client {
|
||||
cli, err := getSDKClientByFlag(key, endpointFlag)
|
||||
if err != nil {
|
||||
common.ExitOnErr(cmd, "can't create API client: %w", err)
|
||||
}
|
||||
return cli
|
||||
}
|
||||
|
||||
func getSDKClientByFlag(key *ecdsa.PrivateKey, endpointFlag string) (*client.Client, error) {
|
||||
var addr network.Address
|
||||
|
||||
err := addr.FromString(viper.GetString(endpointFlag))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: %w", errInvalidEndpoint, err)
|
||||
}
|
||||
return GetSDKClient(key, addr)
|
||||
}
|
||||
|
||||
// GetSDKClient returns default frostfs-sdk-go client.
|
||||
func GetSDKClient(key *ecdsa.PrivateKey, addr network.Address) (*client.Client, error) {
|
||||
var (
|
||||
c client.Client
|
||||
prmInit client.PrmInit
|
||||
prmDial client.PrmDial
|
||||
)
|
||||
|
||||
prmInit.SetDefaultPrivateKey(*key)
|
||||
prmInit.ResolveNeoFSFailures()
|
||||
prmDial.SetServerURI(addr.URIAddr())
|
||||
if timeout := viper.GetDuration(commonflags.Timeout); timeout > 0 {
|
||||
// In CLI we can only set a timeout for the whole operation.
|
||||
// By also setting stream timeout we ensure that no operation hands
|
||||
// for too long.
|
||||
prmDial.SetTimeout(timeout)
|
||||
prmDial.SetStreamTimeout(timeout)
|
||||
|
||||
common.PrintVerbose("Set request timeout to %s.", timeout)
|
||||
}
|
||||
|
||||
c.Init(prmInit)
|
||||
|
||||
if err := c.Dial(prmDial); err != nil {
|
||||
return nil, fmt.Errorf("can't init SDK client: %w", err)
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// GetCurrentEpoch returns current epoch.
|
||||
func GetCurrentEpoch(ctx context.Context, endpoint string) (uint64, error) {
|
||||
var addr network.Address
|
||||
|
||||
if err := addr.FromString(endpoint); err != nil {
|
||||
return 0, fmt.Errorf("can't parse RPC endpoint: %w", err)
|
||||
}
|
||||
|
||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("can't generate key to sign query: %w", err)
|
||||
}
|
||||
|
||||
c, err := GetSDKClient(key, addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ni, err := c.NetworkInfo(ctx, client.PrmNetworkInfo{})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return ni.Info().CurrentEpoch(), nil
|
||||
}
|
49
cmd/frostfs-cli/internal/common/eacl.go
Normal file
49
cmd/frostfs-cli/internal/common/eacl.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/pkg/core/version"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
versionSDK "github.com/TrueCloudLab/frostfs-sdk-go/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var errUnsupportedEACLFormat = errors.New("unsupported eACL format")
|
||||
|
||||
// ReadEACL reads extended ACL table from eaclPath.
|
||||
func ReadEACL(cmd *cobra.Command, eaclPath string) *eacl.Table {
|
||||
_, err := os.Stat(eaclPath) // check if `eaclPath` is an existing file
|
||||
if err != nil {
|
||||
ExitOnErr(cmd, "", errors.New("incorrect path to file with EACL"))
|
||||
}
|
||||
|
||||
PrintVerbose("Reading EACL from file: %s", eaclPath)
|
||||
|
||||
data, err := os.ReadFile(eaclPath)
|
||||
ExitOnErr(cmd, "can't read file with EACL: %w", err)
|
||||
|
||||
table := eacl.NewTable()
|
||||
|
||||
if err = table.UnmarshalJSON(data); err == nil {
|
||||
validateAndFixEACLVersion(table)
|
||||
PrintVerbose("Parsed JSON encoded EACL table")
|
||||
return table
|
||||
}
|
||||
|
||||
if err = table.Unmarshal(data); err == nil {
|
||||
validateAndFixEACLVersion(table)
|
||||
PrintVerbose("Parsed binary encoded EACL table")
|
||||
return table
|
||||
}
|
||||
|
||||
ExitOnErr(cmd, "", errUnsupportedEACLFormat)
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAndFixEACLVersion(table *eacl.Table) {
|
||||
if !version.IsValid(table.Version()) {
|
||||
table.SetVersion(versionSDK.Current())
|
||||
}
|
||||
}
|
28
cmd/frostfs-cli/internal/common/epoch.go
Normal file
28
cmd/frostfs-cli/internal/common/epoch.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ParseEpoch parses epoch argument. Second return value is true if
|
||||
// the specified epoch is relative, and false otherwise.
|
||||
func ParseEpoch(cmd *cobra.Command, flag string) (uint64, bool, error) {
|
||||
s, _ := cmd.Flags().GetString(flag)
|
||||
if len(s) == 0 {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
relative := s[0] == '+'
|
||||
if relative {
|
||||
s = s[1:]
|
||||
}
|
||||
|
||||
epoch, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return 0, relative, fmt.Errorf("can't parse epoch for %s argument: %w", flag, err)
|
||||
}
|
||||
return epoch, relative, nil
|
||||
}
|
50
cmd/frostfs-cli/internal/common/exit.go
Normal file
50
cmd/frostfs-cli/internal/common/exit.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
sdkstatus "github.com/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ExitOnErr prints error and exits with a code that matches
|
||||
// one of the common errors from sdk library. If no errors
|
||||
// found, exits with 1 code.
|
||||
// Does nothing if passed error in nil.
|
||||
func ExitOnErr(cmd *cobra.Command, errFmt string, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if errFmt != "" {
|
||||
err = fmt.Errorf(errFmt, err)
|
||||
}
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
internal
|
||||
aclDenied
|
||||
)
|
||||
|
||||
var (
|
||||
code int
|
||||
|
||||
internalErr = new(sdkstatus.ServerInternal)
|
||||
accessErr = new(sdkstatus.ObjectAccessDenied)
|
||||
)
|
||||
|
||||
switch {
|
||||
case errors.As(err, &internalErr):
|
||||
code = internal
|
||||
case errors.As(err, &accessErr):
|
||||
code = aclDenied
|
||||
err = fmt.Errorf("%w: %s", err, accessErr.Reason())
|
||||
default:
|
||||
code = internal
|
||||
}
|
||||
|
||||
cmd.PrintErrln(err)
|
||||
os.Exit(code)
|
||||
}
|
23
cmd/frostfs-cli/internal/common/json.go
Normal file
23
cmd/frostfs-cli/internal/common/json.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// PrettyPrintJSON prints m as an indented JSON to the cmd output.
|
||||
func PrettyPrintJSON(cmd *cobra.Command, m json.Marshaler, entity string) {
|
||||
data, err := m.MarshalJSON()
|
||||
if err != nil {
|
||||
PrintVerbose("Can't convert %s to json: %w", entity, err)
|
||||
return
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := json.Indent(buf, data, "", " "); err != nil {
|
||||
PrintVerbose("Can't pretty print json: %w", err)
|
||||
return
|
||||
}
|
||||
cmd.Println(buf)
|
||||
}
|
49
cmd/frostfs-cli/internal/common/netmap.go
Normal file
49
cmd/frostfs-cli/internal/common/netmap.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// PrettyPrintNodeInfo print information about network node with given indent and index.
|
||||
// To avoid printing attribute list use short parameter.
|
||||
func PrettyPrintNodeInfo(cmd *cobra.Command, node netmap.NodeInfo,
|
||||
index int, indent string, short bool) {
|
||||
var strState string
|
||||
|
||||
switch {
|
||||
default:
|
||||
strState = "STATE_UNSUPPORTED"
|
||||
case node.IsOnline():
|
||||
strState = "ONLINE"
|
||||
case node.IsOffline():
|
||||
strState = "OFFLINE"
|
||||
case node.IsMaintenance():
|
||||
strState = "MAINTENANCE"
|
||||
}
|
||||
|
||||
cmd.Printf("%sNode %d: %s %s ", indent, index+1, hex.EncodeToString(node.PublicKey()), strState)
|
||||
|
||||
netmap.IterateNetworkEndpoints(node, func(endpoint string) {
|
||||
cmd.Printf("%s ", endpoint)
|
||||
})
|
||||
cmd.Println()
|
||||
|
||||
if !short {
|
||||
node.IterateAttributes(func(key, value string) {
|
||||
cmd.Printf("%s\t%s: %s\n", indent, key, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrintNetMap print information about network map.
|
||||
func PrettyPrintNetMap(cmd *cobra.Command, nm netmap.NetMap) {
|
||||
cmd.Println("Epoch:", nm.Epoch())
|
||||
|
||||
nodes := nm.Nodes()
|
||||
for i := range nodes {
|
||||
PrettyPrintNodeInfo(cmd, nodes[i], i, "", false)
|
||||
}
|
||||
}
|
66
cmd/frostfs-cli/internal/common/token.go
Normal file
66
cmd/frostfs-cli/internal/common/token.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ReadBearerToken reads bearer token from the path provided in a specified flag.
|
||||
func ReadBearerToken(cmd *cobra.Command, flagname string) *bearer.Token {
|
||||
path, err := cmd.Flags().GetString(flagname)
|
||||
ExitOnErr(cmd, "", err)
|
||||
|
||||
if len(path) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
PrintVerbose("Reading bearer token from file [%s]...", path)
|
||||
|
||||
var tok bearer.Token
|
||||
|
||||
err = ReadBinaryOrJSON(&tok, path)
|
||||
ExitOnErr(cmd, "invalid bearer token: %v", err)
|
||||
|
||||
return &tok
|
||||
}
|
||||
|
||||
// BinaryOrJSON is an interface of entities which provide json.Unmarshaler
|
||||
// and NeoFS binary decoder.
|
||||
type BinaryOrJSON interface {
|
||||
Unmarshal([]byte) error
|
||||
json.Unmarshaler
|
||||
}
|
||||
|
||||
// ReadBinaryOrJSON reads file data using provided path and decodes
|
||||
// BinaryOrJSON from the data.
|
||||
func ReadBinaryOrJSON(dst BinaryOrJSON, fPath string) error {
|
||||
PrintVerbose("Reading file [%s]...", fPath)
|
||||
|
||||
// try to read session token from file
|
||||
data, err := os.ReadFile(fPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read file <%s>: %w", fPath, err)
|
||||
}
|
||||
|
||||
PrintVerbose("Trying to decode binary...")
|
||||
|
||||
err = dst.Unmarshal(data)
|
||||
if err != nil {
|
||||
PrintVerbose("Failed to decode binary: %v", err)
|
||||
|
||||
PrintVerbose("Trying to decode JSON...")
|
||||
|
||||
err = dst.UnmarshalJSON(data)
|
||||
if err != nil {
|
||||
PrintVerbose("Failed to decode JSON: %v", err)
|
||||
return errors.New("invalid format")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
47
cmd/frostfs-cli/internal/common/verbose.go
Normal file
47
cmd/frostfs-cli/internal/common/verbose.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// PrintVerbose prints to the stdout if the commonflags.Verbose flag is on.
|
||||
func PrintVerbose(format string, a ...interface{}) {
|
||||
if viper.GetBool(commonflags.Verbose) {
|
||||
fmt.Printf(format+"\n", a...)
|
||||
}
|
||||
}
|
||||
|
||||
// PrettyPrintUnixTime interprets s as unix timestamp and prints it as
|
||||
// a date. Is s is invalid, "malformed" is returned.
|
||||
func PrettyPrintUnixTime(s string) string {
|
||||
unixTime, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return "malformed"
|
||||
}
|
||||
|
||||
timestamp := time.Unix(unixTime, 0)
|
||||
|
||||
return timestamp.String()
|
||||
}
|
||||
|
||||
// PrintChecksum prints checksum.
|
||||
func PrintChecksum(cmd *cobra.Command, name string, recv func() (checksum.Checksum, bool)) {
|
||||
var strVal string
|
||||
|
||||
cs, csSet := recv()
|
||||
if csSet {
|
||||
strVal = hex.EncodeToString(cs.Value())
|
||||
} else {
|
||||
strVal = "<empty>"
|
||||
}
|
||||
|
||||
cmd.Printf("%s: %s\n", name, strVal)
|
||||
}
|
33
cmd/frostfs-cli/internal/commonflags/api.go
Normal file
33
cmd/frostfs-cli/internal/commonflags/api.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package commonflags
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
TTL = "ttl"
|
||||
TTLShorthand = ""
|
||||
TTLDefault = 2
|
||||
TTLUsage = "TTL value in request meta header"
|
||||
|
||||
XHeadersKey = "xhdr"
|
||||
XHeadersShorthand = "x"
|
||||
XHeadersUsage = "Request X-Headers in form of Key=Value"
|
||||
)
|
||||
|
||||
// InitAPI inits common flags for storage node services.
|
||||
func InitAPI(cmd *cobra.Command) {
|
||||
ff := cmd.Flags()
|
||||
|
||||
ff.StringSliceP(XHeadersKey, XHeadersShorthand, []string{}, XHeadersUsage)
|
||||
ff.Uint32P(TTL, TTLShorthand, TTLDefault, TTLUsage)
|
||||
}
|
||||
|
||||
// BindAPI binds API flags of storage node services to the viper.
|
||||
func BindAPI(cmd *cobra.Command) {
|
||||
ff := cmd.Flags()
|
||||
|
||||
_ = viper.BindPFlag(TTL, ff.Lookup(TTL))
|
||||
_ = viper.BindPFlag(XHeadersKey, ff.Lookup(XHeadersKey))
|
||||
}
|
9
cmd/frostfs-cli/internal/commonflags/expiration.go
Normal file
9
cmd/frostfs-cli/internal/commonflags/expiration.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package commonflags
|
||||
|
||||
const (
|
||||
// ExpireAt is a flag for setting last epoch of an object or a token.
|
||||
ExpireAt = "expire-at"
|
||||
// Lifetime is a flag for setting the lifetime of an object or a token,
|
||||
// starting from the current epoch.
|
||||
Lifetime = "lifetime"
|
||||
)
|
84
cmd/frostfs-cli/internal/commonflags/flags.go
Normal file
84
cmd/frostfs-cli/internal/commonflags/flags.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package commonflags
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Common CLI flag keys, shorthands, default
|
||||
// values and their usage descriptions.
|
||||
const (
|
||||
GenerateKey = "generate-key"
|
||||
generateKeyShorthand = "g"
|
||||
generateKeyDefault = false
|
||||
generateKeyUsage = "Generate new private key"
|
||||
|
||||
WalletPath = "wallet"
|
||||
WalletPathShorthand = "w"
|
||||
WalletPathDefault = ""
|
||||
WalletPathUsage = "Path to the wallet or binary key"
|
||||
|
||||
Account = "address"
|
||||
AccountShorthand = ""
|
||||
AccountDefault = ""
|
||||
AccountUsage = "Address of wallet account"
|
||||
|
||||
RPC = "rpc-endpoint"
|
||||
RPCShorthand = "r"
|
||||
RPCDefault = ""
|
||||
RPCUsage = "Remote node address (as 'multiaddr' or '<host>:<port>')"
|
||||
|
||||
Timeout = "timeout"
|
||||
TimeoutShorthand = "t"
|
||||
TimeoutDefault = 15 * time.Second
|
||||
TimeoutUsage = "Timeout for an operation"
|
||||
|
||||
Verbose = "verbose"
|
||||
VerboseShorthand = "v"
|
||||
VerboseUsage = "Verbose output"
|
||||
|
||||
ForceFlag = "force"
|
||||
ForceFlagShorthand = "f"
|
||||
|
||||
CIDFlag = "cid"
|
||||
CIDFlagUsage = "Container ID."
|
||||
|
||||
OIDFlag = "oid"
|
||||
OIDFlagUsage = "Object ID."
|
||||
)
|
||||
|
||||
// Init adds common flags to the command:
|
||||
// - GenerateKey,
|
||||
// - WalletPath,
|
||||
// - Account,
|
||||
// - RPC,
|
||||
// - Timeout.
|
||||
func Init(cmd *cobra.Command) {
|
||||
InitWithoutRPC(cmd)
|
||||
|
||||
ff := cmd.Flags()
|
||||
ff.StringP(RPC, RPCShorthand, RPCDefault, RPCUsage)
|
||||
ff.DurationP(Timeout, TimeoutShorthand, TimeoutDefault, TimeoutUsage)
|
||||
}
|
||||
|
||||
// InitWithoutRPC is similar to Init but doesn't create the RPC flag.
|
||||
func InitWithoutRPC(cmd *cobra.Command) {
|
||||
ff := cmd.Flags()
|
||||
|
||||
ff.BoolP(GenerateKey, generateKeyShorthand, generateKeyDefault, generateKeyUsage)
|
||||
ff.StringP(WalletPath, WalletPathShorthand, WalletPathDefault, WalletPathUsage)
|
||||
ff.StringP(Account, AccountShorthand, AccountDefault, AccountUsage)
|
||||
}
|
||||
|
||||
// Bind binds common command flags to the viper.
|
||||
func Bind(cmd *cobra.Command) {
|
||||
ff := cmd.Flags()
|
||||
|
||||
_ = viper.BindPFlag(GenerateKey, ff.Lookup(GenerateKey))
|
||||
_ = viper.BindPFlag(WalletPath, ff.Lookup(WalletPath))
|
||||
_ = viper.BindPFlag(Account, ff.Lookup(Account))
|
||||
_ = viper.BindPFlag(RPC, ff.Lookup(RPC))
|
||||
_ = viper.BindPFlag(Timeout, ff.Lookup(Timeout))
|
||||
}
|
3
cmd/frostfs-cli/internal/commonflags/json.go
Normal file
3
cmd/frostfs-cli/internal/commonflags/json.go
Normal file
|
@ -0,0 +1,3 @@
|
|||
package commonflags
|
||||
|
||||
const JSON = "json"
|
19
cmd/frostfs-cli/internal/commonflags/session.go
Normal file
19
cmd/frostfs-cli/internal/commonflags/session.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package commonflags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const SessionToken = "session"
|
||||
|
||||
// InitSession registers SessionToken flag representing filepath to the token
|
||||
// of the session with the given name. Supports NeoFS-binary and JSON files.
|
||||
func InitSession(cmd *cobra.Command, name string) {
|
||||
cmd.Flags().String(
|
||||
SessionToken,
|
||||
"",
|
||||
fmt.Sprintf("Filepath to a JSON- or binary-encoded token of the %s session", name),
|
||||
)
|
||||
}
|
119
cmd/frostfs-cli/internal/key/key_test.go
Normal file
119
cmd/frostfs-cli/internal/key/key_test.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
package key
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func Test_getOrGenerate(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
wallPath := filepath.Join(dir, "wallet.json")
|
||||
w, err := wallet.NewWallet(wallPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
badWallPath := filepath.Join(dir, "bad_wallet.json")
|
||||
require.NoError(t, os.WriteFile(badWallPath, []byte("bad content"), os.ModePerm))
|
||||
|
||||
acc1, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, acc1.Encrypt("pass", keys.NEP2ScryptParams()))
|
||||
w.AddAccount(acc1)
|
||||
|
||||
acc2, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, acc2.Encrypt("pass", keys.NEP2ScryptParams()))
|
||||
acc2.Default = true
|
||||
w.AddAccount(acc2)
|
||||
require.NoError(t, w.Save())
|
||||
|
||||
keyPath := filepath.Join(dir, "binary.key")
|
||||
rawKey, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(keyPath, rawKey.Bytes(), os.ModePerm))
|
||||
|
||||
wifKey, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
nep2Key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
nep2, err := keys.NEP2Encrypt(nep2Key, "pass", keys.NEP2ScryptParams())
|
||||
require.NoError(t, err)
|
||||
|
||||
in := bytes.NewBuffer(nil)
|
||||
input.Terminal = term.NewTerminal(input.ReadWriter{
|
||||
Reader: in,
|
||||
Writer: io.Discard,
|
||||
}, "")
|
||||
|
||||
checkKeyError(t, filepath.Join(dir, "badfile"), ErrFs)
|
||||
checkKeyError(t, badWallPath, ErrInvalidKey)
|
||||
|
||||
t.Run("wallet", func(t *testing.T) {
|
||||
checkKeyError(t, wallPath, ErrInvalidPassword)
|
||||
|
||||
in.WriteString("invalid\r")
|
||||
checkKeyError(t, wallPath, ErrInvalidPassword)
|
||||
|
||||
in.WriteString("pass\r")
|
||||
checkKey(t, wallPath, acc2.PrivateKey()) // default account
|
||||
|
||||
viper.Set(commonflags.Account, acc1.Address)
|
||||
in.WriteString("pass\r")
|
||||
checkKey(t, wallPath, acc1.PrivateKey())
|
||||
|
||||
viper.Set(commonflags.Account, "not an address")
|
||||
checkKeyError(t, wallPath, ErrInvalidAddress)
|
||||
|
||||
acc, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
viper.Set(commonflags.Account, acc.Address)
|
||||
checkKeyError(t, wallPath, ErrInvalidAddress)
|
||||
})
|
||||
|
||||
t.Run("WIF", func(t *testing.T) {
|
||||
checkKeyError(t, wifKey.WIF(), ErrFs)
|
||||
})
|
||||
|
||||
t.Run("NEP-2", func(t *testing.T) {
|
||||
checkKeyError(t, nep2, ErrFs)
|
||||
})
|
||||
|
||||
t.Run("raw key", func(t *testing.T) {
|
||||
checkKey(t, keyPath, rawKey)
|
||||
})
|
||||
|
||||
t.Run("generate", func(t *testing.T) {
|
||||
viper.Set(commonflags.GenerateKey, true)
|
||||
actual, err := getOrGenerate()
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, actual)
|
||||
for _, p := range []*keys.PrivateKey{nep2Key, rawKey, wifKey, acc1.PrivateKey(), acc2.PrivateKey()} {
|
||||
require.NotEqual(t, p, actual, "expected new key to be generated")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func checkKeyError(t *testing.T, desc string, err error) {
|
||||
viper.Set(commonflags.WalletPath, desc)
|
||||
_, actualErr := getOrGenerate()
|
||||
require.ErrorIs(t, actualErr, err)
|
||||
}
|
||||
|
||||
func checkKey(t *testing.T, desc string, expected *keys.PrivateKey) {
|
||||
viper.Set(commonflags.WalletPath, desc)
|
||||
actual, err := getOrGenerate()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &expected.PrivateKey, actual)
|
||||
}
|
62
cmd/frostfs-cli/internal/key/raw.go
Normal file
62
cmd/frostfs-cli/internal/key/raw.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package key
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var errCantGenerateKey = errors.New("can't generate new private key")
|
||||
|
||||
// Get returns private key from wallet or binary file.
|
||||
// Ideally we want to touch file-system on the last step.
|
||||
// This function assumes that all flags were bind to viper in a `PersistentPreRun`.
|
||||
func Get(cmd *cobra.Command) *ecdsa.PrivateKey {
|
||||
pk, err := get()
|
||||
common.ExitOnErr(cmd, "can't fetch private key: %w", err)
|
||||
return pk
|
||||
}
|
||||
|
||||
func get() (*ecdsa.PrivateKey, error) {
|
||||
keyDesc := viper.GetString(commonflags.WalletPath)
|
||||
data, err := os.ReadFile(keyDesc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrFs, err)
|
||||
}
|
||||
|
||||
priv, err := keys.NewPrivateKeyFromBytes(data)
|
||||
if err != nil {
|
||||
w, err := wallet.NewWalletFromFile(keyDesc)
|
||||
if err == nil {
|
||||
return FromWallet(w, viper.GetString(commonflags.Account))
|
||||
}
|
||||
return nil, fmt.Errorf("%w: %v", ErrInvalidKey, err)
|
||||
}
|
||||
return &priv.PrivateKey, nil
|
||||
}
|
||||
|
||||
// GetOrGenerate is similar to get but generates a new key if commonflags.GenerateKey is set.
|
||||
func GetOrGenerate(cmd *cobra.Command) *ecdsa.PrivateKey {
|
||||
pk, err := getOrGenerate()
|
||||
common.ExitOnErr(cmd, "can't fetch private key: %w", err)
|
||||
return pk
|
||||
}
|
||||
|
||||
func getOrGenerate() (*ecdsa.PrivateKey, error) {
|
||||
if viper.GetBool(commonflags.GenerateKey) {
|
||||
priv, err := keys.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errCantGenerateKey, err)
|
||||
}
|
||||
return &priv.PrivateKey, nil
|
||||
}
|
||||
return get()
|
||||
}
|
75
cmd/frostfs-cli/internal/key/wallet.go
Normal file
75
cmd/frostfs-cli/internal/key/wallet.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package key
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Key-related errors.
|
||||
var (
|
||||
ErrFs = errors.New("unable to read file from given path")
|
||||
ErrInvalidKey = errors.New("provided key is incorrect, only wallet or binary key supported")
|
||||
ErrInvalidAddress = errors.New("--address option must be specified and valid")
|
||||
ErrInvalidPassword = errors.New("invalid password for the encrypted key")
|
||||
)
|
||||
|
||||
// FromWallet returns private key of the wallet account.
|
||||
func FromWallet(w *wallet.Wallet, addrStr string) (*ecdsa.PrivateKey, error) {
|
||||
var (
|
||||
addr util.Uint160
|
||||
err error
|
||||
)
|
||||
|
||||
if addrStr == "" {
|
||||
printVerbose("Using default wallet address")
|
||||
addr = w.GetChangeAddress()
|
||||
} else {
|
||||
addr, err = flags.ParseAddress(addrStr)
|
||||
if err != nil {
|
||||
printVerbose("Can't parse address: %s", addrStr)
|
||||
return nil, ErrInvalidAddress
|
||||
}
|
||||
}
|
||||
|
||||
acc := w.GetAccount(addr)
|
||||
if acc == nil {
|
||||
printVerbose("Can't find wallet account for %s", addrStr)
|
||||
return nil, ErrInvalidAddress
|
||||
}
|
||||
|
||||
pass, err := getPassword()
|
||||
if err != nil {
|
||||
printVerbose("Can't read password: %v", err)
|
||||
return nil, ErrInvalidPassword
|
||||
}
|
||||
|
||||
if err := acc.Decrypt(pass, keys.NEP2ScryptParams()); err != nil {
|
||||
printVerbose("Can't decrypt account: %v", err)
|
||||
return nil, ErrInvalidPassword
|
||||
}
|
||||
|
||||
return &acc.PrivateKey().PrivateKey, nil
|
||||
}
|
||||
|
||||
func getPassword() (string, error) {
|
||||
// this check allows empty passwords
|
||||
if viper.IsSet("password") {
|
||||
return viper.GetString("password"), nil
|
||||
}
|
||||
|
||||
return input.ReadPassword("Enter password > ")
|
||||
}
|
||||
|
||||
func printVerbose(format string, a ...interface{}) {
|
||||
if viper.GetBool("verbose") {
|
||||
fmt.Printf(format+"\n", a...)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue