adm/ape: Adopt policy reader #1607

Closed
dkirillov wants to merge 4 commits from dkirillov/frostfs-node:feature/adopt_policy_reader into master
11 changed files with 708 additions and 2 deletions

View file

@ -0,0 +1,96 @@
package chains
import (
"math/big"
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/raw/output"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
fyrchik marked this conversation as resolved Outdated

I would like to avoid using this package in this repo.
Ideally all node <-> neo-go communication is done via autogenerated code or helpers from the frostfs-contract.
We already have multiple commands using different mechanisms (actors vs common client from the frostfsid), let's not add yet another one.

Specifically, I would like to make this a thing #1035, so connection handling should be decoupled from the argument parsing.

I would like to avoid using this package in this repo. Ideally all node <-> neo-go communication is done via autogenerated code or helpers from the `frostfs-contract`. We already have multiple commands using different mechanisms (actors vs common client from the frostfsid), let's not add yet another one. Specifically, I would like to make this a thing #1035, so connection handling should be decoupled from the argument parsing.
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/spf13/cobra"
)
var listContainerCmd = &cobra.Command{
Use: "list-container",

What is the difference with list-rule-chains command?

What is the difference with `list-rule-chains` command?

It seems this runs listing under chains subcommand
ape chains list-container (although it can be just ape chains list and flags can define targets) instead of ape list-rule-chains.

It seems this runs listing under `chains` subcommand `ape chains list-container` (although it can be just `ape chains list` and flags can define targets) instead of `ape list-rule-chains`.

This command is aimed to list all container related chains. Not only for container target but also for namespace target (in which container is location)

This command is aimed to list all container related chains. Not only for `container` target but also for `namespace` target (in which container is location)
Short: "List container related (namespace) policies",

I would use different naming: namespace somewhere in the command name and --container as an optional argument (aka filter).
Current implementation looks too specific.

I would use different naming: `namespace` somewhere in the command name and `--container` as an optional argument (aka filter). Current implementation looks too specific.

Current implementation looks too specific.

Indeed it is. This command is aimed to get all container related policies. In container target and in namespace target.

The similar command for user that get all user related (affected) policies (from namespace, group, user targets)

I'm not sure if using "--container as optional argument (aka filter)" is appropriate here

> Current implementation looks too specific. Indeed it is. This command is aimed to get all **container** related policies. In `container` target and in `namespace` target. The similar command for user that get all user related (affected) policies (from `namespace`, `group`, `user` targets) I'm not sure if using "`--container` as optional argument (aka filter)" is appropriate here
Long: "List container related (namespace) policies along with filtering by service (s3/storage)",
Example: `chains list-container -r http://localhost:40332 list --container 7h7NcXcF6k6b1yidqEHc1jkyXUm1MfUDrrTuHAefhiDe
chains list-container -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list --container 7h7NcXcF6k6b1yidqEHc1jkyXUm1MfUDrrTuHAefhiDe --namespace test`,
RunE: runListContainerCmd,
}

We use BindPFlag to allow providing command values via configuration.
I doubt it is useful for any of these 4 flags.
(containerFlag is required, to this bind seems definitely useless).

We use `BindPFlag` to allow providing command values via configuration. I doubt it is useful for any of these 4 flags. (`containerFlag` is required, to this bind seems definitely useless).
const (
containerFlag = "container"
)
func initListContainerCmd() {
listContainerCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
listContainerCmd.Flags().String(apeCmd.ChainNameFlag, "", apeCmd.ChainNameFlagDesc)
listContainerCmd.Flags().String(containerFlag, "", "Container id or bucket name in nns (if name is provided than 'namespace' should be set too)")
listContainerCmd.Flags().String(namespaceFlag, "", "Namespace where container name will be looked up")
listContainerCmd.Flags().Bool(decodeChainFlag, false, "Use this flag to decode chain")
listContainerCmd.Flags().Bool(decodeIDFlag, false, "Use this flag to additionally decode chain id (without --decode-chain no take effect)")
_ = listContainerCmd.MarkFlagRequired(containerFlag)
}
func runListContainerCmd(cmd *cobra.Command, _ []string) error {
chainName := parseChainName(cmd)
namespace := parseNamespace(cmd)
inv, policyHash, _ := initReaders(cmd)
cnrID := parseContainer(cmd, inv, namespace)
printContainer(cmd, namespace, cnrID)
res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, big.NewInt(int64(policycontract.Namespace)), namespace, string(chainName))
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
decodeChain, _ := cmd.Flags().GetBool(decodeChainFlag)

Other ape-related commands have root to empty string replacement:

Other ape-related commands have `root` to empty string replacement: https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/c98357606b4e387f7a8063331438dccfb24d255e/cmd/frostfs-adm/internal/modules/morph/ape/ape_util.go#L27
decodeID, _ := cmd.Flags().GetBool(decodeIDFlag)
cmd.Printf("\nnamespace policies: %d\n", len(res))
err = output.PrintChains(cmd, res, decodeChain, decodeID)
commonCmd.ExitOnErr(cmd, "can't print chains: %w", err)
res, err = commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, big.NewInt(int64(policycontract.Container)), cnrID.EncodeToString(), string(chainName))
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
cmd.Printf("\ncontainer policies: %d\n", len(res))
return output.PrintChains(cmd, res, decodeChain, decodeID)
}
func printContainer(cmd *cobra.Command, namespace string, cnrID cid.ID) {
cmd.Println("container:")
cmd.Printf("\tnamespace: '%s'\n", namespace)
cmd.Printf("\tCID: '%s'\n", cnrID.EncodeToString())
}
func parseContainer(cmd *cobra.Command, inv *invoker.Invoker, namespace string) cid.ID {
fyrchik marked this conversation as resolved Outdated

Why do we have %s for namespace message, but no CID for this one?

Why do we have `%s` for namespace message, but no CID for this one?
containerName, _ := cmd.Flags().GetString(containerFlag)
var cnrID cid.ID
if err := cnrID.DecodeString(containerName); err == nil {
return cnrID
fyrchik marked this conversation as resolved Outdated

It is used only once and in this package, why is it public?

It is used only once and in this package, why is it public?

Why have you decided to move it to helper package instead of making it private?

Why have you decided to move it to `helper` package instead of making it private?

To make it similar to NNSResolveHash. Probably it can be helpful in some other places

To make it similar to `NNSResolveHash`. Probably it can be helpful in some other places

Probably can, probably not.
To me it complicates things now.

Probably can, probably not. To me it complicates things now.
}
var domain container.Domain
domain.SetName(containerName)
if namespace != "" {
domain.SetZone(namespace + ".ns")
}
nnsCs, err := helper.GetContractByID(management.NewReader(inv), 1)
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
cnrID, err = helper.NNSResolveContainerDomain(inv, nnsCs.Hash, domain.Name()+"."+domain.Zone())
commonCmd.ExitOnErr(cmd, "can't resolve container id: %w", err)
return cnrID
}

View file

