[#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().StringP(endpointFlag, "r", "", "N3 RPC node endpoint")
refillGasCmd.Flags().String(storageWalletFlag, "", "path to storage node wallet") refillGasCmd.Flags().String(storageWalletFlag, "", "path to storage node wallet")
refillGasCmd.Flags().String(refillGasAmountFlag, "", "additional amount of GAS to transfer") 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, result: result,
}, nil }, 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. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.27.1 // protoc-gen-go v1.27.1
// protoc v3.18.0 // protoc v3.19.1
// source: pkg/services/control/ir/service.proto // source: pkg/services/control/ir/service.proto
package control package control

View file

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

View file

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

View file

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