From 7bd735f615a7c3aa9badf5a84509b8af1dc04195 Mon Sep 17 00:00:00 2001 From: Andre Duffeck Date: Fri, 17 Mar 2023 12:31:07 +0100 Subject: [PATCH] Add command for inspecting and manipulating node metadata (#5858) * Add command for inspecting and manipulating node metadata * Add changelog * Bump reva --- .../add-decomposedfs-metadata-command.md | 6 + go.mod | 2 +- go.sum | 4 +- ocis/pkg/command/decomposedfs.go | 225 ++++++++++++++++++ 4 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 changelog/unreleased/add-decomposedfs-metadata-command.md create mode 100644 ocis/pkg/command/decomposedfs.go diff --git a/changelog/unreleased/add-decomposedfs-metadata-command.md b/changelog/unreleased/add-decomposedfs-metadata-command.md new file mode 100644 index 0000000000..41e8d2751c --- /dev/null +++ b/changelog/unreleased/add-decomposedfs-metadata-command.md @@ -0,0 +1,6 @@ +Enhancement: add 'ocis decomposedfs metadata' command + +We added a 'ocis decomposedfs metadata' command for inspecting and manipulating +node metadata. + +https://github.com/owncloud/ocis/pull/5858 diff --git a/go.mod b/go.mod index 3a4e0cd5a4..f7814fffd8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/blevesearch/bleve/v2 v2.3.6 github.com/coreos/go-oidc/v3 v3.4.0 github.com/cs3org/go-cs3apis v0.0.0-20221012090518-ef2996678965 - github.com/cs3org/reva/v2 v2.12.1-0.20230315220346-44e55cb0de0a + github.com/cs3org/reva/v2 v2.12.1-0.20230316154023-890c222e8de8 github.com/disintegration/imaging v1.6.2 github.com/gabriel-vasile/mimetype v1.4.1 github.com/ggwhite/go-masker v1.0.9 diff --git a/go.sum b/go.sum index 57399d8d8d..2f18907da4 100644 --- a/go.sum +++ b/go.sum @@ -618,8 +618,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4= github.com/crewjam/saml v0.4.10 h1:Rjs6x4s/aQFXiaPjw3uhB4VdxRqoxHXOJrrj4BsMn9o= github.com/crewjam/saml v0.4.10/go.mod h1:9Zh6dWPtB3MSzTRt8fIFH60Z351QQ+s7hCU3J/tTlA4= -github.com/cs3org/reva/v2 v2.12.1-0.20230315220346-44e55cb0de0a h1:XMIKlCSPVpnFaE+wWCqSPLNwEpwiGSQnBYK/vulzhv4= -github.com/cs3org/reva/v2 v2.12.1-0.20230315220346-44e55cb0de0a/go.mod h1:FNAYs5H3xs8v0OFmNgZtiMAzIMXd/6TJmO0uZuNn8pQ= +github.com/cs3org/reva/v2 v2.12.1-0.20230316154023-890c222e8de8 h1:Gh1piputuyWbjATcRJ5liXCS+x6xc+X2HpR/3l0TUgM= +github.com/cs3org/reva/v2 v2.12.1-0.20230316154023-890c222e8de8/go.mod h1:FNAYs5H3xs8v0OFmNgZtiMAzIMXd/6TJmO0uZuNn8pQ= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= diff --git a/ocis/pkg/command/decomposedfs.go b/ocis/pkg/command/decomposedfs.go new file mode 100644 index 0000000000..8c10f60ca2 --- /dev/null +++ b/ocis/pkg/command/decomposedfs.go @@ -0,0 +1,225 @@ +package command + +import ( + "context" + "encoding/base64" + "encoding/hex" + "fmt" + "sort" + "strings" + + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/lookup" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/metadata" + "github.com/cs3org/reva/v2/pkg/storage/utils/decomposedfs/options" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/owncloud/ocis/v2/ocis-pkg/config" + "github.com/owncloud/ocis/v2/ocis/pkg/register" + "github.com/urfave/cli/v2" +) + +// DecomposedfsCommand is the entrypoint for the groups command. +func DecomposedfsCommand(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "decomposedfs", + Usage: `cli tools to inspect and manipulate a decomposedfs storage.`, + Category: "maintenance", + Subcommands: []*cli.Command{metadataCmd(cfg)}, + } +} + +func init() { + register.AddCommand(DecomposedfsCommand) +} + +func metadataCmd(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "metadata", + Usage: `cli tools to inspect and manipulate node metadata`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "root", + Aliases: []string{"r"}, + Required: true, + Usage: "Path to the decomposedfs", + }, + &cli.StringFlag{ + Name: "node", + Required: true, + Aliases: []string{"n"}, + Usage: "Path to or ID of the node to inspect", + }, + }, + Subcommands: []*cli.Command{dumpCmd(cfg), getCmd(cfg), setCmd(cfg)}, + } +} + +func dumpCmd(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "dump", + Usage: `print the metadata of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`, + Action: func(c *cli.Context) error { + lu, backend := getBackend(c) + path, err := getPath(c, lu) + if err != nil { + return err + } + + attribs, err := backend.All(path) + if err != nil { + fmt.Println("Error reading attributes") + return err + } + printAttribs(attribs, c.String("attribute")) + return nil + }, + } +} + +func getCmd(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "get", + Usage: `print a specific attribute of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "attribute", + Aliases: []string{"a"}, + Usage: "attribute to inspect", + }, + }, + Action: func(c *cli.Context) error { + lu, backend := getBackend(c) + path, err := getPath(c, lu) + if err != nil { + return err + } + + attribs, err := backend.All(path) + if err != nil { + fmt.Println("Error reading attributes") + return err + } + printAttribs(attribs, c.String("attribute")) + return nil + }, + } +} + +func setCmd(cfg *config.Config) *cli.Command { + return &cli.Command{ + Name: "set", + Usage: `manipulate metadata of the given node. Binary attributes can be given hex encoded (prefix by '0x') or base64 encoded (prefix by '0s').`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "attribute", + Required: true, + Aliases: []string{"a"}, + Usage: "attribute to inspect", + }, + &cli.StringFlag{ + Name: "value", + Required: true, + Aliases: []string{"v"}, + Usage: "value to set", + }, + }, + Action: func(c *cli.Context) error { + lu, backend := getBackend(c) + path, err := getPath(c, lu) + if err != nil { + return err + } + + v := c.String("value") + if strings.HasPrefix(v, "0s") { + b64, err := base64.StdEncoding.DecodeString(v[2:]) + if err == nil { + v = string(b64) + } + } else if strings.HasPrefix(v, "0x") { + h, err := hex.DecodeString(v) + if err == nil { + v = string(h) + } + } + + err = backend.Set(path, c.String("attribute"), []byte(v[2:])) + if err != nil { + fmt.Println("Error setting attribute") + return err + } + return nil + }, + } +} + +func backend(root, backend string) metadata.Backend { + switch backend { + case "xattrs": + return metadata.XattrsBackend{} + case "mpk": + return metadata.NewMessagePackBackend(root, options.CacheOptions{}) + } + return metadata.NullBackend{} +} + +func getBackend(c *cli.Context) (*lookup.Lookup, metadata.Backend) { + rootFlag := c.String("root") + + bod := lookup.DetectBackendOnDisk(rootFlag) + backend := backend(rootFlag, bod) + lu := lookup.New(backend, &options.Options{ + Root: rootFlag, + MetadataBackend: bod, + }) + return lu, backend +} + +func getPath(c *cli.Context, lu *lookup.Lookup) (string, error) { + nodeFlag := c.String("node") + + path := "" + if strings.HasPrefix(nodeFlag, "/") { + path = nodeFlag + } else { + nId := c.String("node") + id, err := storagespace.ParseID(nId) + if err != nil { + fmt.Println("Invalid node id.") + return "", err + } + n, _ := lu.NodeFromID(context.Background(), &id) + if err != nil || !n.Exists { + fmt.Println("Can not find node '" + nId + "'") + return "", err + } + path = n.InternalPath() + } + return path, nil +} + +func printAttribs(attribs map[string][]byte, onlyAttribute string) { + if onlyAttribute != "" { + fmt.Println(onlyAttribute + `=` + attribToString(attribs[onlyAttribute])) + return + } + + names := []string{} + for k, _ := range attribs { + names = append(names, k) + } + + sort.Strings(names) + + for _, n := range names { + fmt.Println(n + `=` + attribToString(attribs[n])) + } +} + +func attribToString(attrib []byte) string { + for i := 0; i < len(attrib); i++ { + if attrib[i] < 32 || attrib[i] >= 127 { + return "0s" + base64.StdEncoding.EncodeToString(attrib) + } + } + return `"` + string(attrib) + `"` +}