diff --git a/cmd/frostfs-cli/modules/ape_manager/add_chain.go b/cmd/frostfs-cli/modules/ape_manager/add_chain.go new file mode 100644 index 00000000..7e661439 --- /dev/null +++ b/cmd/frostfs-cli/modules/ape_manager/add_chain.go @@ -0,0 +1,141 @@ +package apemanager + +import ( + "encoding/hex" + "errors" + + internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + apemanager_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/apemanager" + client_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" + cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" + apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" + "github.com/spf13/cobra" +) + +const ( + chainIDFlag = "chain-id" + chainIDHexFlag = "chain-id-hex" + ruleFlag = "rule" + pathFlag = "path" +) + +const ( + targetNameFlag = "target-name" + targetNameDesc = "Resource name in APE resource name format" + targetTypeFlag = "target-type" + targetTypeDesc = "Resource type(container/namespace)" +) + +const ( + defaultNamespace = "" + namespaceTarget = "namespace" + containerTarget = "container" + userTarget = "user" + groupTarget = "group" +) + +var ( + errUnknownTargetType = errors.New("unknown target type") +) + +var addCmd = &cobra.Command{ + Use: "add", + Short: "Add rule chain for a target", + Run: add, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, +} + +func parseTarget(cmd *cobra.Command) (ct apemanager_sdk.ChainTarget) { + typ, _ := cmd.Flags().GetString(targetTypeFlag) + name, _ := cmd.Flags().GetString(targetNameFlag) + + ct.Name = name + + switch typ { + case namespaceTarget: + ct.TargetType = apemanager_sdk.TargetTypeNamespace + case containerTarget: + var cnr cid.ID + commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(name)) + ct.TargetType = apemanager_sdk.TargetTypeContainer + case userTarget: + ct.TargetType = apemanager_sdk.TargetTypeUser + case groupTarget: + ct.TargetType = apemanager_sdk.TargetTypeGroup + default: + commonCmd.ExitOnErr(cmd, "read target type error: %w", errUnknownTargetType) + } + return ct +} + +func parseChain(cmd *cobra.Command) apemanager_sdk.Chain { + chainID, _ := cmd.Flags().GetString(chainIDFlag) + hexEncoded, _ := cmd.Flags().GetBool(chainIDHexFlag) + + chainIDRaw := []byte(chainID) + + if hexEncoded { + var err error + chainIDRaw, err = hex.DecodeString(chainID) + commonCmd.ExitOnErr(cmd, "can't decode chain ID as hex: %w", err) + } + + chain := new(apechain.Chain) + chain.ID = apechain.ID(chainIDRaw) + + if rules, _ := cmd.Flags().GetStringArray(ruleFlag); len(rules) > 0 { + commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, rules)) + } else if encPath, _ := cmd.Flags().GetString(pathFlag); encPath != "" { + commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", util.ParseAPEChainBinaryOrJSON(chain, encPath)) + } else { + commonCmd.ExitOnErr(cmd, "parser error: %w", errors.New("rule is not passed")) + } + + cmd.Println("Parsed chain:") + util.PrintHumanReadableAPEChain(cmd, chain) + + serialized := chain.Bytes() + return apemanager_sdk.Chain{ + Raw: serialized, + } +} + +func add(cmd *cobra.Command, _ []string) { + c := parseChain(cmd) + + target := parseTarget(cmd) + + key := key.Get(cmd) + cli := internalclient.GetSDKClientByFlag(cmd, key, commonflags.RPC) + + res, err := cli.APEManagerAddChain(cmd.Context(), client_sdk.PrmAPEManagerAddChain{ + ChainTarget: target, + Chain: c, + }) + + commonCmd.ExitOnErr(cmd, "add chain error: %w", err) + + cmd.Println("Rule has been added.") + cmd.Println("Chain ID: ", string(res.ChainID)) +} + +func initAddCmd() { + commonflags.Init(addCmd) + + ff := addCmd.Flags() + ff.StringArray(ruleFlag, []string{}, "Rule statement") + ff.String(pathFlag, "", "Path to encoded chain in JSON or binary format") + ff.String(chainIDFlag, "", "Assign ID to the parsed chain") + ff.String(targetNameFlag, "", targetNameDesc) + ff.String(targetTypeFlag, "", targetTypeDesc) + _ = addCmd.MarkFlagRequired(targetTypeFlag) + ff.Bool(chainIDHexFlag, false, "Flag to parse chain ID as hex") + + addCmd.MarkFlagsMutuallyExclusive(pathFlag, ruleFlag) +} diff --git a/cmd/frostfs-cli/modules/ape_manager/list_chain.go b/cmd/frostfs-cli/modules/ape_manager/list_chain.go new file mode 100644 index 00000000..a5dd4461 --- /dev/null +++ b/cmd/frostfs-cli/modules/ape_manager/list_chain.go @@ -0,0 +1,49 @@ +package apemanager + +import ( + internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + apeutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + client_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" + apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" + "github.com/spf13/cobra" +) + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List rule chains defined on target", + Run: list, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, +} + +func list(cmd *cobra.Command, _ []string) { + target := parseTarget(cmd) + + key := key.Get(cmd) + cli := internalclient.GetSDKClientByFlag(cmd, key, commonflags.RPC) + + resp, err := cli.APEManagerListChains(cmd.Context(), + client_sdk.PrmAPEManagerListChains{ + ChainTarget: target, + }) + commonCmd.ExitOnErr(cmd, "list chains call error: %w", err) + + for _, respChain := range resp.Chains { + var chain apechain.Chain + commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(respChain.Raw)) + apeutil.PrintHumanReadableAPEChain(cmd, &chain) + } +} + +func initListCmd() { + commonflags.Init(listCmd) + + ff := listCmd.Flags() + ff.String(targetNameFlag, "", targetNameDesc) + ff.String(targetTypeFlag, "", targetTypeDesc) + _ = listCmd.MarkFlagRequired(targetTypeFlag) +} diff --git a/cmd/frostfs-cli/modules/ape_manager/remove_chain.go b/cmd/frostfs-cli/modules/ape_manager/remove_chain.go new file mode 100644 index 00000000..179bd5c9 --- /dev/null +++ b/cmd/frostfs-cli/modules/ape_manager/remove_chain.go @@ -0,0 +1,66 @@ +package apemanager + +import ( + "encoding/hex" + "errors" + + internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + client_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" + "github.com/spf13/cobra" +) + +var ( + errEmptyChainID = errors.New("chain id cannot be empty") + + removeCmd = &cobra.Command{ + Use: "remove", + Short: "Remove rule chain for a target", + Run: remove, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + commonflags.Bind(cmd) + }, + } +) + +func remove(cmd *cobra.Command, _ []string) { + target := parseTarget(cmd) + + key := key.Get(cmd) + cli := internalclient.GetSDKClientByFlag(cmd, key, commonflags.RPC) + + chainID, _ := cmd.Flags().GetString(chainIDFlag) + if chainID == "" { + commonCmd.ExitOnErr(cmd, "read chain id error: %w", errEmptyChainID) + } + chainIDRaw := []byte(chainID) + + hexEncoded, _ := cmd.Flags().GetBool(chainIDHexFlag) + if hexEncoded { + var err error + chainIDRaw, err = hex.DecodeString(chainID) + commonCmd.ExitOnErr(cmd, "can't decode chain ID as hex: %w", err) + } + + _, err := cli.APEManagerRemoveChain(cmd.Context(), client_sdk.PrmAPEManagerRemoveChain{ + ChainTarget: target, + ChainID: chainIDRaw, + }) + + commonCmd.ExitOnErr(cmd, "remove chain error: %w", err) + + cmd.Println("\nRule has been removed.") +} + +func initRemoveCmd() { + commonflags.Init(removeCmd) + + ff := removeCmd.Flags() + ff.String(targetNameFlag, "", targetNameDesc) + ff.String(targetTypeFlag, "", targetTypeDesc) + _ = removeCmd.MarkFlagRequired(targetTypeFlag) + ff.String(chainIDFlag, "", "Chain id") + ff.Bool(chainIDHexFlag, false, "Flag to parse chain ID as hex") +} diff --git a/cmd/frostfs-cli/modules/ape_manager/root.go b/cmd/frostfs-cli/modules/ape_manager/root.go new file mode 100644 index 00000000..7b4f9292 --- /dev/null +++ b/cmd/frostfs-cli/modules/ape_manager/root.go @@ -0,0 +1,21 @@ +package apemanager + +import ( + "github.com/spf13/cobra" +) + +var Cmd = &cobra.Command{ + Use: "ape-manager", + Short: "Operations with APE manager", + Long: `Operations with APE manager`, +} + +func init() { + Cmd.AddCommand(addCmd) + Cmd.AddCommand(removeCmd) + Cmd.AddCommand(listCmd) + + initAddCmd() + initRemoveCmd() + initListCmd() +} diff --git a/cmd/frostfs-cli/modules/root.go b/cmd/frostfs-cli/modules/root.go index 87e6a905..21c367d2 100644 --- a/cmd/frostfs-cli/modules/root.go +++ b/cmd/frostfs-cli/modules/root.go @@ -8,6 +8,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags" accountingCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/accounting" "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/acl" + apemanager "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/ape_manager" bearerCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/bearer" containerCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/container" controlCli "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/control" @@ -81,6 +82,7 @@ func init() { rootCmd.Flags().Bool("version", false, "Application version and FrostFS API compatibility") rootCmd.AddCommand(acl.Cmd) + rootCmd.AddCommand(apemanager.Cmd) rootCmd.AddCommand(bearerCli.Cmd) rootCmd.AddCommand(sessionCli.Cmd) rootCmd.AddCommand(accountingCli.Cmd)