diff --git a/cmd/khepri/cmd_cat.go b/cmd/khepri/cmd_cat.go new file mode 100644 index 000000000..92a5d8f71 --- /dev/null +++ b/cmd/khepri/cmd_cat.go @@ -0,0 +1,133 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "os" + + "github.com/fd0/khepri" + "github.com/fd0/khepri/backend" +) + +func commandCat(be backend.Server, key *khepri.Key, args []string) error { + if len(args) != 2 { + return errors.New("usage: cat [blob|tree|snapshot|key|lock] ID") + } + + tpe := args[0] + + id, err := backend.ParseID(args[1]) + if err != nil { + return err + } + + ch, err := khepri.NewContentHandler(be, key) + if err != nil { + return err + } + + err = ch.LoadAllSnapshots() + if err != nil { + return err + } + + switch tpe { + case "blob": + // try id + data, err := ch.Load(backend.Blob, id) + if err == nil { + _, err = os.Stdout.Write(data) + return err + } + + // try storage id + buf, err := be.Get(backend.Blob, id) + if err != nil { + return err + } + + // decrypt + buf, err = key.Decrypt(buf) + if err != nil { + return err + } + + _, err = os.Stdout.Write(data) + return err + + case "tree": + var tree khepri.Tree + // try id + err := ch.LoadJSON(backend.Tree, id, &tree) + if err != nil { + // try storage id + buf, err := be.Get(backend.Tree, id) + if err != nil { + return err + } + + // decrypt + buf, err = key.Decrypt(buf) + if err != nil { + return err + } + + // unmarshal + err = json.Unmarshal(backend.Uncompress(buf), &tree) + if err != nil { + return err + } + } + + buf, err := json.MarshalIndent(&tree, "", " ") + if err != nil { + return err + } + + fmt.Println(string(buf)) + + return nil + case "snapshot": + var sn khepri.Snapshot + err := ch.LoadJSONRaw(backend.Snapshot, id, &sn) + if err != nil { + return err + } + + buf, err := json.MarshalIndent(&sn, "", " ") + if err != nil { + return err + } + + fmt.Println(string(buf)) + + return nil + case "key": + data, err := be.Get(backend.Key, id) + if err != nil { + return err + } + + var key khepri.Key + err = json.Unmarshal(data, &key) + if err != nil { + return err + } + + buf, err := json.MarshalIndent(&key, "", " ") + if err != nil { + return err + } + + fmt.Println(string(buf)) + + return nil + case "lock": + return errors.New("not yet implemented") + default: + return errors.New("invalid type") + } + + return nil +} diff --git a/cmd/khepri/cmd_list.go b/cmd/khepri/cmd_list.go index f49cdbf96..46cf6bcc0 100644 --- a/cmd/khepri/cmd_list.go +++ b/cmd/khepri/cmd_list.go @@ -1,21 +1,44 @@ package main import ( + "errors" + "fmt" + "github.com/fd0/khepri" "github.com/fd0/khepri/backend" ) func commandList(be backend.Server, key *khepri.Key, args []string) error { + if len(args) != 1 { + return errors.New("usage: list [blobs|trees|snapshots|keys|locks]") + } - // ids, err := be.ListRefs() - // if err != nil { - // fmt.Fprintf(os.Stderr, "error: %v\n", err) - // return nil - // } + var ( + t backend.Type + each func(backend.Server, backend.Type, func(backend.ID, []byte, error)) error = backend.Each + ) + switch args[0] { + case "blobs": + t = backend.Blob + each = key.Each + case "trees": + t = backend.Tree + each = key.Each + case "snapshots": + t = backend.Snapshot + case "keys": + t = backend.Key + case "locks": + t = backend.Lock + default: + return errors.New("invalid type") + } - // for _, id := range ids { - // fmt.Printf("%v\n", id) - // } - - return nil + return each(be, t, func(id backend.ID, data []byte, err error) { + if t == backend.Blob || t == backend.Tree { + fmt.Printf("%s %s\n", id, backend.Hash(data)) + } else { + fmt.Printf("%s\n", id) + } + }) } diff --git a/cmd/khepri/cmd_ls.go b/cmd/khepri/cmd_ls.go new file mode 100644 index 000000000..53ee53ded --- /dev/null +++ b/cmd/khepri/cmd_ls.go @@ -0,0 +1,74 @@ +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/fd0/khepri" + "github.com/fd0/khepri/backend" +) + +func print_node(prefix string, n *khepri.Node) string { + switch n.Type { + case "file": + return fmt.Sprintf("%s %5d %5d %6d %s %s", + n.Mode, n.UID, n.GID, n.Size, n.ModTime, filepath.Join(prefix, n.Name)) + case "dir": + return fmt.Sprintf("%s %5d %5d %6d %s %s", + n.Mode|os.ModeDir, n.UID, n.GID, n.Size, n.ModTime, filepath.Join(prefix, n.Name)) + case "symlink": + return fmt.Sprintf("%s %5d %5d %6d %s %s -> %s", + n.Mode|os.ModeSymlink, n.UID, n.GID, n.Size, n.ModTime, filepath.Join(prefix, n.Name), n.LinkTarget) + default: + return fmt.Sprintf("", n.Type, n.Name) + } +} + +func print_tree(prefix string, ch *khepri.ContentHandler, id backend.ID) error { + tree := &khepri.Tree{} + + err := ch.LoadJSON(backend.Tree, id, tree) + if err != nil { + return err + } + + for _, entry := range *tree { + fmt.Println(print_node(prefix, entry)) + + if entry.Type == "dir" && entry.Subtree != nil { + err = print_tree(filepath.Join(prefix, entry.Name), ch, entry.Subtree) + if err != nil { + return err + } + } + } + + return nil +} + +func commandLs(be backend.Server, key *khepri.Key, args []string) error { + if len(args) < 1 || len(args) > 2 { + return errors.New("usage: ls SNAPSHOT_ID [dir]") + } + + id, err := backend.ParseID(args[0]) + if err != nil { + return err + } + + ch, err := khepri.NewContentHandler(be, key) + if err != nil { + return err + } + + sn, err := ch.LoadSnapshot(id) + if err != nil { + return err + } + + fmt.Printf("snapshot of %s at %s:\n", sn.Dir, sn.Time) + + return print_tree("", ch, sn.Content) +} diff --git a/cmd/khepri/main.go b/cmd/khepri/main.go index ce8b81531..ac76859ce 100644 --- a/cmd/khepri/main.go +++ b/cmd/khepri/main.go @@ -124,6 +124,8 @@ func init() { commands["restore"] = commandRestore commands["list"] = commandList commands["snapshots"] = commandSnapshots + commands["cat"] = commandCat + commands["ls"] = commandLs } func main() { diff --git a/contenthandler.go b/contenthandler.go index 47fecfe92..68b3f75f1 100644 --- a/contenthandler.go +++ b/contenthandler.go @@ -29,7 +29,7 @@ func NewContentHandler(be backend.Server, key *Key) (*ContentHandler, error) { return ch, nil } -// LoadSnapshotadds all blobs from a snapshot into the content handler and returns the snapshot. +// LoadSnapshot adds all blobs from a snapshot into the content handler and returns the snapshot. func (ch *ContentHandler) LoadSnapshot(id backend.ID) (*Snapshot, error) { sn, err := LoadSnapshot(ch, id) if err != nil {