forked from TrueCloudLab/frostfs-node
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
75
cmd/frostfs-cli/modules/object/delete.go
Normal file
75
cmd/frostfs-cli/modules/object/delete.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/cmd/frostfs-cli/internal/key"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var objectDelCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Aliases: []string{"del"},
|
||||
Short: "Delete object from NeoFS",
|
||||
Long: "Delete object from NeoFS",
|
||||
Run: deleteObject,
|
||||
}
|
||||
|
||||
func initObjectDeleteCmd() {
|
||||
commonflags.Init(objectDelCmd)
|
||||
initFlagSession(objectDelCmd, "DELETE")
|
||||
|
||||
flags := objectDelCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
flags.Bool(binaryFlag, false, "Deserialize object structure from given file.")
|
||||
flags.String(fileFlag, "", "File with object payload")
|
||||
}
|
||||
|
||||
func deleteObject(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
var objAddr oid.Address
|
||||
|
||||
binary, _ := cmd.Flags().GetBool(binaryFlag)
|
||||
if binary {
|
||||
filename, _ := cmd.Flags().GetString(fileFlag)
|
||||
if filename == "" {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", fileFlag))
|
||||
}
|
||||
objAddr = readObjectAddressBin(cmd, &cnr, &obj, filename)
|
||||
} else {
|
||||
cidVal, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
if cidVal == "" {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", commonflags.CIDFlag))
|
||||
}
|
||||
|
||||
oidVal, _ := cmd.Flags().GetString(commonflags.OIDFlag)
|
||||
if oidVal == "" {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", commonflags.OIDFlag))
|
||||
}
|
||||
|
||||
objAddr = readObjectAddress(cmd, &cnr, &obj)
|
||||
}
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
var prm internalclient.DeleteObjectPrm
|
||||
ReadOrOpenSession(cmd, &prm, pk, cnr, &obj)
|
||||
Prepare(cmd, &prm)
|
||||
prm.SetAddress(objAddr)
|
||||
|
||||
res, err := internalclient.DeleteObject(prm)
|
||||
common.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
tomb := res.Tombstone()
|
||||
|
||||
cmd.Println("Object removed successfully.")
|
||||
cmd.Printf(" ID: %s\n CID: %s\n", tomb, cnr)
|
||||
}
|
141
cmd/frostfs-cli/modules/object/get.go
Normal file
141
cmd/frostfs-cli/modules/object/get.go
Normal file
|
@ -0,0 +1,141 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/cmd/frostfs-cli/internal/key"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/cheggaaa/pb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var objectGetCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get object from NeoFS",
|
||||
Long: "Get object from NeoFS",
|
||||
Run: getObject,
|
||||
}
|
||||
|
||||
func initObjectGetCmd() {
|
||||
commonflags.Init(objectGetCmd)
|
||||
initFlagSession(objectGetCmd, "GET")
|
||||
|
||||
flags := objectGetCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectGetCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectGetCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.String(fileFlag, "", "File to write object payload to(with -b together with signature and header). Default: stdout.")
|
||||
flags.Bool(rawFlag, false, rawFlagDesc)
|
||||
flags.Bool(noProgressFlag, false, "Do not show progress bar")
|
||||
flags.Bool(binaryFlag, false, "Serialize whole object structure into given file(id + signature + header + payload).")
|
||||
}
|
||||
|
||||
func getObject(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
var out io.Writer
|
||||
filename := cmd.Flag(fileFlag).Value.String()
|
||||
if filename == "" {
|
||||
out = os.Stdout
|
||||
} else {
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||
if err != nil {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err))
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
out = f
|
||||
}
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
var prm internalclient.GetObjectPrm
|
||||
prm.SetClient(cli)
|
||||
Prepare(cmd, &prm)
|
||||
readSession(cmd, &prm, pk, cnr, obj)
|
||||
|
||||
raw, _ := cmd.Flags().GetBool(rawFlag)
|
||||
prm.SetRawFlag(raw)
|
||||
prm.SetAddress(objAddr)
|
||||
|
||||
var p *pb.ProgressBar
|
||||
noProgress, _ := cmd.Flags().GetBool(noProgressFlag)
|
||||
|
||||
var payloadWriter io.Writer
|
||||
var payloadBuffer *bytes.Buffer
|
||||
binary, _ := cmd.Flags().GetBool(binaryFlag)
|
||||
if binary {
|
||||
payloadBuffer = new(bytes.Buffer)
|
||||
payloadWriter = payloadBuffer
|
||||
} else {
|
||||
payloadWriter = out
|
||||
}
|
||||
|
||||
if filename == "" || noProgress {
|
||||
prm.SetPayloadWriter(payloadWriter)
|
||||
} else {
|
||||
p = pb.New64(0)
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadWriter(p.NewProxyWriter(payloadWriter))
|
||||
prm.SetHeaderCallback(func(o *object.Object) {
|
||||
p.SetTotal64(int64(o.PayloadSize()))
|
||||
p.Start()
|
||||
})
|
||||
}
|
||||
|
||||
res, err := internalclient.GetObject(prm)
|
||||
if p != nil {
|
||||
p.Finish()
|
||||
}
|
||||
if err != nil {
|
||||
if ok := printSplitInfoErr(cmd, err); ok {
|
||||
return
|
||||
}
|
||||
|
||||
common.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
}
|
||||
|
||||
if binary {
|
||||
objToStore := res.Header()
|
||||
//TODO(@acid-ant): #1932 Use streams to marshal/unmarshal payload
|
||||
objToStore.SetPayload(payloadBuffer.Bytes())
|
||||
objBytes, err := objToStore.Marshal()
|
||||
common.ExitOnErr(cmd, "", err)
|
||||
_, err = out.Write(objBytes)
|
||||
common.ExitOnErr(cmd, "unable to write binary object in out: %w ", err)
|
||||
}
|
||||
|
||||
if filename != "" && !strictOutput(cmd) {
|
||||
cmd.Printf("[%s] Object successfully saved\n", filename)
|
||||
}
|
||||
|
||||
// Print header only if file is not streamed to stdout.
|
||||
if filename != "" {
|
||||
err = printHeader(cmd, res.Header())
|
||||
common.ExitOnErr(cmd, "", err)
|
||||
}
|
||||
}
|
||||
|
||||
func strictOutput(cmd *cobra.Command) bool {
|
||||
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
||||
toProto, _ := cmd.Flags().GetBool("proto")
|
||||
return toJSON || toProto
|
||||
}
|
130
cmd/frostfs-cli/modules/object/hash.go
Normal file
130
cmd/frostfs-cli/modules/object/hash.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/cmd/frostfs-cli/internal/key"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/checksum"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const getRangeHashSaltFlag = "salt"
|
||||
|
||||
const (
|
||||
hashSha256 = "sha256"
|
||||
hashTz = "tz"
|
||||
rangeSep = ":"
|
||||
)
|
||||
|
||||
var objectHashCmd = &cobra.Command{
|
||||
Use: "hash",
|
||||
Short: "Get object hash",
|
||||
Long: "Get object hash",
|
||||
Run: getObjectHash,
|
||||
}
|
||||
|
||||
func initObjectHashCmd() {
|
||||
commonflags.Init(objectHashCmd)
|
||||
initFlagSession(objectHashCmd, "RANGEHASH")
|
||||
|
||||
flags := objectHashCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectHashCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectHashCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.String("range", "", "Range to take hash from in the form offset1:length1,...")
|
||||
flags.String("type", hashSha256, "Hash type. Either 'sha256' or 'tz'")
|
||||
flags.String(getRangeHashSaltFlag, "", "Salt in hex format")
|
||||
}
|
||||
|
||||
func getObjectHash(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
ranges, err := getRangeList(cmd)
|
||||
common.ExitOnErr(cmd, "", err)
|
||||
typ, err := getHashType(cmd)
|
||||
common.ExitOnErr(cmd, "", err)
|
||||
|
||||
strSalt := strings.TrimPrefix(cmd.Flag(getRangeHashSaltFlag).Value.String(), "0x")
|
||||
|
||||
salt, err := hex.DecodeString(strSalt)
|
||||
common.ExitOnErr(cmd, "could not decode salt: %w", err)
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
tz := typ == hashTz
|
||||
fullHash := len(ranges) == 0
|
||||
if fullHash {
|
||||
var headPrm internalclient.HeadObjectPrm
|
||||
headPrm.SetClient(cli)
|
||||
Prepare(cmd, &headPrm)
|
||||
headPrm.SetAddress(objAddr)
|
||||
|
||||
// get hash of full payload through HEAD (may be user can do it through dedicated command?)
|
||||
res, err := internalclient.HeadObject(headPrm)
|
||||
common.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
var cs checksum.Checksum
|
||||
var csSet bool
|
||||
|
||||
if tz {
|
||||
cs, csSet = res.Header().PayloadHomomorphicHash()
|
||||
} else {
|
||||
cs, csSet = res.Header().PayloadChecksum()
|
||||
}
|
||||
|
||||
if csSet {
|
||||
cmd.Println(hex.EncodeToString(cs.Value()))
|
||||
} else {
|
||||
cmd.Println("Missing checksum in object header.")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var hashPrm internalclient.HashPayloadRangesPrm
|
||||
hashPrm.SetClient(cli)
|
||||
Prepare(cmd, &hashPrm)
|
||||
readSession(cmd, &hashPrm, pk, cnr, obj)
|
||||
hashPrm.SetAddress(objAddr)
|
||||
hashPrm.SetSalt(salt)
|
||||
hashPrm.SetRanges(ranges)
|
||||
|
||||
if tz {
|
||||
hashPrm.TZ()
|
||||
}
|
||||
|
||||
res, err := internalclient.HashPayloadRanges(hashPrm)
|
||||
common.ExitOnErr(cmd, "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]))
|
||||
}
|
||||
}
|
||||
|
||||
func getHashType(cmd *cobra.Command) (string, error) {
|
||||
rawType := cmd.Flag("type").Value.String()
|
||||
switch typ := strings.ToLower(rawType); typ {
|
||||
case hashSha256, hashTz:
|
||||
return typ, nil
|
||||
default:
|
||||
return "", fmt.Errorf("invalid hash type: %s", typ)
|
||||
}
|
||||
}
|
202
cmd/frostfs-cli/modules/object/head.go
Normal file
202
cmd/frostfs-cli/modules/object/head.go
Normal file
|
@ -0,0 +1,202 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/cmd/frostfs-cli/internal/key"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
oidSDK "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var objectHeadCmd = &cobra.Command{
|
||||
Use: "head",
|
||||
Short: "Get object header",
|
||||
Long: "Get object header",
|
||||
Run: getObjectHeader,
|
||||
}
|
||||
|
||||
func initObjectHeadCmd() {
|
||||
commonflags.Init(objectHeadCmd)
|
||||
initFlagSession(objectHeadCmd, "HEAD")
|
||||
|
||||
flags := objectHeadCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectHeadCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectHeadCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.String(fileFlag, "", "File to write header to. Default: stdout.")
|
||||
flags.Bool("main-only", false, "Return only main fields")
|
||||
flags.Bool(commonflags.JSON, false, "Marshal output in JSON")
|
||||
flags.Bool("proto", false, "Marshal output in Protobuf")
|
||||
flags.Bool(rawFlag, false, rawFlagDesc)
|
||||
}
|
||||
|
||||
func getObjectHeader(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
mainOnly, _ := cmd.Flags().GetBool("main-only")
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
var prm internalclient.HeadObjectPrm
|
||||
prm.SetClient(cli)
|
||||
Prepare(cmd, &prm)
|
||||
readSession(cmd, &prm, pk, cnr, obj)
|
||||
|
||||
raw, _ := cmd.Flags().GetBool(rawFlag)
|
||||
prm.SetRawFlag(raw)
|
||||
prm.SetAddress(objAddr)
|
||||
prm.SetMainOnlyFlag(mainOnly)
|
||||
|
||||
res, err := internalclient.HeadObject(prm)
|
||||
if err != nil {
|
||||
if ok := printSplitInfoErr(cmd, err); ok {
|
||||
return
|
||||
}
|
||||
|
||||
common.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
}
|
||||
|
||||
err = saveAndPrintHeader(cmd, res.Header(), cmd.Flag(fileFlag).Value.String())
|
||||
common.ExitOnErr(cmd, "", err)
|
||||
}
|
||||
|
||||
func saveAndPrintHeader(cmd *cobra.Command, obj *object.Object, filename string) error {
|
||||
bs, err := marshalHeader(cmd, obj)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not marshal header: %w", err)
|
||||
}
|
||||
if len(bs) != 0 {
|
||||
if filename == "" {
|
||||
cmd.Println(string(bs))
|
||||
return nil
|
||||
}
|
||||
err = os.WriteFile(filename, bs, os.ModePerm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not write header to file: %w", err)
|
||||
}
|
||||
cmd.Printf("[%s] Header successfully saved.", filename)
|
||||
}
|
||||
|
||||
return printHeader(cmd, obj)
|
||||
}
|
||||
|
||||
func marshalHeader(cmd *cobra.Command, hdr *object.Object) ([]byte, error) {
|
||||
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
||||
toProto, _ := cmd.Flags().GetBool("proto")
|
||||
switch {
|
||||
case toJSON && toProto:
|
||||
return nil, errors.New("'--json' and '--proto' flags are mutually exclusive")
|
||||
case toJSON:
|
||||
return hdr.MarshalJSON()
|
||||
case toProto:
|
||||
return hdr.Marshal()
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func printObjectID(cmd *cobra.Command, recv func() (oidSDK.ID, bool)) {
|
||||
var strID string
|
||||
|
||||
id, ok := recv()
|
||||
if ok {
|
||||
strID = id.String()
|
||||
} else {
|
||||
strID = "<empty>"
|
||||
}
|
||||
|
||||
cmd.Printf("ID: %s\n", strID)
|
||||
}
|
||||
|
||||
func printContainerID(cmd *cobra.Command, recv func() (cid.ID, bool)) {
|
||||
var strID string
|
||||
|
||||
id, ok := recv()
|
||||
if ok {
|
||||
strID = id.String()
|
||||
} else {
|
||||
strID = "<empty>"
|
||||
}
|
||||
|
||||
cmd.Printf("CID: %s\n", strID)
|
||||
}
|
||||
|
||||
func printHeader(cmd *cobra.Command, obj *object.Object) error {
|
||||
printObjectID(cmd, obj.ID)
|
||||
printContainerID(cmd, obj.ContainerID)
|
||||
cmd.Printf("Owner: %s\n", obj.OwnerID())
|
||||
cmd.Printf("CreatedAt: %d\n", obj.CreationEpoch())
|
||||
cmd.Printf("Size: %d\n", obj.PayloadSize())
|
||||
common.PrintChecksum(cmd, "HomoHash", obj.PayloadHomomorphicHash)
|
||||
common.PrintChecksum(cmd, "Checksum", obj.PayloadChecksum)
|
||||
cmd.Printf("Type: %s\n", obj.Type())
|
||||
|
||||
cmd.Println("Attributes:")
|
||||
for _, attr := range obj.Attributes() {
|
||||
if attr.Key() == object.AttributeTimestamp {
|
||||
cmd.Printf(" %s=%s (%s)\n",
|
||||
attr.Key(),
|
||||
attr.Value(),
|
||||
common.PrettyPrintUnixTime(attr.Value()))
|
||||
continue
|
||||
}
|
||||
cmd.Printf(" %s=%s\n", attr.Key(), attr.Value())
|
||||
}
|
||||
|
||||
if signature := obj.Signature(); signature != nil {
|
||||
cmd.Print("ID signature:\n")
|
||||
|
||||
// TODO(@carpawell): #1387 implement and use another approach to avoid conversion
|
||||
var sigV2 refs.Signature
|
||||
signature.WriteToV2(&sigV2)
|
||||
|
||||
cmd.Printf(" public key: %s\n", hex.EncodeToString(sigV2.GetKey()))
|
||||
cmd.Printf(" signature: %s\n", hex.EncodeToString(sigV2.GetSign()))
|
||||
}
|
||||
|
||||
return printSplitHeader(cmd, obj)
|
||||
}
|
||||
|
||||
func printSplitHeader(cmd *cobra.Command, obj *object.Object) error {
|
||||
if splitID := obj.SplitID(); splitID != nil {
|
||||
cmd.Printf("Split ID: %s\n", splitID)
|
||||
}
|
||||
|
||||
if oid, ok := obj.ParentID(); ok {
|
||||
cmd.Printf("Split ParentID: %s\n", oid)
|
||||
}
|
||||
|
||||
if prev, ok := obj.PreviousID(); ok {
|
||||
cmd.Printf("Split PreviousID: %s\n", prev)
|
||||
}
|
||||
|
||||
for _, child := range obj.Children() {
|
||||
cmd.Printf("Split ChildID: %s\n", child.String())
|
||||
}
|
||||
|
||||
parent := obj.Parent()
|
||||
if parent != nil {
|
||||
cmd.Print("\nSplit Parent Header:\n")
|
||||
|
||||
return printHeader(cmd, parent)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
111
cmd/frostfs-cli/modules/object/lock.go
Normal file
111
cmd/frostfs-cli/modules/object/lock.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
objectV2 "github.com/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/cmd/frostfs-cli/internal/key"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
objectSDK "github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// object lock command.
|
||||
var objectLockCmd = &cobra.Command{
|
||||
Use: "lock",
|
||||
Short: "Lock object in container",
|
||||
Long: "Lock object in container",
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
cidRaw, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
|
||||
var cnr cid.ID
|
||||
err := cnr.DecodeString(cidRaw)
|
||||
common.ExitOnErr(cmd, "Incorrect container arg: %v", err)
|
||||
|
||||
oidsRaw, _ := cmd.Flags().GetStringSlice(commonflags.OIDFlag)
|
||||
|
||||
lockList := make([]oid.ID, len(oidsRaw))
|
||||
|
||||
for i := range oidsRaw {
|
||||
err = lockList[i].DecodeString(oidsRaw[i])
|
||||
common.ExitOnErr(cmd, fmt.Sprintf("Incorrect object arg #%d: %%v", i+1), err)
|
||||
}
|
||||
|
||||
key := key.GetOrGenerate(cmd)
|
||||
|
||||
var idOwner user.ID
|
||||
user.IDFromKey(&idOwner, key.PublicKey)
|
||||
|
||||
var lock objectSDK.Lock
|
||||
lock.WriteMembers(lockList)
|
||||
|
||||
exp, _ := cmd.Flags().GetUint64(commonflags.ExpireAt)
|
||||
lifetime, _ := cmd.Flags().GetUint64(commonflags.Lifetime)
|
||||
if exp == 0 && lifetime == 0 { // mutual exclusion is ensured by cobra
|
||||
common.ExitOnErr(cmd, "", errors.New("either expiration epoch of a lifetime is required"))
|
||||
}
|
||||
|
||||
if lifetime != 0 {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
endpoint, _ := cmd.Flags().GetString(commonflags.RPC)
|
||||
|
||||
currEpoch, err := internalclient.GetCurrentEpoch(ctx, endpoint)
|
||||
common.ExitOnErr(cmd, "Request current epoch: %w", err)
|
||||
|
||||
exp = currEpoch + lifetime
|
||||
}
|
||||
|
||||
common.PrintVerbose("Lock object will expire at %d epoch", exp)
|
||||
|
||||
var expirationAttr objectSDK.Attribute
|
||||
expirationAttr.SetKey(objectV2.SysAttributeExpEpoch)
|
||||
expirationAttr.SetValue(strconv.FormatUint(exp, 10))
|
||||
|
||||
obj := objectSDK.New()
|
||||
obj.SetContainerID(cnr)
|
||||
obj.SetOwnerID(&idOwner)
|
||||
obj.SetType(objectSDK.TypeLock)
|
||||
obj.SetAttributes(expirationAttr)
|
||||
obj.SetPayload(lock.Marshal())
|
||||
|
||||
var prm internalclient.PutObjectPrm
|
||||
ReadOrOpenSession(cmd, &prm, key, cnr, nil)
|
||||
Prepare(cmd, &prm)
|
||||
prm.SetHeader(obj)
|
||||
|
||||
res, err := internalclient.PutObject(prm)
|
||||
common.ExitOnErr(cmd, "Store lock object in NeoFS: %w", err)
|
||||
|
||||
cmd.Printf("Lock object ID: %s\n", res.ID())
|
||||
cmd.Println("Objects successfully locked.")
|
||||
},
|
||||
}
|
||||
|
||||
func initCommandObjectLock() {
|
||||
commonflags.Init(objectLockCmd)
|
||||
initFlagSession(objectLockCmd, "PUT")
|
||||
|
||||
ff := objectLockCmd.Flags()
|
||||
|
||||
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectLockCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
ff.StringSlice(commonflags.OIDFlag, nil, commonflags.OIDFlagUsage)
|
||||
_ = objectLockCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
ff.Uint64P(commonflags.ExpireAt, "e", 0, "Lock expiration epoch")
|
||||
|
||||
ff.Uint64(commonflags.Lifetime, 0, "Lock lifetime")
|
||||
objectLockCmd.MarkFlagsMutuallyExclusive(commonflags.ExpireAt, commonflags.Lifetime)
|
||||
}
|
244
cmd/frostfs-cli/modules/object/put.go
Normal file
244
cmd/frostfs-cli/modules/object/put.go
Normal file
|
@ -0,0 +1,244 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
objectV2 "github.com/TrueCloudLab/frostfs-api-go/v2/object"
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/cmd/frostfs-cli/internal/key"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/cheggaaa/pb"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
noProgressFlag = "no-progress"
|
||||
notificationFlag = "notify"
|
||||
)
|
||||
|
||||
var putExpiredOn uint64
|
||||
|
||||
var objectPutCmd = &cobra.Command{
|
||||
Use: "put",
|
||||
Short: "Put object to NeoFS",
|
||||
Long: "Put object to NeoFS",
|
||||
Run: putObject,
|
||||
}
|
||||
|
||||
func initObjectPutCmd() {
|
||||
commonflags.Init(objectPutCmd)
|
||||
initFlagSession(objectPutCmd, "PUT")
|
||||
|
||||
flags := objectPutCmd.Flags()
|
||||
|
||||
flags.String(fileFlag, "", "File with object payload")
|
||||
_ = objectPutCmd.MarkFlagFilename(fileFlag)
|
||||
_ = objectPutCmd.MarkFlagRequired(fileFlag)
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
|
||||
flags.String("attributes", "", "User attributes in form of Key1=Value1,Key2=Value2")
|
||||
flags.Bool("disable-filename", false, "Do not set well-known filename attribute")
|
||||
flags.Bool("disable-timestamp", false, "Do not set well-known timestamp attribute")
|
||||
flags.Uint64VarP(&putExpiredOn, commonflags.ExpireAt, "e", 0, "Last epoch in the life of the object")
|
||||
flags.Bool(noProgressFlag, false, "Do not show progress bar")
|
||||
|
||||
flags.String(notificationFlag, "", "Object notification in the form of *epoch*:*topic*; '-' topic means using default")
|
||||
flags.Bool(binaryFlag, false, "Deserialize object structure from given file.")
|
||||
}
|
||||
|
||||
func putObject(cmd *cobra.Command, _ []string) {
|
||||
binary, _ := cmd.Flags().GetBool(binaryFlag)
|
||||
cidVal, _ := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||
|
||||
if !binary && cidVal == "" {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("required flag \"%s\" not set", commonflags.CIDFlag))
|
||||
}
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
var ownerID user.ID
|
||||
var cnr cid.ID
|
||||
|
||||
filename, _ := cmd.Flags().GetString(fileFlag)
|
||||
f, err := os.OpenFile(filename, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err))
|
||||
}
|
||||
var payloadReader io.Reader = f
|
||||
obj := object.New()
|
||||
|
||||
if binary {
|
||||
buf, err := os.ReadFile(filename)
|
||||
common.ExitOnErr(cmd, "unable to read given file: %w", err)
|
||||
objTemp := object.New()
|
||||
//TODO(@acid-ant): #1932 Use streams to marshal/unmarshal payload
|
||||
common.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf))
|
||||
payloadReader = bytes.NewReader(objTemp.Payload())
|
||||
cnr, _ = objTemp.ContainerID()
|
||||
ownerID = *objTemp.OwnerID()
|
||||
} else {
|
||||
readCID(cmd, &cnr)
|
||||
user.IDFromKey(&ownerID, pk.PublicKey)
|
||||
}
|
||||
|
||||
attrs, err := parseObjectAttrs(cmd)
|
||||
common.ExitOnErr(cmd, "can't parse object attributes: %w", err)
|
||||
|
||||
expiresOn, _ := cmd.Flags().GetUint64(commonflags.ExpireAt)
|
||||
if expiresOn > 0 {
|
||||
var expAttrFound bool
|
||||
expAttrValue := strconv.FormatUint(expiresOn, 10)
|
||||
|
||||
for i := range attrs {
|
||||
if attrs[i].Key() == objectV2.SysAttributeExpEpoch {
|
||||
attrs[i].SetValue(expAttrValue)
|
||||
expAttrFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !expAttrFound {
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, object.Attribute{})
|
||||
attrs[index].SetKey(objectV2.SysAttributeExpEpoch)
|
||||
attrs[index].SetValue(expAttrValue)
|
||||
}
|
||||
}
|
||||
|
||||
obj.SetContainerID(cnr)
|
||||
obj.SetOwnerID(&ownerID)
|
||||
obj.SetAttributes(attrs...)
|
||||
|
||||
notificationInfo, err := parseObjectNotifications(cmd)
|
||||
common.ExitOnErr(cmd, "can't parse object notification information: %w", err)
|
||||
|
||||
if notificationInfo != nil {
|
||||
obj.SetNotification(*notificationInfo)
|
||||
}
|
||||
|
||||
var prm internalclient.PutObjectPrm
|
||||
ReadOrOpenSession(cmd, &prm, pk, cnr, nil)
|
||||
Prepare(cmd, &prm)
|
||||
prm.SetHeader(obj)
|
||||
|
||||
var p *pb.ProgressBar
|
||||
|
||||
noProgress, _ := cmd.Flags().GetBool(noProgressFlag)
|
||||
if noProgress {
|
||||
prm.SetPayloadReader(payloadReader)
|
||||
} else {
|
||||
if binary {
|
||||
p = pb.New(len(obj.Payload()))
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadReader(p.NewProxyReader(payloadReader))
|
||||
prm.SetHeaderCallback(func(o *object.Object) { p.Start() })
|
||||
} else {
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
cmd.PrintErrf("Failed to get file size, progress bar is disabled: %v\n", err)
|
||||
prm.SetPayloadReader(f)
|
||||
} else {
|
||||
p = pb.New64(fi.Size())
|
||||
p.Output = cmd.OutOrStdout()
|
||||
prm.SetPayloadReader(p.NewProxyReader(f))
|
||||
prm.SetHeaderCallback(func(o *object.Object) {
|
||||
p.Start()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res, err := internalclient.PutObject(prm)
|
||||
if p != nil {
|
||||
p.Finish()
|
||||
}
|
||||
common.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
cmd.Printf("[%s] Object successfully stored\n", filename)
|
||||
cmd.Printf(" OID: %s\n CID: %s\n", res.ID(), cnr)
|
||||
}
|
||||
|
||||
func parseObjectAttrs(cmd *cobra.Command) ([]object.Attribute, error) {
|
||||
var rawAttrs []string
|
||||
|
||||
raw := cmd.Flag("attributes").Value.String()
|
||||
if len(raw) != 0 {
|
||||
rawAttrs = strings.Split(raw, ",")
|
||||
}
|
||||
|
||||
attrs := make([]object.Attribute, len(rawAttrs), len(rawAttrs)+2) // name + timestamp attributes
|
||||
for i := range rawAttrs {
|
||||
kv := strings.SplitN(rawAttrs[i], "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("invalid attribute format: %s", rawAttrs[i])
|
||||
}
|
||||
attrs[i].SetKey(kv[0])
|
||||
attrs[i].SetValue(kv[1])
|
||||
}
|
||||
|
||||
disableFilename, _ := cmd.Flags().GetBool("disable-filename")
|
||||
if !disableFilename {
|
||||
filename := filepath.Base(cmd.Flag(fileFlag).Value.String())
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, object.Attribute{})
|
||||
attrs[index].SetKey(object.AttributeFileName)
|
||||
attrs[index].SetValue(filename)
|
||||
}
|
||||
|
||||
disableTime, _ := cmd.Flags().GetBool("disable-timestamp")
|
||||
if !disableTime {
|
||||
index := len(attrs)
|
||||
attrs = append(attrs, object.Attribute{})
|
||||
attrs[index].SetKey(object.AttributeTimestamp)
|
||||
attrs[index].SetValue(strconv.FormatInt(time.Now().Unix(), 10))
|
||||
}
|
||||
|
||||
return attrs, nil
|
||||
}
|
||||
|
||||
func parseObjectNotifications(cmd *cobra.Command) (*object.NotificationInfo, error) {
|
||||
const (
|
||||
separator = ":"
|
||||
useDefaultTopic = "-"
|
||||
)
|
||||
|
||||
raw := cmd.Flag(notificationFlag).Value.String()
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawSlice := strings.SplitN(raw, separator, 2)
|
||||
if len(rawSlice) != 2 {
|
||||
return nil, fmt.Errorf("notification must be in the form of: *epoch*%s*topic*, got %s", separator, raw)
|
||||
}
|
||||
|
||||
ni := new(object.NotificationInfo)
|
||||
|
||||
epoch, err := strconv.ParseUint(rawSlice[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse notification epoch %s: %w", rawSlice[0], err)
|
||||
}
|
||||
|
||||
ni.SetEpoch(epoch)
|
||||
|
||||
if rawSlice[1] == "" {
|
||||
return nil, fmt.Errorf("incorrect empty topic: use %s to force using default topic", useDefaultTopic)
|
||||
}
|
||||
|
||||
if rawSlice[1] != useDefaultTopic {
|
||||
ni.SetTopic(rawSlice[1])
|
||||
}
|
||||
|
||||
return ni, nil
|
||||
}
|
175
cmd/frostfs-cli/modules/object/range.go
Normal file
175
cmd/frostfs-cli/modules/object/range.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/cmd/frostfs-cli/internal/key"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var objectRangeCmd = &cobra.Command{
|
||||
Use: "range",
|
||||
Short: "Get payload range data of an object",
|
||||
Long: "Get payload range data of an object",
|
||||
Run: getObjectRange,
|
||||
}
|
||||
|
||||
func initObjectRangeCmd() {
|
||||
commonflags.Init(objectRangeCmd)
|
||||
initFlagSession(objectRangeCmd, "RANGE")
|
||||
|
||||
flags := objectRangeCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectRangeCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.String(commonflags.OIDFlag, "", commonflags.OIDFlagUsage)
|
||||
_ = objectRangeCmd.MarkFlagRequired(commonflags.OIDFlag)
|
||||
|
||||
flags.String("range", "", "Range to take data from in the form offset:length")
|
||||
flags.String(fileFlag, "", "File to write object payload to. Default: stdout.")
|
||||
flags.Bool(rawFlag, false, rawFlagDesc)
|
||||
}
|
||||
|
||||
func getObjectRange(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
var obj oid.ID
|
||||
|
||||
objAddr := readObjectAddress(cmd, &cnr, &obj)
|
||||
|
||||
ranges, err := getRangeList(cmd)
|
||||
common.ExitOnErr(cmd, "", err)
|
||||
|
||||
if len(ranges) != 1 {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("exactly one range must be specified, got: %d", len(ranges)))
|
||||
}
|
||||
|
||||
var out io.Writer
|
||||
|
||||
filename := cmd.Flag(fileFlag).Value.String()
|
||||
if filename == "" {
|
||||
out = os.Stdout
|
||||
} else {
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("can't open file '%s': %w", filename, err))
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
out = f
|
||||
}
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
var prm internalclient.PayloadRangePrm
|
||||
prm.SetClient(cli)
|
||||
Prepare(cmd, &prm)
|
||||
readSession(cmd, &prm, pk, cnr, obj)
|
||||
|
||||
raw, _ := cmd.Flags().GetBool(rawFlag)
|
||||
prm.SetRawFlag(raw)
|
||||
prm.SetAddress(objAddr)
|
||||
prm.SetRange(ranges[0])
|
||||
prm.SetPayloadWriter(out)
|
||||
|
||||
_, err = internalclient.PayloadRange(prm)
|
||||
if err != nil {
|
||||
if ok := printSplitInfoErr(cmd, err); ok {
|
||||
return
|
||||
}
|
||||
|
||||
common.ExitOnErr(cmd, "can't get object payload range: %w", err)
|
||||
}
|
||||
|
||||
if filename != "" {
|
||||
cmd.Printf("[%s] Payload successfully saved\n", filename)
|
||||
}
|
||||
}
|
||||
|
||||
func printSplitInfoErr(cmd *cobra.Command, err error) bool {
|
||||
var errSplitInfo *object.SplitInfoError
|
||||
|
||||
ok := errors.As(err, &errSplitInfo)
|
||||
|
||||
if ok {
|
||||
cmd.PrintErrln("Object is complex, split information received.")
|
||||
printSplitInfo(cmd, errSplitInfo.SplitInfo())
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func printSplitInfo(cmd *cobra.Command, info *object.SplitInfo) {
|
||||
bs, err := marshalSplitInfo(cmd, info)
|
||||
common.ExitOnErr(cmd, "can't marshal split info: %w", err)
|
||||
|
||||
cmd.Println(string(bs))
|
||||
}
|
||||
|
||||
func marshalSplitInfo(cmd *cobra.Command, info *object.SplitInfo) ([]byte, error) {
|
||||
toJSON, _ := cmd.Flags().GetBool(commonflags.JSON)
|
||||
toProto, _ := cmd.Flags().GetBool("proto")
|
||||
switch {
|
||||
case toJSON && toProto:
|
||||
return nil, errors.New("'--json' and '--proto' flags are mutually exclusive")
|
||||
case toJSON:
|
||||
return info.MarshalJSON()
|
||||
case toProto:
|
||||
return info.Marshal()
|
||||
default:
|
||||
b := bytes.NewBuffer(nil)
|
||||
if splitID := info.SplitID(); splitID != nil {
|
||||
b.WriteString("Split ID: " + splitID.String() + "\n")
|
||||
}
|
||||
if link, ok := info.Link(); ok {
|
||||
b.WriteString("Linking object: " + link.String() + "\n")
|
||||
}
|
||||
if last, ok := info.LastPart(); ok {
|
||||
b.WriteString("Last object: " + last.String() + "\n")
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func getRangeList(cmd *cobra.Command) ([]*object.Range, error) {
|
||||
v := cmd.Flag("range").Value.String()
|
||||
if len(v) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
vs := strings.Split(v, ",")
|
||||
rs := make([]*object.Range, len(vs))
|
||||
for i := range vs {
|
||||
r := strings.Split(vs[i], rangeSep)
|
||||
if len(r) != 2 {
|
||||
return nil, fmt.Errorf("invalid range specifier: %s", vs[i])
|
||||
}
|
||||
|
||||
offset, err := strconv.ParseUint(r[0], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid range specifier: %s", vs[i])
|
||||
}
|
||||
length, err := strconv.ParseUint(r[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid range specifier: %s", vs[i])
|
||||
}
|
||||
rs[i] = object.NewRange()
|
||||
rs[i].SetOffset(offset)
|
||||
rs[i].SetLength(length)
|
||||
}
|
||||
return rs, nil
|
||||
}
|
47
cmd/frostfs-cli/modules/object/root.go
Normal file
47
cmd/frostfs-cli/modules/object/root.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// Cmd represents the object command.
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "object",
|
||||
Short: "Operations with Objects",
|
||||
Long: `Operations with Objects`,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
// bind exactly that cmd's flags to
|
||||
// the viper before execution
|
||||
commonflags.Bind(cmd)
|
||||
commonflags.BindAPI(cmd)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
objectChildCommands := []*cobra.Command{
|
||||
objectPutCmd,
|
||||
objectDelCmd,
|
||||
objectGetCmd,
|
||||
objectSearchCmd,
|
||||
objectHeadCmd,
|
||||
objectHashCmd,
|
||||
objectRangeCmd,
|
||||
objectLockCmd}
|
||||
|
||||
Cmd.AddCommand(objectChildCommands...)
|
||||
|
||||
for _, objCommand := range objectChildCommands {
|
||||
InitBearer(objCommand)
|
||||
commonflags.InitAPI(objCommand)
|
||||
}
|
||||
|
||||
initObjectPutCmd()
|
||||
initObjectDeleteCmd()
|
||||
initObjectGetCmd()
|
||||
initObjectSearchCmd()
|
||||
initObjectHeadCmd()
|
||||
initObjectHashCmd()
|
||||
initObjectRangeCmd()
|
||||
initCommandObjectLock()
|
||||
}
|
145
cmd/frostfs-cli/modules/object/search.go
Normal file
145
cmd/frostfs-cli/modules/object/search.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
internalclient "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"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/cmd/frostfs-cli/internal/key"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oidSDK "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
searchFilters []string
|
||||
|
||||
objectSearchCmd = &cobra.Command{
|
||||
Use: "search",
|
||||
Short: "Search object",
|
||||
Long: "Search object",
|
||||
Run: searchObject,
|
||||
}
|
||||
)
|
||||
|
||||
func initObjectSearchCmd() {
|
||||
commonflags.Init(objectSearchCmd)
|
||||
initFlagSession(objectSearchCmd, "SEARCH")
|
||||
|
||||
flags := objectSearchCmd.Flags()
|
||||
|
||||
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||
_ = objectSearchCmd.MarkFlagRequired(commonflags.CIDFlag)
|
||||
|
||||
flags.StringSliceVarP(&searchFilters, "filters", "f", nil,
|
||||
"Repeated filter expressions or files with protobuf JSON")
|
||||
|
||||
flags.Bool("root", false, "Search for user objects")
|
||||
flags.Bool("phy", false, "Search physically stored objects")
|
||||
flags.String(commonflags.OIDFlag, "", "Search object by identifier")
|
||||
}
|
||||
|
||||
func searchObject(cmd *cobra.Command, _ []string) {
|
||||
var cnr cid.ID
|
||||
readCID(cmd, &cnr)
|
||||
|
||||
sf, err := parseSearchFilters(cmd)
|
||||
common.ExitOnErr(cmd, "", err)
|
||||
|
||||
pk := key.GetOrGenerate(cmd)
|
||||
|
||||
cli := internalclient.GetSDKClientByFlag(cmd, pk, commonflags.RPC)
|
||||
|
||||
var prm internalclient.SearchObjectsPrm
|
||||
prm.SetClient(cli)
|
||||
Prepare(cmd, &prm)
|
||||
readSessionGlobal(cmd, &prm, pk, cnr)
|
||||
prm.SetContainerID(cnr)
|
||||
prm.SetFilters(sf)
|
||||
|
||||
res, err := internalclient.SearchObjects(prm)
|
||||
common.ExitOnErr(cmd, "rpc error: %w", err)
|
||||
|
||||
ids := res.IDList()
|
||||
|
||||
cmd.Printf("Found %d objects.\n", len(ids))
|
||||
for i := range ids {
|
||||
cmd.Println(ids[i].String())
|
||||
}
|
||||
}
|
||||
|
||||
var searchUnaryOpVocabulary = map[string]object.SearchMatchType{
|
||||
"NOPRESENT": object.MatchNotPresent,
|
||||
}
|
||||
|
||||
var searchBinaryOpVocabulary = map[string]object.SearchMatchType{
|
||||
"EQ": object.MatchStringEqual,
|
||||
"NE": object.MatchStringNotEqual,
|
||||
"COMMON_PREFIX": object.MatchCommonPrefix,
|
||||
}
|
||||
|
||||
func parseSearchFilters(cmd *cobra.Command) (object.SearchFilters, error) {
|
||||
var fs object.SearchFilters
|
||||
|
||||
for i := range searchFilters {
|
||||
words := strings.Fields(searchFilters[i])
|
||||
|
||||
switch len(words) {
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid field number: %d", len(words))
|
||||
case 1:
|
||||
data, err := os.ReadFile(words[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read attributes filter from file: %w", err)
|
||||
}
|
||||
|
||||
subFs := object.NewSearchFilters()
|
||||
|
||||
if err := subFs.UnmarshalJSON(data); err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal attributes filter from file: %w", err)
|
||||
}
|
||||
|
||||
fs = append(fs, subFs...)
|
||||
case 2:
|
||||
m, ok := searchUnaryOpVocabulary[words[1]]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported unary op: %s", words[1])
|
||||
}
|
||||
|
||||
fs.AddFilter(words[0], "", m)
|
||||
case 3:
|
||||
m, ok := searchBinaryOpVocabulary[words[1]]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported binary op: %s", words[1])
|
||||
}
|
||||
|
||||
fs.AddFilter(words[0], words[2], m)
|
||||
}
|
||||
}
|
||||
|
||||
root, _ := cmd.Flags().GetBool("root")
|
||||
if root {
|
||||
fs.AddRootFilter()
|
||||
}
|
||||
|
||||
phy, _ := cmd.Flags().GetBool("phy")
|
||||
if phy {
|
||||
fs.AddPhyFilter()
|
||||
}
|
||||
|
||||
oid, _ := cmd.Flags().GetString(commonflags.OIDFlag)
|
||||
if oid != "" {
|
||||
var id oidSDK.ID
|
||||
if err := id.DecodeString(oid); err != nil {
|
||||
return nil, fmt.Errorf("could not parse object ID: %w", err)
|
||||
}
|
||||
|
||||
fs.AddObjectIDFilter(object.MatchStringEqual, id)
|
||||
}
|
||||
|
||||
return fs, nil
|
||||
}
|
474
cmd/frostfs-cli/modules/object/util.go
Normal file
474
cmd/frostfs-cli/modules/object/util.go
Normal file
|
@ -0,0 +1,474 @@
|
|||
package object
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
internal "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/common"
|
||||
"github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||
sessionCli "github.com/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/session"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/client"
|
||||
cid "github.com/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
frostfsecdsa "github.com/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "github.com/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"github.com/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
bearerTokenFlag = "bearer"
|
||||
|
||||
rawFlag = "raw"
|
||||
rawFlagDesc = "Set raw request option"
|
||||
fileFlag = "file"
|
||||
binaryFlag = "binary"
|
||||
)
|
||||
|
||||
type RPCParameters interface {
|
||||
SetBearerToken(prm *bearer.Token)
|
||||
SetTTL(uint32)
|
||||
SetXHeaders([]string)
|
||||
}
|
||||
|
||||
// InitBearer adds bearer token flag to a command.
|
||||
func InitBearer(cmd *cobra.Command) {
|
||||
flags := cmd.Flags()
|
||||
flags.String(bearerTokenFlag, "", "File with signed JSON or binary encoded bearer token")
|
||||
}
|
||||
|
||||
// Prepare prepares object-related parameters for a command.
|
||||
func Prepare(cmd *cobra.Command, prms ...RPCParameters) {
|
||||
ttl := viper.GetUint32(commonflags.TTL)
|
||||
common.PrintVerbose("TTL: %d", ttl)
|
||||
|
||||
for i := range prms {
|
||||
btok := common.ReadBearerToken(cmd, bearerTokenFlag)
|
||||
|
||||
prms[i].SetBearerToken(btok)
|
||||
prms[i].SetTTL(ttl)
|
||||
prms[i].SetXHeaders(parseXHeaders(cmd))
|
||||
}
|
||||
}
|
||||
|
||||
func parseXHeaders(cmd *cobra.Command) []string {
|
||||
xHeaders, _ := cmd.Flags().GetStringSlice(commonflags.XHeadersKey)
|
||||
xs := make([]string, 0, 2*len(xHeaders))
|
||||
|
||||
for i := range xHeaders {
|
||||
kv := strings.SplitN(xHeaders[i], "=", 2)
|
||||
if len(kv) != 2 {
|
||||
panic(fmt.Errorf("invalid X-Header format: %s", xHeaders[i]))
|
||||
}
|
||||
|
||||
xs = append(xs, kv[0], kv[1])
|
||||
}
|
||||
|
||||
return xs
|
||||
}
|
||||
|
||||
func readObjectAddress(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID) oid.Address {
|
||||
readCID(cmd, cnr)
|
||||
readOID(cmd, obj)
|
||||
|
||||
var addr oid.Address
|
||||
addr.SetContainer(*cnr)
|
||||
addr.SetObject(*obj)
|
||||
return addr
|
||||
}
|
||||
|
||||
func readObjectAddressBin(cmd *cobra.Command, cnr *cid.ID, obj *oid.ID, filename string) oid.Address {
|
||||
buf, err := os.ReadFile(filename)
|
||||
common.ExitOnErr(cmd, "unable to read given file: %w", err)
|
||||
objTemp := object.New()
|
||||
common.ExitOnErr(cmd, "can't unmarshal object from given file: %w", objTemp.Unmarshal(buf))
|
||||
|
||||
var addr oid.Address
|
||||
*cnr, _ = objTemp.ContainerID()
|
||||
*obj, _ = objTemp.ID()
|
||||
addr.SetContainer(*cnr)
|
||||
addr.SetObject(*obj)
|
||||
return addr
|
||||
}
|
||||
|
||||
func readCID(cmd *cobra.Command, id *cid.ID) {
|
||||
err := id.DecodeString(cmd.Flag(commonflags.CIDFlag).Value.String())
|
||||
common.ExitOnErr(cmd, "decode container ID string: %w", err)
|
||||
}
|
||||
|
||||
func readOID(cmd *cobra.Command, id *oid.ID) {
|
||||
err := id.DecodeString(cmd.Flag(commonflags.OIDFlag).Value.String())
|
||||
common.ExitOnErr(cmd, "decode object ID string: %w", err)
|
||||
}
|
||||
|
||||
// SessionPrm is a common interface of object operation's input which supports
|
||||
// sessions.
|
||||
type SessionPrm interface {
|
||||
SetSessionToken(*session.Object)
|
||||
SetClient(*client.Client)
|
||||
}
|
||||
|
||||
// forwards all parameters to _readVerifiedSession and object as nil.
|
||||
func readSessionGlobal(cmd *cobra.Command, dst SessionPrm, key *ecdsa.PrivateKey, cnr cid.ID) {
|
||||
_readVerifiedSession(cmd, dst, key, cnr, nil)
|
||||
}
|
||||
|
||||
// forwards all parameters to _readVerifiedSession.
|
||||
func readSession(cmd *cobra.Command, dst SessionPrm, key *ecdsa.PrivateKey, cnr cid.ID, obj oid.ID) {
|
||||
_readVerifiedSession(cmd, dst, key, cnr, &obj)
|
||||
}
|
||||
|
||||
// decodes session.Object from the file by path specified in the
|
||||
// commonflags.SessionToken flag. Returns nil if flag is not set.
|
||||
func getSession(cmd *cobra.Command) *session.Object {
|
||||
common.PrintVerbose("Trying to read session from the file...")
|
||||
|
||||
path, _ := cmd.Flags().GetString(commonflags.SessionToken)
|
||||
if path == "" {
|
||||
common.PrintVerbose("File with session token is not provided.")
|
||||
return nil
|
||||
}
|
||||
|
||||
common.PrintVerbose("Reading session from the file [%s]...", path)
|
||||
|
||||
var tok session.Object
|
||||
|
||||
err := common.ReadBinaryOrJSON(&tok, path)
|
||||
common.ExitOnErr(cmd, "read session: %v", err)
|
||||
|
||||
return &tok
|
||||
}
|
||||
|
||||
// decodes object session from JSON file from commonflags.SessionToken command
|
||||
// flag if it is provided, and writes resulting session into the provided SessionPrm.
|
||||
// Returns flag presence. Checks:
|
||||
//
|
||||
// - if session verb corresponds to given SessionPrm according to its type
|
||||
// - relation to the given container
|
||||
// - relation to the given object if non-nil
|
||||
// - relation to the given private key used within the command
|
||||
// - session signature
|
||||
//
|
||||
// SessionPrm MUST be one of:
|
||||
//
|
||||
// *internal.GetObjectPrm
|
||||
// *internal.HeadObjectPrm
|
||||
// *internal.SearchObjectsPrm
|
||||
// *internal.PayloadRangePrm
|
||||
// *internal.HashPayloadRangesPrm
|
||||
func _readVerifiedSession(cmd *cobra.Command, dst SessionPrm, key *ecdsa.PrivateKey, cnr cid.ID, obj *oid.ID) {
|
||||
var cmdVerb session.ObjectVerb
|
||||
|
||||
switch dst.(type) {
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported op parameters %T", dst))
|
||||
case *internal.GetObjectPrm:
|
||||
cmdVerb = session.VerbObjectGet
|
||||
case *internal.HeadObjectPrm:
|
||||
cmdVerb = session.VerbObjectHead
|
||||
case *internal.SearchObjectsPrm:
|
||||
cmdVerb = session.VerbObjectSearch
|
||||
case *internal.PayloadRangePrm:
|
||||
cmdVerb = session.VerbObjectRange
|
||||
case *internal.HashPayloadRangesPrm:
|
||||
cmdVerb = session.VerbObjectRangeHash
|
||||
}
|
||||
|
||||
tok := getSession(cmd)
|
||||
if tok == nil {
|
||||
return
|
||||
}
|
||||
|
||||
common.PrintVerbose("Checking session correctness...")
|
||||
|
||||
switch false {
|
||||
case tok.AssertContainer(cnr):
|
||||
common.ExitOnErr(cmd, "", errors.New("unrelated container in the session"))
|
||||
case obj == nil || tok.AssertObject(*obj):
|
||||
common.ExitOnErr(cmd, "", errors.New("unrelated object in the session"))
|
||||
case tok.AssertVerb(cmdVerb):
|
||||
common.ExitOnErr(cmd, "", errors.New("wrong verb of the session"))
|
||||
case tok.AssertAuthKey((*frostfsecdsa.PublicKey)(&key.PublicKey)):
|
||||
common.ExitOnErr(cmd, "", errors.New("unrelated key in the session"))
|
||||
case tok.VerifySignature():
|
||||
common.ExitOnErr(cmd, "", errors.New("invalid signature of the session data"))
|
||||
}
|
||||
|
||||
common.PrintVerbose("Session is correct.")
|
||||
|
||||
dst.SetSessionToken(tok)
|
||||
}
|
||||
|
||||
// ReadOrOpenSession opens client connection and calls ReadOrOpenSessionViaClient with it.
|
||||
func ReadOrOpenSession(cmd *cobra.Command, dst SessionPrm, key *ecdsa.PrivateKey, cnr cid.ID, obj *oid.ID) {
|
||||
cli := internal.GetSDKClientByFlag(cmd, key, commonflags.RPC)
|
||||
ReadOrOpenSessionViaClient(cmd, dst, cli, key, cnr, obj)
|
||||
}
|
||||
|
||||
// ReadOrOpenSessionViaClient tries to read session from the file specified in
|
||||
// commonflags.SessionToken flag, finalizes structures of the decoded token
|
||||
// and write the result into provided SessionPrm. If file is missing,
|
||||
// ReadOrOpenSessionViaClient calls OpenSessionViaClient.
|
||||
func ReadOrOpenSessionViaClient(cmd *cobra.Command, dst SessionPrm, cli *client.Client, key *ecdsa.PrivateKey, cnr cid.ID, obj *oid.ID) {
|
||||
tok := getSession(cmd)
|
||||
if tok == nil {
|
||||
OpenSessionViaClient(cmd, dst, cli, key, cnr, obj)
|
||||
return
|
||||
}
|
||||
|
||||
var objs []oid.ID
|
||||
if obj != nil {
|
||||
objs = []oid.ID{*obj}
|
||||
|
||||
if _, ok := dst.(*internal.DeleteObjectPrm); ok {
|
||||
common.PrintVerbose("Collecting relatives of the removal object...")
|
||||
|
||||
objs = append(objs, collectObjectRelatives(cmd, cli, cnr, *obj)...)
|
||||
}
|
||||
}
|
||||
|
||||
finalizeSession(cmd, dst, tok, key, cnr, objs...)
|
||||
dst.SetClient(cli)
|
||||
}
|
||||
|
||||
// OpenSession opens client connection and calls OpenSessionViaClient with it.
|
||||
func OpenSession(cmd *cobra.Command, dst SessionPrm, key *ecdsa.PrivateKey, cnr cid.ID, obj *oid.ID) {
|
||||
cli := internal.GetSDKClientByFlag(cmd, key, commonflags.RPC)
|
||||
OpenSessionViaClient(cmd, dst, cli, key, cnr, obj)
|
||||
}
|
||||
|
||||
// OpenSessionViaClient opens object session with the remote node, finalizes
|
||||
// structure of the session token and writes the result into the provided
|
||||
// SessionPrm. Also writes provided client connection to the SessionPrm.
|
||||
//
|
||||
// SessionPrm MUST be one of:
|
||||
//
|
||||
// *internal.PutObjectPrm
|
||||
// *internal.DeleteObjectPrm
|
||||
//
|
||||
// If provided SessionPrm is of type internal.DeleteObjectPrm, OpenSessionViaClient
|
||||
// spreads the session to all object's relatives.
|
||||
func OpenSessionViaClient(cmd *cobra.Command, dst SessionPrm, cli *client.Client, key *ecdsa.PrivateKey, cnr cid.ID, obj *oid.ID) {
|
||||
var objs []oid.ID
|
||||
|
||||
if obj != nil {
|
||||
if _, ok := dst.(*internal.DeleteObjectPrm); ok {
|
||||
common.PrintVerbose("Collecting relatives of the removal object...")
|
||||
|
||||
rels := collectObjectRelatives(cmd, cli, cnr, *obj)
|
||||
|
||||
if len(rels) == 0 {
|
||||
objs = []oid.ID{*obj}
|
||||
} else {
|
||||
objs = append(rels, *obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tok session.Object
|
||||
|
||||
const sessionLifetime = 10 // in NeoFS epochs
|
||||
|
||||
common.PrintVerbose("Opening remote session with the node...")
|
||||
|
||||
err := sessionCli.CreateSession(&tok, cli, sessionLifetime)
|
||||
common.ExitOnErr(cmd, "open remote session: %w", err)
|
||||
|
||||
common.PrintVerbose("Session successfully opened.")
|
||||
|
||||
finalizeSession(cmd, dst, &tok, key, cnr, objs...)
|
||||
|
||||
dst.SetClient(cli)
|
||||
}
|
||||
|
||||
// specifies session verb, binds the session to the given container and limits
|
||||
// the session by the given objects (if specified). After all data is written,
|
||||
// signs session using provided private key and writes the session into the
|
||||
// given SessionPrm.
|
||||
//
|
||||
// SessionPrm MUST be one of:
|
||||
//
|
||||
// *internal.PutObjectPrm
|
||||
// *internal.DeleteObjectPrm
|
||||
func finalizeSession(cmd *cobra.Command, dst SessionPrm, tok *session.Object, key *ecdsa.PrivateKey, cnr cid.ID, objs ...oid.ID) {
|
||||
common.PrintVerbose("Finalizing session token...")
|
||||
|
||||
switch dst.(type) {
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported op parameters %T", dst))
|
||||
case *internal.PutObjectPrm:
|
||||
common.PrintVerbose("Binding session to object PUT...")
|
||||
tok.ForVerb(session.VerbObjectPut)
|
||||
case *internal.DeleteObjectPrm:
|
||||
common.PrintVerbose("Binding session to object DELETE...")
|
||||
tok.ForVerb(session.VerbObjectDelete)
|
||||
}
|
||||
|
||||
common.PrintVerbose("Binding session to container %s...", cnr)
|
||||
|
||||
tok.BindContainer(cnr)
|
||||
if len(objs) > 0 {
|
||||
common.PrintVerbose("Limiting session by the objects %v...", objs)
|
||||
tok.LimitByObjects(objs...)
|
||||
}
|
||||
|
||||
common.PrintVerbose("Signing session...")
|
||||
|
||||
err := tok.Sign(*key)
|
||||
common.ExitOnErr(cmd, "sign session: %w", err)
|
||||
|
||||
common.PrintVerbose("Session token successfully formed and attached to the request.")
|
||||
|
||||
dst.SetSessionToken(tok)
|
||||
}
|
||||
|
||||
// calls commonflags.InitSession with "object <verb>" name.
|
||||
func initFlagSession(cmd *cobra.Command, verb string) {
|
||||
commonflags.InitSession(cmd, "object "+verb)
|
||||
}
|
||||
|
||||
// collects and returns all relatives of the given object stored in the specified
|
||||
// container. Empty result without an error means lack of relationship in the
|
||||
// container.
|
||||
//
|
||||
// The object itself is not included in the result.
|
||||
func collectObjectRelatives(cmd *cobra.Command, cli *client.Client, cnr cid.ID, obj oid.ID) []oid.ID {
|
||||
common.PrintVerbose("Fetching raw object header...")
|
||||
|
||||
// request raw header first
|
||||
var addrObj oid.Address
|
||||
addrObj.SetContainer(cnr)
|
||||
addrObj.SetObject(obj)
|
||||
|
||||
var prmHead internal.HeadObjectPrm
|
||||
prmHead.SetClient(cli)
|
||||
prmHead.SetAddress(addrObj)
|
||||
prmHead.SetRawFlag(true)
|
||||
|
||||
Prepare(cmd, &prmHead)
|
||||
|
||||
_, err := internal.HeadObject(prmHead)
|
||||
|
||||
var errSplit *object.SplitInfoError
|
||||
|
||||
switch {
|
||||
default:
|
||||
common.ExitOnErr(cmd, "failed to get raw object header: %w", err)
|
||||
case err == nil:
|
||||
common.PrintVerbose("Raw header received - object is singular.")
|
||||
return nil
|
||||
case errors.As(err, &errSplit):
|
||||
common.PrintVerbose("Split information received - object is virtual.")
|
||||
}
|
||||
|
||||
splitInfo := errSplit.SplitInfo()
|
||||
|
||||
// collect split chain by the descending ease of operations (ease is evaluated heuristically).
|
||||
// If any approach fails, we don't try the next since we assume that it will fail too.
|
||||
|
||||
if idLinking, ok := splitInfo.Link(); ok {
|
||||
common.PrintVerbose("Collecting split members using linking object %s...", idLinking)
|
||||
|
||||
addrObj.SetObject(idLinking)
|
||||
prmHead.SetAddress(addrObj)
|
||||
prmHead.SetRawFlag(false)
|
||||
// client is already set
|
||||
|
||||
res, err := internal.HeadObject(prmHead)
|
||||
common.ExitOnErr(cmd, "failed to get linking object's header: %w", err)
|
||||
|
||||
children := res.Header().Children()
|
||||
|
||||
common.PrintVerbose("Received split members from the linking object: %v", children)
|
||||
|
||||
// include linking object
|
||||
return append(children, idLinking)
|
||||
}
|
||||
|
||||
if idSplit := splitInfo.SplitID(); idSplit != nil {
|
||||
common.PrintVerbose("Collecting split members by split ID...")
|
||||
|
||||
var query object.SearchFilters
|
||||
query.AddSplitIDFilter(object.MatchStringEqual, idSplit)
|
||||
|
||||
var prm internal.SearchObjectsPrm
|
||||
prm.SetContainerID(cnr)
|
||||
prm.SetClient(cli)
|
||||
prm.SetFilters(query)
|
||||
|
||||
res, err := internal.SearchObjects(prm)
|
||||
common.ExitOnErr(cmd, "failed to search objects by split ID: %w", err)
|
||||
|
||||
members := res.IDList()
|
||||
|
||||
common.PrintVerbose("Found objects by split ID: %v", res.IDList())
|
||||
|
||||
return members
|
||||
}
|
||||
|
||||
idMember, ok := splitInfo.LastPart()
|
||||
if !ok {
|
||||
common.ExitOnErr(cmd, "", errors.New("missing any data in received object split information"))
|
||||
}
|
||||
|
||||
common.PrintVerbose("Traverse the object split chain in reverse...", idMember)
|
||||
|
||||
var res *internal.HeadObjectRes
|
||||
chain := []oid.ID{idMember}
|
||||
chainSet := map[oid.ID]struct{}{idMember: {}}
|
||||
|
||||
prmHead.SetRawFlag(false)
|
||||
// split members are almost definitely singular, but don't get hung up on it
|
||||
|
||||
for {
|
||||
common.PrintVerbose("Reading previous element of the split chain member %s...", idMember)
|
||||
|
||||
addrObj.SetObject(idMember)
|
||||
|
||||
res, err = internal.HeadObject(prmHead)
|
||||
common.ExitOnErr(cmd, "failed to read split chain member's header: %w", err)
|
||||
|
||||
idMember, ok = res.Header().PreviousID()
|
||||
if !ok {
|
||||
common.PrintVerbose("Chain ended.")
|
||||
break
|
||||
}
|
||||
|
||||
if _, ok = chainSet[idMember]; ok {
|
||||
common.ExitOnErr(cmd, "", fmt.Errorf("duplicated member in the split chain %s", idMember))
|
||||
}
|
||||
|
||||
chain = append(chain, idMember)
|
||||
chainSet[idMember] = struct{}{}
|
||||
}
|
||||
|
||||
common.PrintVerbose("Looking for a linking object...")
|
||||
|
||||
var query object.SearchFilters
|
||||
query.AddParentIDFilter(object.MatchStringEqual, obj)
|
||||
|
||||
var prmSearch internal.SearchObjectsPrm
|
||||
prmSearch.SetClient(cli)
|
||||
prmSearch.SetContainerID(cnr)
|
||||
prmSearch.SetFilters(query)
|
||||
|
||||
resSearch, err := internal.SearchObjects(prmSearch)
|
||||
common.ExitOnErr(cmd, "failed to find object children: %w", err)
|
||||
|
||||
list := resSearch.IDList()
|
||||
|
||||
for i := range list {
|
||||
if _, ok = chainSet[list[i]]; !ok {
|
||||
common.PrintVerbose("Found one more related object %s.", list[i])
|
||||
chain = append(chain, list[i])
|
||||
}
|
||||
}
|
||||
|
||||
return chain
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue