[#979] adm: Add subnet-related commands

Add `subnet` command which contains all subnet-related commands. Add
sub-commands:
  * `create` for creation;
  * `remove` for removal;
  * `get` for reading;
  * `admin` for admin management;
  * `client` for client management.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2021-11-28 18:48:32 +03:00 committed by LeL
parent 70f17dc778
commit e8f8e58e90
11 changed files with 1096 additions and 4 deletions

View file

@ -0,0 +1,65 @@
package internal
import (
"fmt"
"strconv"
"google.golang.org/protobuf/proto"
)
// StringifySubnetClientGroupID returns string representation of SubnetClientGroupID using MarshalText.
// Returns string with message on error.
func StringifySubnetClientGroupID(id *SubnetClientGroupID) string {
text, err := id.MarshalText()
if err != nil {
return fmt.Sprintf("<invalid> %v", err)
}
return string(text)
}
// MarshalText encodes SubnetClientGroupID into text format according to NeoFS API V2 protocol:
// value in base-10 integer string format.
//
// Implements encoding.TextMarshaler.
func (x *SubnetClientGroupID) MarshalText() ([]byte, error) {
num := x.GetValue() // NPE safe, returns zero on nil
return []byte(strconv.FormatUint(uint64(num), 10)), nil
}
// UnmarshalText decodes SubnetID from the text according to NeoFS API V2 protocol:
// should be base-10 integer string format with bitsize = 32.
//
// Returns strconv.ErrRange if integer overflows uint32.
//
// Must not be called on nil.
//
// Implements encoding.TextUnmarshaler.
func (x *SubnetClientGroupID) UnmarshalText(txt []byte) error {
num, err := strconv.ParseUint(string(txt), 10, 32)
if err != nil {
return fmt.Errorf("invalid numeric value: %w", err)
}
x.SetNumber(uint32(num))
return nil
}
// Marshal encodes SubnetClientGroupID into a binary format of NeoFS API V2 protocol
// (Protocol Buffers with direct field order).
func (x *SubnetClientGroupID) Marshal() ([]byte, error) {
return proto.Marshal(x)
}
// Unmarshal decodes SubnetClientGroupID from NeoFS API V2 binary format (see Marshal). Must not be called on nil.
func (x *SubnetClientGroupID) Unmarshal(data []byte) error {
return proto.Unmarshal(data, x)
}
// SetNumber sets SubnetClientGroupID value in uint32 format. Must not be called on nil.
// By default, number is 0.
func (x *SubnetClientGroupID) SetNumber(num uint32) {
x.Value = num
}

View file

@ -0,0 +1,156 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.19.1
// source: cmd/neofs-adm/internal/modules/morph/internal/types.proto
package internal
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// Client group identifier in the NeoFS subnet.
//
// String representation of a value is base-10 integer.
//
// JSON representation is an object containing single `value` number field.
type SubnetClientGroupID struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// 4-byte integer identifier of the subnet client group.
Value uint32 `protobuf:"fixed32,1,opt,name=value,proto3" json:"value,omitempty"`
}
func (x *SubnetClientGroupID) Reset() {
*x = SubnetClientGroupID{}
if protoimpl.UnsafeEnabled {
mi := &file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SubnetClientGroupID) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*SubnetClientGroupID) ProtoMessage() {}
func (x *SubnetClientGroupID) ProtoReflect() protoreflect.Message {
mi := &file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SubnetClientGroupID.ProtoReflect.Descriptor instead.
func (*SubnetClientGroupID) Descriptor() ([]byte, []int) {
return file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescGZIP(), []int{0}
}
func (x *SubnetClientGroupID) GetValue() uint32 {
if x != nil {
return x.Value
}
return 0
}
var File_cmd_neofs_adm_internal_modules_morph_internal_types_proto protoreflect.FileDescriptor
var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc = []byte{
0x0a, 0x39, 0x63, 0x6d, 0x64, 0x2f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x61, 0x64, 0x6d, 0x2f,
0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
0x2f, 0x6d, 0x6f, 0x72, 0x70, 0x68, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f,
0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x6e, 0x65, 0x6f,
0x2e, 0x66, 0x73, 0x2e, 0x76, 0x32, 0x2e, 0x72, 0x65, 0x66, 0x73, 0x22, 0x2b, 0x0a, 0x13, 0x53,
0x75, 0x62, 0x6e, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x6f, 0x75, 0x70,
0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x07, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x4f, 0x5a, 0x4d, 0x67, 0x69, 0x74, 0x68,
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6e, 0x73, 0x70, 0x63, 0x63, 0x2d, 0x64, 0x65, 0x76,
0x2f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x6e, 0x6f, 0x64, 0x65, 0x2f, 0x63, 0x6d, 0x64, 0x2f,
0x6e, 0x65, 0x6f, 0x66, 0x73, 0x2d, 0x61, 0x64, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2f, 0x6d, 0x6f, 0x72, 0x70, 0x68,
0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescOnce sync.Once
file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData = file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc
)
func file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescGZIP() []byte {
file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescOnce.Do(func() {
file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData = protoimpl.X.CompressGZIP(file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData)
})
return file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDescData
}
var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes = []interface{}{
(*SubnetClientGroupID)(nil), // 0: neo.fs.v2.refs.SubnetClientGroupID
}
var file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs = []int32{
0, // [0:0] is the sub-list for method output_type
0, // [0:0] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_init() }
func file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_init() {
if File_cmd_neofs_adm_internal_modules_morph_internal_types_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SubnetClientGroupID); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes,
DependencyIndexes: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs,
MessageInfos: file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_msgTypes,
}.Build()
File_cmd_neofs_adm_internal_modules_morph_internal_types_proto = out.File
file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_rawDesc = nil
file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_goTypes = nil
file_cmd_neofs_adm_internal_modules_morph_internal_types_proto_depIdxs = nil
}

View file

@ -0,0 +1,15 @@
syntax = "proto3";
package neo.fs.v2.refs;
option go_package = "github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal";
// Client group identifier in the NeoFS subnet.
//
// String representation of a value is base-10 integer.
//
// JSON representation is an object containing single `value` number field.
message SubnetClientGroupID {
// 4-byte integer identifier of the subnet client group.
fixed32 value = 1 [json_name = "value"];
}

View file

@ -205,4 +205,6 @@ func init() {
refillGasCmd.Flags().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
refillGasCmd.Flags().String(storageWalletFlag, "", "path to storage node wallet")
refillGasCmd.Flags().String(refillGasAmountFlag, "", "additional amount of GAS to transfer")
RootCmd.AddCommand(cmdSubnet)
}

View file

@ -0,0 +1,711 @@
package morph
import (
"encoding/hex"
"errors"
"fmt"
"math"
"os"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neofs-node/cmd/neofs-adm/internal/modules/morph/internal"
"github.com/nspcc-dev/neofs-node/pkg/morph/client"
morphsubnet "github.com/nspcc-dev/neofs-node/pkg/morph/client/subnet"
"github.com/nspcc-dev/neofs-node/pkg/util/rand"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/nspcc-dev/neofs-sdk-go/subnet"
subnetid "github.com/nspcc-dev/neofs-sdk-go/subnet/id"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// cmdSubnet flags.
const (
// Neo RPC endpoint
flagSubnetEndpoint = endpointFlag
// filepath to private key
flagSubnetKey = "key"
// contract address
flagSubnetContract = "contract"
// disable notary
flagSubnetNonNotary = "non-notary"
)
func viperBindFlags(cmd *cobra.Command, flags ...string) {
for i := range flags {
_ = viper.BindPFlag(flags[i], cmd.Flags().Lookup(flags[i]))
}
}
// subnet command section.
var cmdSubnet = &cobra.Command{
Use: "subnet",
Short: "NeoFS subnet management.",
PreRun: func(cmd *cobra.Command, _ []string) {
viperBindFlags(cmd,
flagSubnetEndpoint,
flagSubnetKey,
flagSubnetContract,
flagSubnetNonNotary,
)
},
}
// shared flags of cmdSubnet sub-commands.
const (
// subnet identifier
flagSubnet = "subnet"
// subnet client group ID
flagSubnetGroup = "group"
)
// reads private key from the filepath configured in flagSubnetKey flag.
func readSubnetKey(key *keys.PrivateKey) error {
// read key from file
keyPath := viper.GetString(flagSubnetKey)
if keyPath == "" {
return errors.New("missing path to private key")
}
data, err := os.ReadFile(keyPath)
if err != nil {
return fmt.Errorf("read private key file: %w", err)
}
// decode key
k, err := keys.NewPrivateKeyFromBytes(data)
if err != nil {
return fmt.Errorf("decode private key: %w", err)
}
*key = *k
return nil
}
// initializes morph subnet client with the specified private key.
//
// Parameters are read from:
// * contract address: flagSubnetContract;
// * endpoint: flagSubnetEndpoint;
// * non-notary mode: flagSubnetNonNotary.
func initSubnetClient(c *morphsubnet.Client, key *keys.PrivateKey) error {
// read endpoint
endpoint := viper.GetString(flagSubnetEndpoint)
if endpoint == "" {
return errors.New("missing endpoint")
}
// read contract address
contractAddr, err := util.Uint160DecodeStringLE(viper.GetString(flagSubnetContract))
if err != nil {
return fmt.Errorf("subnet contract address: %w", err)
}
// create base morph client
cMorph, err := client.New(key, endpoint)
if err != nil {
return err
}
// calc client mode
cMode := morphsubnet.NotaryNonAlphabet
if viper.GetBool(flagSubnetNonNotary) {
cMode = morphsubnet.NonNotary
}
// init subnet morph client
var prmInit morphsubnet.InitPrm
prmInit.SetBaseClient(cMorph)
prmInit.SetContractAddress(contractAddr)
prmInit.SetMode(cMode)
if err := c.Init(prmInit); err != nil {
return fmt.Errorf("init call: %w", err)
}
return nil
}
// create subnet command.
var cmdSubnetCreate = &cobra.Command{
Use: "create",
Short: "Create NeoFS subnet.",
RunE: func(cmd *cobra.Command, _ []string) error {
// read private key
var key keys.PrivateKey
err := readSubnetKey(&key)
if err != nil {
return fmt.Errorf("read private key: %w", err)
}
// calculate wallet address from key
n3Wallet, err := owner.NEO3WalletFromPublicKey(&key.PrivateKey.PublicKey)
if err != nil {
return fmt.Errorf("wallet from key: %w", err)
}
// generate subnet ID and marshal it
var (
id subnetid.ID
num uint32
)
for {
num = uint32(rand.Uint64(rand.New(), math.MaxUint32))
id.SetNumber(num)
if !subnetid.IsZero(id) {
break
}
}
binID, err := id.Marshal()
if err != nil {
return fmt.Errorf("marshal subnet ID: %w", err)
}
// declare creator ID and encode it
creator := *owner.NewIDFromNeo3Wallet(n3Wallet)
binCreator, err := creator.Marshal()
if err != nil {
return fmt.Errorf("marshal creator ID: %w", err)
}
// fill subnet info and encode it
var info subnet.Info
info.SetID(id)
info.SetOwner(creator)
binInfo, err := info.Marshal()
if err != nil {
return fmt.Errorf("marshal subnet info: %w", err)
}
// initialize morph subnet client
var cSubnet morphsubnet.Client
err = initSubnetClient(&cSubnet, &key)
if err != nil {
return fmt.Errorf("init subnet client: %w", err)
}
// prepare call parameters and create subnet
var prm morphsubnet.PutPrm
prm.SetID(binID)
prm.SetOwner(binCreator)
prm.SetInfo(binInfo)
_, err = cSubnet.Put(prm)
if err != nil {
return fmt.Errorf("morph call: %w", err)
}
cmd.Printf("Create subnet request sent successfully. ID: %s.\n", &id)
return nil
},
}
// cmdSubnetRemove flags.
const (
// subnet ID to be removed
flagSubnetRemoveID = flagSubnet
)
// errZeroSubnet is returned on attempts to work with zero subnet which is virtual.
var errZeroSubnet = errors.New("zero subnet")
// remove subnet command.
var cmdSubnetRemove = &cobra.Command{
Use: "remove",
Short: "Remove NeoFS subnet.",
PreRun: func(cmd *cobra.Command, _ []string) {
viperBindFlags(cmd,
flagSubnetRemoveID,
)
},
RunE: func(cmd *cobra.Command, _ []string) error {
// read private key
var key keys.PrivateKey
err := readSubnetKey(&key)
if err != nil {
return fmt.Errorf("read private key: %w", err)
}
// read ID and encode it
var id subnetid.ID
err = id.UnmarshalText([]byte(viper.GetString(flagSubnetRemoveID)))
if err != nil {
return fmt.Errorf("decode ID text: %w", err)
}
if subnetid.IsZero(id) {
return errZeroSubnet
}
binID, err := id.Marshal()
if err != nil {
return fmt.Errorf("marshal subnet ID: %w", err)
}
// initialize morph subnet client
var cSubnet morphsubnet.Client
err = initSubnetClient(&cSubnet, &key)
if err != nil {
return fmt.Errorf("init subnet client: %w", err)
}
// prepare call parameters and remove subnet
var prm morphsubnet.DeletePrm
prm.SetID(binID)
_, err = cSubnet.Delete(prm)
if err != nil {
return fmt.Errorf("morph call: %w", err)
}
cmd.Println("Remove subnet request sent successfully.")
return nil
},
}
// cmdSubnetGet flags.
const (
// subnet ID to be read
flagSubnetGetID = flagSubnet
)
// get subnet command.
var cmdSubnetGet = &cobra.Command{
Use: "get",
Short: "Read information about the NeoFS subnet.",
PreRun: func(cmd *cobra.Command, _ []string) {
viperBindFlags(cmd,
flagSubnetGetID,
)
},
RunE: func(cmd *cobra.Command, _ []string) error {
// read private key
var key keys.PrivateKey
err := readSubnetKey(&key)
if err != nil {
return fmt.Errorf("read private key: %w", err)
}
// read ID and encode it
var id subnetid.ID
err = id.UnmarshalText([]byte(viper.GetString(flagSubnetGetID)))
if err != nil {
return fmt.Errorf("decode ID text: %w", err)
}
if subnetid.IsZero(id) {
return errZeroSubnet
}
binID, err := id.Marshal()
if err != nil {
return fmt.Errorf("marshal subnet ID: %w", err)
}
// initialize morph subnet client
var cSubnet morphsubnet.Client
err = initSubnetClient(&cSubnet, &key)
if err != nil {
return fmt.Errorf("init subnet client: %w", err)
}
// prepare call parameters and read subnet
var prm morphsubnet.GetPrm
prm.SetID(binID)
res, err := cSubnet.Get(prm)
if err != nil {
return fmt.Errorf("morph call: %w", err)
}
// decode info
var info subnet.Info
if err = info.Unmarshal(res.Info()); err != nil {
return fmt.Errorf("decode subnet info: %w", err)
}
// print information
var ownerID owner.ID
info.ReadOwner(&ownerID)
cmd.Printf("Owner: %s\n", ownerID)
return nil
},
}
// cmdSubnetAdmin subnet flags.
const (
// subnet ID to be managed
flagSubnetAdminSubnet = flagSubnet
// admin public key
flagSubnetAdminID = "id"
// manage client admins instead of node ones
flagSubnetAdminClient = "client"
)
// command to manage subnet admins.
var cmdSubnetAdmin = &cobra.Command{
Use: "admin",
Short: "Manage administrators of the NeoFS subnet.",
PreRun: func(cmd *cobra.Command, _ []string) {
viperBindFlags(cmd,
flagSubnetAdminSubnet,
flagSubnetAdminID,
)
},
}
// cmdSubnetAdminAdd flags.
const (
// client group ID
flagSubnetAdminAddGroup = flagSubnetGroup
)
// common executor cmdSubnetAdminAdd and cmdSubnetAdminRemove commands.
func manageSubnetAdmins(cmd *cobra.Command, rm bool) error {
// read private key
var key keys.PrivateKey
err := readSubnetKey(&key)
if err != nil {
return fmt.Errorf("read private key: %w", err)
}
// read ID and encode it
var id subnetid.ID
err = id.UnmarshalText([]byte(viper.GetString(flagSubnetAdminSubnet)))
if err != nil {
return fmt.Errorf("decode ID text: %w", err)
}
if subnetid.IsZero(id) {
return errZeroSubnet
}
binID, err := id.Marshal()
if err != nil {
return fmt.Errorf("marshal subnet ID: %w", err)
}
// read admin key and decode it
binAdminKey, err := hex.DecodeString(viper.GetString(flagSubnetAdminID))
if err != nil {
return fmt.Errorf("decode admin key text: %w", err)
}
var pubkey keys.PublicKey
if err = pubkey.DecodeBytes(binAdminKey); err != nil {
return fmt.Errorf("admin key format: %w", err)
}
// prepare call parameters
var prm morphsubnet.ManageAdminsPrm
if viper.GetBool(flagSubnetAdminClient) {
// read group ID and encode it
var groupID internal.SubnetClientGroupID
err = groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup)))
if err != nil {
return fmt.Errorf("decode group ID text: %w", err)
}
binGroupID, err := groupID.Marshal()
if err != nil {
return fmt.Errorf("marshal group ID: %w", err)
}
prm.SetClient()
prm.SetGroup(binGroupID)
}
prm.SetSubnet(binID)
prm.SetAdmin(binAdminKey)
if rm {
prm.SetRemove()
}
// initialize morph subnet client
var cSubnet morphsubnet.Client
err = initSubnetClient(&cSubnet, &key)
if err != nil {
return fmt.Errorf("init subnet client: %w", err)
}
_, err = cSubnet.ManageAdmins(prm)
if err != nil {
return fmt.Errorf("morph call: %w", err)
}
var op string
if rm {
op = "Remove"
} else {
op = "Add"
}
cmd.Printf("%s admin request sent successfully.\n", op)
return nil
}
// command to add subnet admin.
var cmdSubnetAdminAdd = &cobra.Command{
Use: "add",
Short: "Add admin to the NeoFS subnet.",
PreRun: func(cmd *cobra.Command, _ []string) {
viperBindFlags(cmd,
flagSubnetAdminAddGroup,
flagSubnetAdminClient,
)
},
RunE: func(cmd *cobra.Command, _ []string) error {
return manageSubnetAdmins(cmd, false)
},
}
// command to remove subnet admin.
var cmdSubnetAdminRemove = &cobra.Command{
Use: "remove",
Short: "Remove admin of the NeoFS subnet.",
PreRun: func(cmd *cobra.Command, _ []string) {
viperBindFlags(cmd,
flagSubnetAdminClient,
)
},
RunE: func(cmd *cobra.Command, _ []string) error {
return manageSubnetAdmins(cmd, true)
},
}
// cmdSubnetClient flags.
const (
// ID of the subnet to be managed
flagSubnetClientSubnet = flagSubnet
// client's NeoFS ID
flagSubnetClientID = flagSubnetAdminClient
// ID of the subnet client group
flagSubnetClientGroup = flagSubnetGroup
)
// command to manage subnet clients.
var cmdSubnetClient = &cobra.Command{
Use: "client",
Short: "Manage clients of the NeoFS subnet.",
PreRun: func(cmd *cobra.Command, _ []string) {
viperBindFlags(cmd,
flagSubnetClientSubnet,
flagSubnetClientID,
flagSubnetClientGroup,
)
},
}
// common executor cmdSubnetClientAdd and cmdSubnetClientRemove commands.
func manageSubnetClients(cmd *cobra.Command, rm bool) error {
// read private key
var key keys.PrivateKey
err := readSubnetKey(&key)
if err != nil {
return fmt.Errorf("read private key: %w", err)
}
// read ID and encode it
var id subnetid.ID
err = id.UnmarshalText([]byte(viper.GetString(flagSubnetClientSubnet)))
if err != nil {
return fmt.Errorf("decode ID text: %w", err)
}
if subnetid.IsZero(id) {
return errZeroSubnet
}
binID, err := id.Marshal()
if err != nil {
return fmt.Errorf("marshal subnet ID: %w", err)
}
// read client ID and encode it
var clientID owner.ID
err = clientID.Parse(viper.GetString(flagSubnetClientID))
if err != nil {
return fmt.Errorf("decode client ID text: %w", err)
}
binClientID, err := clientID.Marshal()
if err != nil {
return fmt.Errorf("marshal client ID: %w", err)
}
var prm morphsubnet.ManageClientsPrm
if viper.GetBool(flagSubnetAdminClient) {
// read group ID and encode it
var groupID internal.SubnetClientGroupID
err = groupID.UnmarshalText([]byte(viper.GetString(flagSubnetAdminAddGroup)))
if err != nil {
return fmt.Errorf("decode group ID text: %w", err)
}
binGroupID, err := groupID.Marshal()
if err != nil {
return fmt.Errorf("marshal group ID: %w", err)
}
prm.SetGroup(binGroupID)
}
prm.SetSubnet(binID)
prm.SetClient(binClientID)
if rm {
prm.SetRemove()
}
// initialize morph subnet client
var cSubnet morphsubnet.Client
err = initSubnetClient(&cSubnet, &key)
if err != nil {
return fmt.Errorf("init subnet client: %w", err)
}
_, err = cSubnet.ManageClients(prm)
if err != nil {
return fmt.Errorf("morph call: %w", err)
}
var op string
if rm {
op = "Remove"
} else {
op = "Add"
}
cmd.Printf("%s client request sent successfully.\n", op)
return nil
}
// command to add subnet client.
var cmdSubnetClientAdd = &cobra.Command{
Use: "add",
Short: "Add client to the NeoFS subnet.",
RunE: func(cmd *cobra.Command, _ []string) error {
return manageSubnetClients(cmd, false)
},
}
// command to remove subnet client.
var cmdSubnetClientRemove = &cobra.Command{
Use: "remove",
Short: "Remove client of the NeoFS subnet.",
RunE: func(cmd *cobra.Command, _ []string) error {
return manageSubnetClients(cmd, true)
},
}
// registers flags and binds sub-commands for subnet commands.
func init() {
// get subnet flags
cmdSubnetGet.Flags().String(flagSubnetGetID, "", "ID of the subnet to read")
_ = cmdSubnetAdminAdd.MarkFlagRequired(flagSubnetGetID)
// remove subnet flags
cmdSubnetRemove.Flags().String(flagSubnetRemoveID, "", "ID of the subnet to remove")
_ = cmdSubnetRemove.MarkFlagRequired(flagSubnetRemoveID)
// subnet administer flags
adminFlags := cmdSubnetAdmin.Flags()
adminFlags.String(flagSubnetAdminSubnet, "", "ID of the subnet to manage administrators")
_ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetAdminSubnet)
adminFlags.String(flagSubnetAdminID, "", "Hex-encoded public key of the admin")
_ = cmdSubnetAdmin.MarkFlagRequired(flagSubnetAdminID)
// add admin flags
cmdSubnetAdminAddFlags := cmdSubnetAdminAdd.Flags()
cmdSubnetAdminAddFlags.String(flagSubnetAdminAddGroup, "", fmt.Sprintf(
"Client group ID in text format (needed with --%s only)", flagSubnetAdminClient))
cmdSubnetAdminAddFlags.Bool(flagSubnetAdminClient, false, "Add client admin instead of node one")
// remove admin flags
cmdSubnetAdminRemoveFlags := cmdSubnetAdminRemove.Flags()
cmdSubnetAdminRemoveFlags.Bool(flagSubnetAdminClient, false, "Remove client admin instead of node one")
// client managements flags
clientFlags := cmdSubnetClient.Flags()
clientFlags.String(flagSubnetClientSubnet, "", "ID of the subnet to be managed")
_ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientSubnet)
clientFlags.String(flagSubnetClientGroup, "", "ID of the client group to work with")
_ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientGroup)
clientFlags.String(flagSubnetClientID, "", "Client's user ID in NeoFS system in text format")
_ = cmdSubnetClient.MarkFlagRequired(flagSubnetClientID)
// add all admin managing commands to corresponding command section
cmdSubnetAdmin.AddCommand(
cmdSubnetAdminAdd,
cmdSubnetAdminRemove,
)
// add all client managing commands to corresponding command section
cmdSubnetClient.AddCommand(
cmdSubnetClientAdd,
cmdSubnetClientRemove,
)
// subnet global flags
cmdSubnetFlags := cmdSubnet.Flags()
cmdSubnetFlags.StringP(flagSubnetEndpoint, "r", "", "N3 RPC node endpoint")
_ = cmdSubnet.MarkFlagRequired(flagSubnetEndpoint)
cmdSubnetFlags.StringP(flagSubnetKey, "k", "", "Path to file with private key")
_ = cmdSubnet.MarkFlagRequired(flagSubnetKey)
cmdSubnetFlags.StringP(flagSubnetContract, "a", "", "Subnet contract address (string LE)")
_ = cmdSubnet.MarkFlagRequired(flagSubnetContract)
cmdSubnetFlags.Bool(flagSubnetNonNotary, false, "Flag to work in non-notary environment")
// add all subnet commands to corresponding command section
cmdSubnet.AddCommand(
cmdSubnetCreate,
cmdSubnetRemove,
cmdSubnetGet,
cmdSubnetAdmin,
cmdSubnetClient,
)
}

View file

@ -0,0 +1,87 @@
package morphsubnet
import "github.com/nspcc-dev/neofs-node/pkg/morph/client"
// ManageAdminsPrm groups parameters of administer methods of Subnet contract.
//
// Zero value adds node admin. Subnet, key and group must be specified via setters.
type ManageAdminsPrm struct {
// remove or add admin
rm bool
// client or node admin
client bool
subnet []byte
admin []byte
group []byte
}
// SetRemove marks admin to be removed. By default, admin is added.
func (x *ManageAdminsPrm) SetRemove() {
x.rm = true
}
// SetClient switches to client admin. By default, node admin is modified.
func (x *ManageAdminsPrm) SetClient() {
x.client = true
}
// SetSubnet sets identifier of the subnet in a binary NeoFS API protocol format.
func (x *ManageAdminsPrm) SetSubnet(id []byte) {
x.subnet = id
}
// SetAdmin sets admin's public key in a binary format.
func (x *ManageAdminsPrm) SetAdmin(key []byte) {
x.admin = key
}
// SetGroup sets identifier of the client group in a binary NeoFS API protocol format.
// Makes sense only for client admins (see ManageAdminsPrm.SetClient).
func (x *ManageAdminsPrm) SetGroup(id []byte) {
x.group = id
}
// ManageAdminsRes groups resulting values of node administer methods of Subnet contract.
type ManageAdminsRes struct{}
// ManageAdmins manages admin list of the NeoFS subnet through Subnet contract calls.
func (x Client) ManageAdmins(prm ManageAdminsPrm) (*ManageAdminsPrm, error) {
var method string
args := make([]interface{}, 1, 3)
args[0] = prm.subnet
if prm.client {
args = append(args, prm.group, prm.admin)
if prm.rm {
method = "removeClientAdmin"
} else {
method = "addClientAdmin"
}
} else {
args = append(args, prm.admin)
if prm.rm {
method = "removeNodeAdmin"
} else {
method = "addNodeAdmin"
}
}
var prmInvoke client.InvokePrm
prmInvoke.SetMethod(method)
prmInvoke.SetArgs(args...)
err := x.client.Invoke(prmInvoke)
if err != nil {
return nil, err
}
return new(ManageAdminsPrm), nil
}

