diff --git a/cmd/neofs-cli/modules/root.go b/cmd/neofs-cli/modules/root.go index f539c991..d824b9b3 100644 --- a/cmd/neofs-cli/modules/root.go +++ b/cmd/neofs-cli/modules/root.go @@ -16,6 +16,7 @@ import ( objectCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/object" sessionCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/session" sgCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/storagegroup" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/tree" utilCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/util" "github.com/nspcc-dev/neofs-node/misc" "github.com/nspcc-dev/neofs-node/pkg/util/gendoc" @@ -81,6 +82,7 @@ func init() { rootCmd.AddCommand(objectCli.Cmd) rootCmd.AddCommand(sgCli.Cmd) rootCmd.AddCommand(containerCli.Cmd) + rootCmd.AddCommand(tree.Cmd) rootCmd.AddCommand(gendoc.Command(rootCmd)) } diff --git a/cmd/neofs-cli/modules/tree/add.go b/cmd/neofs-cli/modules/tree/add.go new file mode 100644 index 00000000..03a49adb --- /dev/null +++ b/cmd/neofs-cli/modules/tree/add.go @@ -0,0 +1,95 @@ +package tree + +import ( + "crypto/sha256" + "fmt" + "strings" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" + "github.com/nspcc-dev/neofs-node/pkg/services/tree" + cid "github.com/nspcc-dev/neofs-sdk-go/container/id" + "github.com/spf13/cobra" +) + +var addCmd = &cobra.Command{ + Use: "add", + Short: "Add a node to the tree service", + Run: add, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, +} + +func initAddCmd() { + commonflags.Init(addCmd) + initCTID(addCmd) + + ff := addCmd.Flags() + ff.StringSlice(metaFlagKey, nil, "Meta pairs in the form of Key1=[0x]Value1,Key2=[0x]Value2") + ff.Uint64(parentIDFlagKey, 0, "Parent node ID") + + _ = cobra.MarkFlagRequired(ff, commonflags.RPC) +} + +func add(cmd *cobra.Command, _ []string) { + pk := key.GetOrGenerate(cmd) + + var cnr cid.ID + err := cnr.DecodeString(cmd.Flag(containerIDFlagKey).Value.String()) + common.ExitOnErr(cmd, "decode container ID string: %w", err) + + tid, _ := cmd.Flags().GetString(treeIDFlagKey) + pid, _ := cmd.Flags().GetUint64(parentIDFlagKey) + + meta, err := parseMeta(cmd) + common.ExitOnErr(cmd, "meta data parsing: %w", err) + + ctx := cmd.Context() + + cli, err := _client(ctx) + common.ExitOnErr(cmd, "client: %w", err) + + rawCID := make([]byte, sha256.Size) + cnr.Encode(rawCID) + + req := new(tree.AddRequest) + req.Body = &tree.AddRequest_Body{ + ContainerId: rawCID, + TreeId: tid, + ParentId: pid, + Meta: meta, + BearerToken: nil, // TODO: #1891 add token handling + } + + common.ExitOnErr(cmd, "message signing: %w", tree.SignMessage(req, pk)) + + resp, err := cli.Add(ctx, req) + common.ExitOnErr(cmd, "rpc call: %w", err) + + cmd.Println("Node ID: ", resp.Body.NodeId) +} + +func parseMeta(cmd *cobra.Command) ([]*tree.KeyValue, error) { + raws, _ := cmd.Flags().GetStringSlice(metaFlagKey) + if len(raws) == 0 { + return nil, nil + } + + pairs := make([]*tree.KeyValue, 0, len(raws)) + for i := range raws { + kv := strings.SplitN(raws[i], "=", 2) + if len(kv) != 2 { + return nil, fmt.Errorf("invalid meta pair format: %s", raws[i]) + } + + var pair tree.KeyValue + pair.Key = kv[0] + pair.Value = []byte(kv[1]) + + pairs = append(pairs, &pair) + } + + return pairs, nil +} diff --git a/cmd/neofs-cli/modules/tree/client.go b/cmd/neofs-cli/modules/tree/client.go new file mode 100644 index 00000000..2590fc15 --- /dev/null +++ b/cmd/neofs-cli/modules/tree/client.go @@ -0,0 +1,40 @@ +package tree + +import ( + "context" + "strings" + "time" + + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/pkg/network" + "github.com/nspcc-dev/neofs-node/pkg/services/tree" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +// _client returns grpc Tree service client. Should be removed +// after making Tree API public. +func _client(ctx context.Context) (tree.TreeServiceClient, error) { + var netAddr network.Address + err := netAddr.FromString(viper.GetString(commonflags.RPC)) + if err != nil { + return nil, err + } + + opts := make([]grpc.DialOption, 1, 2) + opts[0] = grpc.WithBlock() + + if !strings.HasPrefix(netAddr.URIAddr(), "grpcs:") { + opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) + } + + // a default connection establishing timeout + const defaultClientConnectTimeout = time.Second * 2 + + ctx, cancel := context.WithTimeout(ctx, defaultClientConnectTimeout) + cc, err := grpc.DialContext(ctx, netAddr.URIAddr(), opts...) + cancel() + + return tree.NewTreeServiceClient(cc), err +} diff --git a/cmd/neofs-cli/modules/tree/root.go b/cmd/neofs-cli/modules/tree/root.go new file mode 100644 index 00000000..48ee8c86 --- /dev/null +++ b/cmd/neofs-cli/modules/tree/root.go @@ -0,0 +1,34 @@ +package tree + +import ( + "github.com/spf13/cobra" +) + +var Cmd = &cobra.Command{ + Use: "tree", + Short: "Operations with the Tree service", +} + +func init() { + Cmd.AddCommand(addCmd) + + initAddCmd() +} + +const ( + containerIDFlagKey = "cid" + treeIDFlagKey = "tid" + parentIDFlagKey = "pid" + + metaFlagKey = "meta" +) + +func initCTID(cmd *cobra.Command) { + ff := cmd.Flags() + + ff.String(containerIDFlagKey, "", "Container ID") + _ = cmd.MarkFlagRequired(containerIDFlagKey) + + ff.String(treeIDFlagKey, "", "Tree ID") + _ = cmd.MarkFlagRequired(treeIDFlagKey) +}