@ -0,0 +1,203 @@
package chains
import (
"encoding/hex"
"errors"
"fmt"
"math/big"
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
ffsidclient "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/raw/output"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
neoflags "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var listUserCmd = &cobra.Command{
Use: "list-user",
Short: "List user related (groups/namespace) policies",
Long: "List user related (groups/namespace) policies along with filtering by service (s3/storage)",
Example: `policy-reader list-user -r http://localhost:40332 list --user NiGqBpUdMvAC68SxUeyYwVPyBCsqzNuof
policy-reader list-user -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list --user NiGqBpUdMvAC68SxUeyYwVPyBCsqzNuofL --service s3`,
RunE: runListCmd,
}
var errUnknownChainNameType = errors.New("unknown chain-name")
const (
userFlag = "user"
namespaceFlag = "namespace"
decodeChainFlag = "decode-chain"
decodeIDFlag = "decode-id"
)
const methodIteratorChainsByPrefix = "iteratorChainsByPrefix"
func initListUserCmd() {
listUserCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
listUserCmd.Flags().String(apeCmd.ChainNameFlag, "", apeCmd.ChainNameFlagDesc)
listUserCmd.Flags().String(userFlag, "", "User address or name in frostfsid contract (if name is provided than 'namespace' should be set too)")
listUserCmd.Flags().String(namespaceFlag, "", "Namespace where user name will be looked up")
listUserCmd.Flags().Bool(decodeChainFlag, false, "Use this flag to decode chain")
listUserCmd.Flags().Bool(decodeIDFlag, false, "Use this flag to additionally decode chain id (without --decode-chain no take effect)")
_ = listUserCmd.MarkFlagRequired(userFlag)
}
func runListCmd(cmd *cobra.Command, _ []string) error {
chainName := parseChainName(cmd)
namespace := parseNamespace(cmd)
inv, policyHash, ffsidCli := initReaders(cmd)
user, _ := cmd.Flags().GetString(userFlag)
subj, err := resolveSubject(ffsidCli, namespace, user)
commonCmd.ExitOnErr(cmd, "can't resolve frostfsid subject: %w", err)
printSubject(cmd, subj)
res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, big.NewInt(int64(policycontract.Namespace)), subj.Namespace, string(chainName))
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
decodeChain, _ := cmd.Flags().GetBool(decodeChainFlag)
decodeID, _ := cmd.Flags().GetBool(decodeIDFlag)
cmd.Printf("\nnamespace policies: %d\n", len(res))
err = output.PrintChains(cmd, res, decodeChain, decodeID)
commonCmd.ExitOnErr(cmd, "can't print chains: %w", err)
userEntity := big.NewInt(int64(policycontract.User))
userEntityName := fmt.Sprintf("%s:%s", subj.Namespace, subj.PrimaryKey.Address())
res, err = commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, userEntity, userEntityName, string(chainName))
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
cmd.Printf("\nuser policies: %d\n", len(res))
err = output.PrintChains(cmd, res, decodeChain, decodeID)
commonCmd.ExitOnErr(cmd, "can't print chains: %w", err)
cmd.Printf("\ngroup policies: %d\n", len(subj.Groups))
groupEntity := big.NewInt(int64(policycontract.Group))
for _, group := range subj.Groups {
groupEntityName := fmt.Sprintf("%s:%d", group.Namespace, group.ID)
res, err = commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, groupEntity, groupEntityName, string(chainName))
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
cmd.Printf("user group '%s' (id: %d) policies: %d\n", group.Name, group.ID, len(res))
err = output.PrintChains(cmd, res, decodeChain, decodeID)
commonCmd.ExitOnErr(cmd, "can't print chains: %w", err)
}
return nil
}
func resolveSubject(ffsid *ffsidclient.Client, namespace, userName string) (*ffsidclient.SubjectExtended, error) {
if userHash, err := neoflags.ParseAddress(userName); err == nil {
subj, err := ffsid.GetSubject(userHash)
if err != nil {
return nil, err
}
return ffsid.GetSubjectExtended(subj.PrimaryKey.GetScriptHash())
}
subj, err := ffsid.GetSubjectByName(namespace, userName)
if err != nil {
return nil, err
}
return ffsid.GetSubjectExtended(subj.PrimaryKey.GetScriptHash())
}
func parseChainName(cmd *cobra.Command) apechain.Name {
chainName, _ := cmd.Flags().GetString(apeCmd.ChainNameFlag)
switch chainName {
case "":
return ""
fyrchik marked this conversation as resolved Outdated

To my complete surprise, there is an old and undone issue: https://github.com/spf13/pflag/issues/236 which could be helpful here.

To my complete surprise, there is an old and undone issue: https://github.com/spf13/pflag/issues/236 which could be helpful here.
case "s3":
return apechain.S3
case "ingress":
return apechain.Ingress
}
commonCmd.ExitOnErr(cmd, "can't parse chain-name: %w", errUnknownChainNameType)
panic("unreachable")
}
func parseNamespace(cmd *cobra.Command) string {
namespace, _ := cmd.Flags().GetString(namespaceFlag)
if namespace == "root" {
fyrchik marked this conversation as resolved Outdated

It accepts SubjectExtended but doesn't print AdditionalKeys. It this intended?

It accepts `SubjectExtended` but doesn't print `AdditionalKeys`. It this intended?

Probably this was done (in policy-reader tool) before additional keys starts to be useful to see

Probably this was done (in `policy-reader` tool) before additional keys starts to be useful to see
namespace = ""
}
return namespace
}
func printSubject(cmd *cobra.Command, subj *ffsidclient.SubjectExtended) {
cmd.Println("subject:")
cmd.Printf("\tnamespace: '%s'\n", subj.Namespace)
cmd.Printf("\tname: '%s'\n", subj.Name)
cmd.Printf("\tkey: '%s'\n", hex.EncodeToString(subj.PrimaryKey.Bytes()))
cmd.Printf("\tadditional keys:\n")
for _, key := range subj.AdditionalKeys {
cmd.Printf("\t\t%s\n", hex.EncodeToString(key.Bytes()))
}

We print all other arrays line-by-line, and here we use %v, why so?

We print all other arrays line-by-line, and here we use `%v`, why so?

Because we don't need extra formatting in this case

Because we don't need extra formatting in this case

I mean, why we don't need it?
Having keys line-by-line is easier to read and will correspond to the formatting of other array values.

I mean, why we don't need it? Having keys line-by-line is easier to read and will correspond to the formatting of other array values.
cmd.Printf("\tclaims:\n")
for k, v := range subj.KV {
cmd.Printf("\t\t%s: '%s'\n", k, v)
}
cmd.Printf("\tgroups:\n")
for _, gr := range subj.Groups {
cmd.Printf("\t\t%d: '%s'\n", gr.ID, gr.Name)
}
}
func initReaders(cmd *cobra.Command) (*invoker.Invoker, util.Uint160, *ffsidclient.Client) {
endpoint := viper.GetString(commonflags.EndpointFlag)
rpcCli, err := rpcclient.New(cmd.Context(), endpoint, rpcclient.Options{})
commonCmd.ExitOnErr(cmd, "can't init rpc client: %w", err)
inv := invoker.New(rpcCli, nil)
nnsCs, err := helper.GetContractByID(management.NewReader(inv), 1)
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
policyHashStr, _ := cmd.Flags().GetString(policyHashFlag)
policyHash, err := util.Uint160DecodeStringLE(policyHashStr)
if err != nil {
policyHash, err = helper.NNSResolveHash(inv, nnsCs.Hash, policyHashStr)
commonCmd.ExitOnErr(cmd, "can't resolve NNS policy contract: %w", err)
}
frostfsidHashStr, _ := cmd.Flags().GetString(frostfsidHashFlag)
frostfsidHash, err := util.Uint160DecodeStringLE(policyHashStr)
Review

Is it really policyHashStr?

Is it really `policyHashStr`?
if err != nil {
frostfsidHash, err = helper.NNSResolveHash(inv, nnsCs.Hash, frostfsidHashStr)
commonCmd.ExitOnErr(cmd, "can't resolve NNS frostfsid contract: %w", err)
}
acc, err := wallet.NewAccount()
commonCmd.ExitOnErr(cmd, "can't create new account: %w", err)
ffsidCli, err := ffsidclient.New(rpcCli, acc, frostfsidHash, ffsidclient.Options{})
commonCmd.ExitOnErr(cmd, "can't init frostfsid client: %w", err)
return inv, policyHash, ffsidCli
}

