[#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:
parent
70f17dc778
commit
e8f8e58e90
11 changed files with 936 additions and 0 deletions
65
cmd/neofs-adm/internal/modules/morph/internal/types.go
Normal file
65
cmd/neofs-adm/internal/modules/morph/internal/types.go
Normal 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
|
||||||
|
}
|
BIN
cmd/neofs-adm/internal/modules/morph/internal/types.pb.go
generated
Normal file
BIN
cmd/neofs-adm/internal/modules/morph/internal/types.pb.go
generated
Normal file
Binary file not shown.
15
cmd/neofs-adm/internal/modules/morph/internal/types.proto
Normal file
15
cmd/neofs-adm/internal/modules/morph/internal/types.proto
Normal 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"];
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
711
cmd/neofs-adm/internal/modules/morph/subnet.go
Normal file
711
cmd/neofs-adm/internal/modules/morph/subnet.go
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
87
pkg/morph/client/subnet/admin.go
Normal file
87
pkg/morph/client/subnet/admin.go
Normal 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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
BIN
pkg/services/control/ir/service.pb.go
generated
BIN
pkg/services/control/ir/service.pb.go
generated
Binary file not shown.
BIN
pkg/services/control/ir/types.pb.go
generated
BIN
pkg/services/control/ir/types.pb.go
generated
Binary file not shown.
BIN
pkg/services/control/service.pb.go
generated
BIN
pkg/services/control/service.pb.go
generated
Binary file not shown.
BIN
pkg/services/control/types.pb.go
generated
BIN
pkg/services/control/types.pb.go
generated
Binary file not shown.
Loading…
Reference in a new issue