View file

@ -56,3 +56,59 @@ func (x *Client) UserAllowed(prm UserAllowedPrm) (*UserAllowedRes, error) {
result: result,
}, nil
}
// ManageClientsPrm groups parameters of client management in Subnet contract.
//
// Zero value adds subnet client. Subnet, group and client ID must be specified via setters.
type ManageClientsPrm struct {
// remove or add client
rm bool
args [3]interface{}
}
// SetRemove marks client to be removed. By default, client is added.
func (x *ManageClientsPrm) SetRemove() {
x.rm = true
}
// SetSubnet sets identifier of the subnet in a binary NeoFS API protocol format.
func (x *ManageClientsPrm) SetSubnet(id []byte) {
x.args[0] = id
}
// SetGroup sets identifier of the client group in a binary NeoFS API protocol format.
func (x *ManageClientsPrm) SetGroup(id []byte) {
x.args[1] = id
}
// SetClient sets client's user ID in a binary NeoFS API protocol format.
func (x *ManageClientsPrm) SetClient(id []byte) {
x.args[2] = id
}
// ManageClientsRes groups resulting values of client management methods of Subnet contract.
type ManageClientsRes struct{}
// ManageClients manages client list of the NeoFS subnet through Subnet contract calls.
func (x Client) ManageClients(prm ManageClientsPrm) (*ManageClientsRes, error) {
var method string
if prm.rm {
method = "removeUser"
} else {
method = "addUser"
}
var prmInvoke client.InvokePrm
prmInvoke.SetMethod(method)
prmInvoke.SetArgs(prm.args[:]...)
err := x.client.Invoke(prmInvoke)
if err != nil {
return nil, err
}
return new(ManageClientsRes), nil
}

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.0
// protoc v3.19.1
// source: pkg/services/control/ir/service.proto
package control

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.0
// protoc v3.19.1
// source: pkg/services/control/ir/types.proto
package control

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.0
// protoc v3.19.1
// source: pkg/services/control/service.proto
package control

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.27.1
// protoc v3.18.0
// protoc v3.19.1
// source: pkg/services/control/types.proto
package control