View file

@ -0,0 +1,32 @@
package chains
import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var Cmd = &cobra.Command{
Use: "chains",
Short: "Chain related policy operations",
Long: "Chain related policy operations. Complex scenarios like: list all user chains (including groups, namespaces).",
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
},
}
const (
policyHashFlag = "policy-hash"
frostfsidHashFlag = "frostfsid-hash"
)
func init() {
aarifullin marked this conversation as resolved Outdated

We don't need to pass it. See

We don't need to pass it. [See](https://git.frostfs.info/TrueCloudLab/frostfs-node/src/branch/master/cmd/frostfs-adm/internal/modules/morph/ape/ape_util.go#L64-L65)

These commands aimed to debug policy contract in any environment. In some environment this contract isn't registered in NNS

These commands aimed to debug `policy` contract in any environment. In some environment this contract isn't registered in NNS

Okay. You haven't marked the flag as required but it's not processed when it's empty either.
Could we use searching within NNS as default behavior if its value is empty?

Okay. You haven't marked the flag as required but it's not processed when it's empty either. Could we use searching within NNS as default behavior if its value is empty?
Cmd.PersistentFlags().String(policyHashFlag, "policy.frostfs", "NNS name or script hash of policy contract")
Cmd.PersistentFlags().String(frostfsidHashFlag, "frostfsid.frostfs", "NNS name or script hash of frostfsid contract")
Cmd.AddCommand(listUserCmd)
initListUserCmd()

Every other command may resolve contract hash from NNS.
frostfs-adm may resolve any domain too.
Why this argument exists?

Every other command may resolve contract hash from NNS. `frostfs-adm` may resolve any domain too. Why this argument exists?
https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls/1607#issuecomment-64644
Cmd.AddCommand(listContainerCmd)
initListContainerCmd()
}

View file

