diff --git a/cmd/frostfs-adm/internal/modules/morph/frostfsid.go b/cmd/frostfs-adm/internal/modules/morph/frostfsid.go new file mode 100644 index 00000000..79ff0cdd --- /dev/null +++ b/cmd/frostfs-adm/internal/modules/morph/frostfsid.go @@ -0,0 +1,327 @@ +package morph + +import ( + "fmt" + "sort" + + frostfsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + "github.com/nspcc-dev/neo-go/pkg/encoding/address" + "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/management" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + namespaceFlag = "namespace" + subjectNameFlag = "subject-name" + subjectKeyFlag = "subject-key" + includeNamesFlag = "include-names" + groupNameFlag = "group-name" +) + +var ( + frostfsidCmd = &cobra.Command{ + Use: "frostfsid", + Short: "Section for frostfsid interactions commands", + } + + frostfsidCreateNamespaceCmd = &cobra.Command{ + Use: "create-namespace", + Short: "Create new namespace in frostfsid contract", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + Run: frostfsidCreateNamespace, + } + + frostfsidListNamespacesCmd = &cobra.Command{ + Use: "list-namespaces", + Short: "List all namespaces in frostfsid", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + Run: frostfsidListNamespaces, + } + + frostfsidCreateSubjectCmd = &cobra.Command{ + Use: "create-subject", + Short: "Create subject in frostfsid contract", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + Run: frostfsidCreateSubject, + } + + frostfsidDeleteSubjectCmd = &cobra.Command{ + Use: "delete-subject", + Short: "Delete subject from frostfsid contract", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + Run: frostfsidDeleteSubject, + } + + frostfsidListSubjectsCmd = &cobra.Command{ + Use: "list-subjects", + Short: "List subjects in namespace", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + Run: frostfsidListSubjects, + } + + frostfsidCreateGroupCmd = &cobra.Command{ + Use: "create-group", + Short: "Create group in frostfsid contract", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + Run: frostfsidCreateGroup, + } + + frostfsidDeleteGroupCmd = &cobra.Command{ + Use: "delete-group", + Short: "Delete group from frostfsid contract", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + Run: frostfsidDeleteGroup, + } + + frostfsidListGroupsCmd = &cobra.Command{ + Use: "list-groups", + Short: "List groups in namespace", + PreRun: func(cmd *cobra.Command, _ []string) { + _ = viper.BindPFlag(alphabetWalletsFlag, cmd.Flags().Lookup(alphabetWalletsFlag)) + _ = viper.BindPFlag(endpointFlag, cmd.Flags().Lookup(endpointFlag)) + }, + Run: frostfsidListGroups, + } +) + +func initFrostfsIDCreateNamespaceCmd() { + frostfsidCmd.AddCommand(frostfsidCreateNamespaceCmd) + frostfsidCreateNamespaceCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + frostfsidCreateNamespaceCmd.Flags().String(namespaceFlag, "", "Namespace name to create") +} + +func initFrostfsIDListNamespacesCmd() { + frostfsidCmd.AddCommand(frostfsidListNamespacesCmd) + frostfsidListNamespacesCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) +} + +func initFrostfsIDCreateSubjectCmd() { + frostfsidCmd.AddCommand(frostfsidCreateSubjectCmd) + frostfsidCreateSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + frostfsidCreateSubjectCmd.Flags().String(namespaceFlag, "", "Namespace where create subject") + frostfsidCreateSubjectCmd.Flags().String(subjectNameFlag, "", "Subject name, must be unique in namespace") + frostfsidCreateSubjectCmd.Flags().String(subjectKeyFlag, "", "Subject hex-encoded public key") +} + +func initFrostfsIDDeleteSubjectCmd() { + frostfsidCmd.AddCommand(frostfsidDeleteSubjectCmd) + frostfsidDeleteSubjectCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + frostfsidDeleteSubjectCmd.Flags().String(subjectAddressFlag, "", "Subject address") +} + +func initFrostfsIDListSubjectsCmd() { + frostfsidCmd.AddCommand(frostfsidListSubjectsCmd) + frostfsidListSubjectsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + frostfsidListSubjectsCmd.Flags().String(namespaceFlag, "", "Namespace to list subjects") + frostfsidListSubjectsCmd.Flags().Bool(includeNamesFlag, false, "Whether include subject name (require additional requests)") +} + +func initFrostfsIDCreateGroupCmd() { + frostfsidCmd.AddCommand(frostfsidCreateGroupCmd) + frostfsidCreateGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + frostfsidCreateGroupCmd.Flags().String(namespaceFlag, "", "Namespace where create group") + frostfsidCreateGroupCmd.Flags().String(groupNameFlag, "", "Group name, must be unique in namespace") +} + +func initFrostfsIDDeleteGroupCmd() { + frostfsidCmd.AddCommand(frostfsidDeleteGroupCmd) + frostfsidDeleteGroupCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + frostfsidDeleteGroupCmd.Flags().String(namespaceFlag, "", "Namespace to delete group") + frostfsidDeleteGroupCmd.Flags().Int64(groupIDFlag, 0, "Group id") +} + +func initFrostfsIDListGroupsCmd() { + frostfsidCmd.AddCommand(frostfsidListGroupsCmd) + frostfsidListGroupsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc) + frostfsidListGroupsCmd.Flags().String(namespaceFlag, "", "Namespace to list groups") +} + +type ffsidMethodArgs struct { + name string + args []any +} + +func frostfsidCreateNamespace(cmd *cobra.Command, _ []string) { + ns, _ := cmd.Flags().GetString(namespaceFlag) + + err := sendFrostfsIDTx(cmd, ffsidMethodArgs{name: "createNamespace", args: []any{ns}}) + commonCmd.ExitOnErr(cmd, "processing error: %w", err) +} + +func frostfsidListNamespaces(cmd *cobra.Command, _ []string) { + ffsid, err := frostfsIDClient(cmd) + commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err) + + namespaces, err := ffsid.ListNamespaces() + commonCmd.ExitOnErr(cmd, "list namespaces: %w", err) + + sort.Slice(namespaces, func(i, j int) bool { return namespaces[i].Name < namespaces[j].Name }) + + for _, namespace := range namespaces { + cmd.Printf("%q\n", namespace.Name) + } +} + +func frostfsidCreateSubject(cmd *cobra.Command, _ []string) { + ns, _ := cmd.Flags().GetString(namespaceFlag) + subjName, _ := cmd.Flags().GetString(subjectNameFlag) + subjKey := getFrostfsIDSubjectKey(cmd) + + args := []ffsidMethodArgs{ + {name: "createSubject", args: []any{ns, subjKey.Bytes()}}, + } + + if subjName != "" { + args = append(args, ffsidMethodArgs{name: "setSubjectName", args: []any{subjKey.GetScriptHash(), subjName}}) + } + + err := sendFrostfsIDTx(cmd, args...) + commonCmd.ExitOnErr(cmd, "processing error: %w", err) +} + +func frostfsidDeleteSubject(cmd *cobra.Command, _ []string) { + subjectAddress := getFrostfsIDSubjectAddress(cmd) + + err := sendFrostfsIDTx(cmd, ffsidMethodArgs{name: "deleteSubject", args: []any{subjectAddress}}) + commonCmd.ExitOnErr(cmd, "delete subject error: %w", err) +} + +func frostfsidListSubjects(cmd *cobra.Command, _ []string) { + ns, _ := cmd.Flags().GetString(namespaceFlag) + includeNames, _ := cmd.Flags().GetBool(includeNamesFlag) + + ffsid, err := frostfsIDClient(cmd) + commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err) + + subAddresses, err := ffsid.ListNamespaceSubjects(ns) + commonCmd.ExitOnErr(cmd, "list subjects: %w", err) + + sort.Slice(subAddresses, func(i, j int) bool { return subAddresses[i].Less(subAddresses[j]) }) + + for _, addr := range subAddresses { + if !includeNames { + cmd.Println(address.Uint160ToString(addr)) + continue + } + + subj, err := ffsid.GetSubject(addr) + commonCmd.ExitOnErr(cmd, "get subject: %w", err) + + cmd.Printf("%s (%s)\n", address.Uint160ToString(addr), subj.Name) + } +} + +func frostfsidCreateGroup(cmd *cobra.Command, _ []string) { + ns, _ := cmd.Flags().GetString(namespaceFlag) + groupName, _ := cmd.Flags().GetString(groupNameFlag) + + ffsid, err := frostfsIDClient(cmd) + commonCmd.ExitOnErr(cmd, "init contract client: %w", err) + + groupID, err := ffsid.ParseGroupID(ffsid.Wait(ffsid.CreateGroup(ns, groupName))) + commonCmd.ExitOnErr(cmd, "create group: %w", err) + + cmd.Printf("group '%s' created with id: %d\n", groupName, groupID) +} + +func frostfsidDeleteGroup(cmd *cobra.Command, _ []string) { + ns, _ := cmd.Flags().GetString(namespaceFlag) + groupID, _ := cmd.Flags().GetInt64(groupIDFlag) + + ffsid, err := frostfsIDClient(cmd) + commonCmd.ExitOnErr(cmd, "init contract client: %w", err) + + _, err = ffsid.Wait(ffsid.DeleteGroup(ns, groupID)) + commonCmd.ExitOnErr(cmd, "delete group error: %w", err) +} + +func frostfsidListGroups(cmd *cobra.Command, _ []string) { + ns, _ := cmd.Flags().GetString(namespaceFlag) + + ffsid, err := frostfsIDClient(cmd) + commonCmd.ExitOnErr(cmd, "init contract invoker: %w", err) + + groups, err := ffsid.ListGroups(ns) + commonCmd.ExitOnErr(cmd, "list groups: %w", err) + + sort.Slice(groups, func(i, j int) bool { return groups[i].Name < groups[j].Name }) + + for _, group := range groups { + cmd.Printf("%q (%d)\n", group.Name, group.ID) + } +} + +func sendFrostfsIDTx(cmd *cobra.Command, methodArgs ...ffsidMethodArgs) error { + wCtx, err := newInitializeContext(cmd, viper.GetViper()) + if err != nil { + return fmt.Errorf("can't to initialize context: %w", err) + } + + r := management.NewReader(wCtx.ReadOnlyInvoker) + cs, err := r.GetContractByID(1) + if err != nil { + return fmt.Errorf("can't get NNS contract info: %w", err) + } + + ffsidHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, domainOf(frostfsIDContract)) + if err != nil { + return fmt.Errorf("can't get proxy contract hash: %w", err) + } + + bw := io.NewBufBinWriter() + for _, method := range methodArgs { + emit.AppCall(bw.BinWriter, ffsidHash, method.name, callflag.All, method.args...) + } + + if err = wCtx.sendConsensusTx(bw.Bytes()); err != nil { + return err + } + + return wCtx.awaitTx() +} + +func frostfsIDClient(cmd *cobra.Command) (*frostfsidclient.Client, error) { + wCtx, err := newInitializeContext(cmd, viper.GetViper()) + if err != nil { + return nil, fmt.Errorf("can't to initialize context: %w", err) + } + + r := management.NewReader(wCtx.ReadOnlyInvoker) + cs, err := r.GetContractByID(1) + if err != nil { + return nil, fmt.Errorf("can't get NNS contract info: %w", err) + } + + ffsidHash, err := nnsResolveHash(wCtx.ReadOnlyInvoker, cs.Hash, domainOf(frostfsIDContract)) + if err != nil { + return nil, fmt.Errorf("can't get proxy contract hash: %w", err) + } + + return frostfsidclient.NewSimple(wCtx.CommitteeAct, ffsidHash), nil +} diff --git a/cmd/frostfs-adm/internal/modules/morph/frostfsid_util.go b/cmd/frostfs-adm/internal/modules/morph/frostfsid_util.go index cfe3c1ee..529d98dd 100644 --- a/cmd/frostfs-adm/internal/modules/morph/frostfsid_util.go +++ b/cmd/frostfs-adm/internal/modules/morph/frostfsid_util.go @@ -3,9 +3,11 @@ package morph import ( "fmt" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -31,3 +33,28 @@ func getFrostfsIDAdmin(v *viper.Viper) (util.Uint160, bool, error) { } return util.Uint160{}, true, fmt.Errorf("frostfsid: admin is invalid: '%s'", admin) } + +func getFrostfsIDSubjectKey(cmd *cobra.Command) *keys.PublicKey { + subjKeyHex, _ := cmd.Flags().GetString(subjectKeyFlag) + subjKey, err := keys.NewPublicKeyFromString(subjKeyHex) + commonCmd.ExitOnErr(cmd, "invalid subject key: %w", err) + return subjKey +} + +//func decodeFrostfsIDNamespaces(resStack []stackitem.Item) ([]string, error) { +// var res []string +// +// if len(resStack) == 0 { +// return res, nil +// } +// +// if len(resStack) > 0 { +// nodes, err := decodeNodeList(resStack[0]) +// if err != nil { +// return nil, err +// } +// +// nm.SetNodes(nodes) +// } +// +//} diff --git a/cmd/frostfs-adm/internal/modules/morph/root.go b/cmd/frostfs-adm/internal/modules/morph/root.go index 4176a647..6db26217 100644 --- a/cmd/frostfs-adm/internal/modules/morph/root.go +++ b/cmd/frostfs-adm/internal/modules/morph/root.go @@ -293,6 +293,16 @@ func init() { initProxyAddAccount() initProxyRemoveAccount() + + RootCmd.AddCommand(frostfsidCmd) + initFrostfsIDCreateNamespaceCmd() + initFrostfsIDListNamespacesCmd() + initFrostfsIDCreateSubjectCmd() + initFrostfsIDDeleteSubjectCmd() + initFrostfsIDListSubjectsCmd() + initFrostfsIDCreateGroupCmd() + initFrostfsIDDeleteGroupCmd() + initFrostfsIDListGroupsCmd() } func initProxyAddAccount() { diff --git a/go.mod b/go.mod index ddf587ad..8b48cd78 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20231031104748-498877e378fd - git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958 + git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20240115082915-f2a82aa635aa git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65 git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20231122162120-56debcfa569e git.frostfs.info/TrueCloudLab/hrw v1.2.1 diff --git a/go.sum b/go.sum index 9be0e55c..11b208d3 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20231031104748-498877e378fd h1:MxiIXu4vUATX9bNYFnM1dXAYkx5dnPOGCMzwNNXJEvM= git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20231031104748-498877e378fd/go.mod h1:uY0AYmCznjZdghDnAk7THFIe1Vlg531IxUcus7ZfUJI= -git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958 h1:X9yPizADIhD3K/gdKVCthlAnf9aQ3UJJGnZgIwwixRQ= -git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958/go.mod h1:rQWdsG18NaiFvkJpMguJev913KD/yleHaniRBkUyt0o= +git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20240115082915-f2a82aa635aa h1:uuN90s74idThHXi/jF/IlTdFimOIs+IBU44rdTE/Zdo= +git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20240115082915-f2a82aa635aa/go.mod h1:F/fe1OoIDKr5Bz99q4sriuHDuf3aZefZy9ZsCqEtgxc= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk= git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU= git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20231101111734-b3ad3335ff65 h1:PaZ8GpnUoXxUoNsc1qp36bT2u7FU+neU4Jn9cl8AWqI=