@ -0,0 +1,74 @@
package raw
import (
"encoding/base64"
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var listChainNamesCmd = &cobra.Command{
Use: "list-chain-names",
Short: "Invoke 'listChainNames' method",
Long: "Invoke 'listChainNames' method in policy contract and print results to stdout",
Example: `raw -r http://localhost:40332 list-chain-names --kind n --name ''
raw -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list-chain-names --kind c --name 7h7NcXcF6k6b1yidqEHc1jkyXUm1MfUDrrTuHAefhiDe`,
RunE: runListChainNamesCmd,
}
const (
nameFlag = "name"
nameBase64Flag = "name-base64"
)
func initListChainNamesCmd() {
listChainNamesCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
listChainNamesCmd.Flags().String(kindFlag, "n", "Target kind (1-byte) to list (n(namespace)/c(container)/g(group)/u(user)/i(iam)) or its integer representation")
listChainNamesCmd.Flags().String(nameFlag, "", "Target name to list")
listChainNamesCmd.Flags().Bool(nameBase64Flag, false, "Use this flag if you provide name in base64 format")
_ = listChainNamesCmd.MarkFlagRequired(kindFlag)
_ = listChainNamesCmd.MarkFlagRequired(nameFlag)
}
func runListChainNamesCmd(cmd *cobra.Command, _ []string) error {
kind, _ := cmd.Flags().GetString(kindFlag)
entity, err := parseTargetKind(kind)
commonCmd.ExitOnErr(cmd, "can't parse target kind: %w", err)
entityName, err := parseEntityName(cmd)
commonCmd.ExitOnErr(cmd, "can't parse name: %w", err)
inv, policyHash := initPolicyReader(cmd)
res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodListChainNames, entity, entityName)
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
cmd.Printf("%s target chain names: %d\n", kind, len(res))
for _, re := range res {
bytes, err := re.TryBytes()
commonCmd.ExitOnErr(cmd, "can't parse result: %w", err)
cmd.Printf("%s\t(base64: '%s')\n", string(bytes), base64.StdEncoding.EncodeToString(bytes))
}
return nil
}
func parseEntityName(cmd *cobra.Command) ([]byte, error) {
entityNameStr, _ := cmd.Flags().GetString(nameFlag)
var entityName []byte
if viper.GetBool(nameBase64Flag) {
return base64.StdEncoding.DecodeString(entityNameStr)
}
if entityNameStr == "root" {
entityNameStr = ""
}
entityName = []byte(entityNameStr)
return entityName, nil
}

View file

@ -0,0 +1,71 @@
package raw
import (
"encoding/base64"
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/raw/output"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"github.com/spf13/cobra"
)
var listChainsByPrefixCmd = &cobra.Command{
Use: "list-chains-by-prefix",
Short: "Invoke 'iteratorChainsByPrefix' method",
Long: "Invoke 'iteratorChainsByPrefix' method in policy contract and print results to stdout",
Example: `raw -r http://localhost:40332 list-chains-by-prefix --kind n --name ''
raw -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list-chains-by-prefix --kind c --name 7h7NcXcF6k6b1yidqEHc1jkyXUm1MfUDrrTuHAefhiDe`,
RunE: runListChainsByPrefixCmd,
}
const (
prefixFlag = "prefix"
prefixBase64Flag = "prefix-base64"
decodeChainFlag = "decode-chain"
decodeIDFlag = "decode-id"
)
func initListChainsByPrefixCmd() {
listChainsByPrefixCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
listChainsByPrefixCmd.Flags().String(kindFlag, "n", "Target kind (1-byte) to list (n(namespace)/c(container)/g(group)/u(user)/i(iam)) or its integer representation")
listChainsByPrefixCmd.Flags().String(nameFlag, "", "Target name to list")
listChainsByPrefixCmd.Flags().String(prefixFlag, "", "Prefix to list")
listChainsByPrefixCmd.Flags().Bool(prefixBase64Flag, false, "Use this flag if you provide prefix in base64 format")
listChainsByPrefixCmd.Flags().Bool(nameBase64Flag, false, "Use this flag if you provide name in base64 format")
listChainsByPrefixCmd.Flags().Bool(decodeChainFlag, false, "Use this flag to decode chain")
listChainsByPrefixCmd.Flags().Bool(decodeIDFlag, false, "Use this flag to additionally decode chain id (without --decode-chain no take effect)")
_ = listChainsByPrefixCmd.MarkFlagRequired(kindFlag)
_ = listChainsByPrefixCmd.MarkFlagRequired(nameFlag)
}
func runListChainsByPrefixCmd(cmd *cobra.Command, _ []string) error {
kind, _ := cmd.Flags().GetString(kindFlag)
entity, err := parseTargetKind(kind)
commonCmd.ExitOnErr(cmd, "can't parse target kind: %w", err)
entityName, err := parseEntityName(cmd)
commonCmd.ExitOnErr(cmd, "can't parse name: %w", err)
prefixStr, _ := cmd.Flags().GetString(prefixFlag)
prefixBase64, _ := cmd.Flags().GetBool(prefixBase64Flag)
var prefix []byte
if prefixBase64 {
if prefix, err = base64.StdEncoding.DecodeString(prefixStr); err != nil {
return err
}
} else {
prefix = []byte(prefixStr)
}
inv, policyHash := initPolicyReader(cmd)
res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodIteratorChainsByPrefix, entity, entityName, prefix)
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
cmd.Printf("%s target chains names: %d\n", kind, len(res))
decodeChain, _ := cmd.Flags().GetBool(decodeChainFlag)
decodeID, _ := cmd.Flags().GetBool(decodeIDFlag)
return output.PrintChains(cmd, res, decodeChain, decodeID)
}

View file

@ -0,0 +1,100 @@
package raw
import (
"encoding/base64"
"fmt"
"math/big"
"strconv"
"git.frostfs.info/TrueCloudLab/frostfs-contract/commonclient"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var listTargetsCmd = &cobra.Command{
Use: "list-targets",
Short: "Invoke 'listTargets' method",
Long: "Invoke 'listTargets' method in policy contract and print results to stdout",
Example: `raw -r http://localhost:40332 list-targets
raw -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list-targets --kind c
raw -r http://localhost:40332 --policy-hash 81c1a41d09e08087a4b679418b12be5d3ab15742 list-targets --kind 99`,
RunE: runListTargetsCmd,
}
const (
kindFlag = "kind"
)
const (
methodIteratorChainsByPrefix = "iteratorChainsByPrefix"
methodListTargets = "listTargets"
methodListChainNames = "listChainNames"
)
func initListTargetsCmd() {
listTargetsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
listTargetsCmd.Flags().String(kindFlag, "n", "Target kind (1-byte) to list (n(namespace)/c(container)/g(group)/u(user)/i(iam)) or its integer representation")
}
func runListTargetsCmd(cmd *cobra.Command, _ []string) error {
kind, _ := cmd.Flags().GetString(kindFlag)
entity, err := parseTargetKind(kind)
commonCmd.ExitOnErr(cmd, "can't parse target kind: %w", err)
inv, policyHash := initPolicyReader(cmd)
res, err := commonclient.ReadIteratorItems(inv, 100, policyHash, methodListTargets, entity)
commonCmd.ExitOnErr(cmd, "can't read iterator: %w", err)
cmd.Printf("%s targets: %d\n", kind, len(res))
for _, re := range res {
bytes, err := re.TryBytes()
commonCmd.ExitOnErr(cmd, "can't parse result: %w", err)
cmd.Printf("%s\t(base64: '%s')\n", string(bytes), base64.StdEncoding.EncodeToString(bytes))
}
return nil
}
func parseTargetKind(typ string) (*big.Int, error) {
val, err := strconv.ParseInt(typ, 10, 64)
if err == nil {
return big.NewInt(val), nil
}
if len(typ) != 1 {

This is very error-prone: we connect some constants from contracts with CLI interface in the frostfs-adm.
If something changes, we won't notice.
Can we make this an implicit switch that uses some exported constants from the policy contract?

And, to be fair, full multi-byte names or even boolean flags look better IMO.

This is very error-prone: we connect some constants from contracts with CLI interface in the frostfs-adm. If something changes, we won't notice. Can we make this an implicit switch that uses some exported constants from the `policy` contract? And, to be fair, full multi-byte names or even boolean flags look better IMO.

I consider this command as low-level one, so I would like to be able to invoke ListTarget using any valid one-byte parameter (the same way as I can do this by neo-go contract testinvokefuntion)

I consider this command as low-level one, so I would like to be able to invoke [ListTarget](https://git.frostfs.info/TrueCloudLab/frostfs-contract/src/commit/201db45bd739fb729448ea8c806ae51ff01a5f2d/policy/policy_contract.go#L254) using any valid one-byte parameter (the same way as I can do this by `neo-go contract testinvokefuntion`)

But can we provide e.g. \x00?

But can we provide e.g. `\x00`?

Update parsing to be able provide just number

Update parsing to be able provide just number
return nil, fmt.Errorf("invalid type: %s", typ)
}
return big.NewInt(int64(typ[0])), nil
}
func initPolicyReader(cmd *cobra.Command) (*invoker.Invoker, util.Uint160) {
endpoint := viper.GetString(commonflags.EndpointFlag)
rpcCli, err := rpcclient.New(cmd.Context(), endpoint, rpcclient.Options{})
commonCmd.ExitOnErr(cmd, "can't init rpc client: %w", err)
aarifullin marked this conversation as resolved Outdated

Please, let this flag be optional (see this)

Please, let this flag be optional (see [this](https://git.frostfs.info/TrueCloudLab/frostfs-node/pulls/1607#issuecomment-64674))

This is already optional, If I understood you correctly. I have default policy.frostfs value for this flag

Cmd.PersistentFlags().String(policyHashFlag, "policy.frostfs", "NNS name or script hash of policy contract")

This is already optional, If I understood you correctly. I have default `policy.frostfs` value for this flag https://git.frostfs.info/dkirillov/frostfs-node/src/commit/4022349fbe06f374bd15cf9a29f043d5827c2f2a/cmd/frostfs-adm/internal/modules/morph/ape/raw/root.go#L23

Ah, yeah. Correct!

Ah, yeah. Correct!
inv := invoker.New(rpcCli, nil)
policyHashStr, _ := cmd.Flags().GetString(policyHashFlag)
if policyHash, err := util.Uint160DecodeStringLE(policyHashStr); err == nil {
return inv, policyHash

Could you explain, why this code works?
We get policyhash, which is hex-encoded contract hash we would like to use.
But instead of decoding it we use NNSResolveHash where policyHashStr is provided as a domain and resolved.
Secondly, regardless of how it works, if the user provides explicit policy hash, we should not care about NNS at all.

What am I missing here?

Could you explain, why this code works? We get policyhash, which is hex-encoded contract hash we would like to use. But instead of decoding it we use `NNSResolveHash` where `policyHashStr` is provided as a _domain_ and resolved. Secondly, regardless of how it works, if the user provides explicit policy hash, we should not care about NNS at all. What am I missing here?

Oh, It seems I've lost decoding during refactor. I'll fix it

Oh, It seems I've lost decoding during refactor. I'll fix it
  1. There are other places like this too, please check them.
  2. Now we skip err != nil for some reason. Why? We expect the hash, if it is invalid command should fail.
1. There are other places like this too, please check them. 2. Now we skip `err != nil` for some reason. Why? We expect the hash, if it is invalid command should fail.
  1. It's similar to the getting flags across frostfs-adm code e.g.

    s, _ := cmd.Flags().GetString(addrAdminFlag)

2. It's similar to the getting flags across frostfs-adm code e.g. https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/0991077cb364c94e40f6838035acab4754b4d8af/cmd/frostfs-adm/internal/modules/morph/ape/ape.go#L202

We skip an error there because it cannot possibly happen (the flag is defined and all flags can be used as string).
The error you skip (in the if below this line) happens during parsing.

We skip an error there because it cannot possibly happen (the flag is defined and all flags can be used as string). The error you skip (in the `if` below this line) happens during parsing.

If parsing failed then we should try to resolve it in NNS, because it can have format like policy.frostfs

If parsing failed then we should try to resolve it in NNS, because it can have format like `policy.frostfs`

Then it should be a --policy-domain and not --policy-hash.
However, I think policy-hash is enough, if we want to use a custom domain, we might as well resolve it separately.

Then it should be a `--policy-domain` and not `--policy-hash`. However, I think `policy-hash` is enough, if we want to use a custom domain, we might as well resolve it separately.
}
nnsCs, err := helper.GetContractByID(management.NewReader(inv), 1)
commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)
policyHash, err := helper.NNSResolveHash(inv, nnsCs.Hash, policyHashStr)
commonCmd.ExitOnErr(cmd, "can't resolve NNS policy contract: %w", err)
return inv, policyHash
}

View file

@ -0,0 +1,56 @@
package output
import (
"encoding/base64"
apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/spf13/cobra"
)
const (
minPrintable = 32
maxPrintable = 127
)
func PrintChains(cmd *cobra.Command, list []stackitem.Item, decodeChain, decodeID bool) error {
for _, item := range list {
bytes, err := item.TryBytes()
if err != nil {
return err
}
if !decodeChain {
cmd.Printf("\t%s\n", string(bytes))
continue
}
var chain apechain.Chain
if err = chain.DecodeBytes(bytes); err != nil {
cmd.PrintErrf("invalid chain format: %s\n", base64.StdEncoding.EncodeToString(bytes))
continue
}
raw, err := chain.MarshalJSON()
if err != nil {
return err
}
if decodeID {
var printableID string
for _, r := range string(chain.ID) {
if minPrintable <= r && r <= maxPrintable {
printableID += string(r)
} else {
printableID += "."
}
}
cmd.Printf("\t%s - %s\n", printableID, string(raw))
} else {
cmd.Printf("\t%s\n", string(raw))
}
}
return nil
}

View file

@ -0,0 +1,32 @@
package raw
import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var Cmd = &cobra.Command{
Use: "raw",
Short: "FrostFS policy contract raw reader",
PersistentPreRun: func(cmd *cobra.Command, _ []string) {
aarifullin marked this conversation as resolved Outdated

raw is subcommand and Long won't be showed in the prompt. You can leave Short only

`raw` is subcommand and `Long` won't be showed in the prompt. You can leave `Short` only
_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
},
}
const (
policyHashFlag = "policy-hash"
)
func init() {
Cmd.PersistentFlags().String(policyHashFlag, "policy.frostfs", "NNS name or script hash of policy contract")
Cmd.AddCommand(listTargetsCmd)
initListTargetsCmd()
Cmd.AddCommand(listChainNamesCmd)
initListChainNamesCmd()
Cmd.AddCommand(listChainsByPrefixCmd)
initListChainsByPrefixCmd()
}

View file

@ -1,6 +1,10 @@
package ape
import "github.com/spf13/cobra"
import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/chains"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape/raw"
"github.com/spf13/cobra"
)
var Cmd = &cobra.Command{
Use: "ape",
@ -8,6 +12,9 @@ var Cmd = &cobra.Command{
}
func init() {
Cmd.AddCommand(raw.Cmd)
Cmd.AddCommand(chains.Cmd)
initAddRuleChainCmd()
initRemoveRuleChainCmd()
initListRuleChainsCmd()

View file

@ -10,6 +10,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/config"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
@ -158,6 +159,40 @@ func NNSResolveHash(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (
return ParseNNSResolveResult(item)
}
// NNSResolveContainerDomain returns errMissingNNSRecord if invocation fault exception contains "token not found".
func NNSResolveContainerDomain(inv *invoker.Invoker, nnsHash util.Uint160, domain string) (cid.ID, error) {
item, err := NNSResolve(inv, nnsHash, domain)
if err != nil {
return cid.ID{}, err
}
return parseNNSResolveResultCID(item)
}
func parseNNSResolveResultCID(res stackitem.Item) (cid.ID, error) {
arr, ok := res.Value().([]stackitem.Item)
if !ok {
arr = []stackitem.Item{res}
}
if _, ok := res.Value().(stackitem.Null); ok || len(arr) == 0 {
return cid.ID{}, errors.New("NNS record is missing")
}
var cnrID cid.ID
for i := range arr {
bs, err := arr[i].TryBytes()
if err != nil {
continue
}
if err = cnrID.DecodeString(string(bs)); err == nil {
return cnrID, nil
}
}
return cid.ID{}, errors.New("no valid CIDs are found")
}
func DomainOf(contract string) string {
return contract + ".frostfs"
}

View file

@ -7,7 +7,7 @@ const (
TargetNameFlag = "target-name"
TargetNameFlagDesc = "Resource name in APE resource name format"
TargetTypeFlag = "target-type"
TargetTypeFlagDesc = "Resource type(container/namespace)"
TargetTypeFlagDesc = "Resource type(container/namespace/group/user)"
fyrchik marked this conversation as resolved Outdated

This should be in a separate commit.

This should be in a separate commit.
ChainIDFlag = "chain-id"
ChainIDFlagDesc = "Chain id"
ChainIDHexFlag = "chain-id-hex"