cmd: rename camput to pk-put, and make "pk put" call it

A new "put" mode is added to the pk command, so that the "pk put"
command can be used to create and upload blobs.

What this command does is actually just call the previously named
"camput" executable, which is renamed to "pk-put" in this change.

This involves adding a new way to register a mode in cmdmain, when such
a mode is just meant to call an external binary. To emphasize the
distinction, the existing func (to register a sub-command, or a mode) is
renamed from RegisterCommand to RegisterMode, and RegisterCommand is now
the name of the new func/way.

Updates #981
Updates #1056

Change-Id: Ief954c17aa88a376f551df7de4b4e9fe41ad96d1
This commit is contained in:
mpl 2018-03-06 22:39:14 +01:00 committed by Brad Fitzpatrick
parent a13abdeb8c
commit ce4658abfc
76 changed files with 397 additions and 177 deletions

View File

@ -44,7 +44,7 @@ type gceCmd struct {
}
func init() {
cmdmain.RegisterCommand("gce", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("gce", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(gceCmd)
flags.StringVar(&cmd.project, "project", "", "Name of Project.")
flags.StringVar(&cmd.zone, "zone", gce.DefaultRegion, "GCE zone or region. If a region is given, a random zone in that region is selected. See https://cloud.google.com/compute/docs/zones")

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Hacks for running camput as a child process on Android.
// Hacks for running pk-put as a child process on Android.
package main

View File

@ -31,7 +31,7 @@ type attrCmd struct {
}
func init() {
cmdmain.RegisterCommand("attr", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("attr", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(attrCmd)
flags.BoolVar(&cmd.add, "add", false, `Adds attribute (e.g. "tag")`)
flags.BoolVar(&cmd.del, "del", false, "Deletes named attribute [value]")
@ -44,7 +44,7 @@ func (c *attrCmd) Describe() string {
}
func (c *attrCmd) Usage() {
cmdmain.Errorf("Usage: camput [globalopts] attr [attroption] <permanode> <name> <value>")
cmdmain.Errorf("Usage: pk-put [globalopts] attr [attroption] <permanode> <name> <value>")
}
func (c *attrCmd) Examples() []string {

View File

@ -44,7 +44,7 @@ type blobCmd struct {
}
func init() {
cmdmain.RegisterCommand("blob", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("blob", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(blobCmd)
flags.BoolVar(&cmd.makePermanode, "permanode", false, "Create and associate a new permanode for the blob.")
flags.StringVar(&cmd.title, "title", "", "Optional title attribute to set on permanode when using -permanode.")
@ -59,7 +59,7 @@ func (c *blobCmd) Describe() string {
}
func (c *blobCmd) Usage() {
fmt.Fprintf(cmdmain.Stderr, "Usage: camput [globalopts] blob <files>\n camput [globalopts] blob -\n")
fmt.Fprintf(cmdmain.Stderr, "Usage: pk-put [globalopts] blob <files>\n pk-put [globalopts] blob -\n")
}
func (c *blobCmd) Examples() []string {

View File

@ -93,7 +93,7 @@ func init() {
}
}
// So multiple cmd/camput TestFoo funcs run, each with
// So multiple cmd/pk-put TestFoo funcs run, each with
// an fresh (and not previously closed) Uploader:
uploader = nil
uploaderOnce = sync.Once{}

View File

@ -30,7 +30,7 @@ import (
"perkeep.org/pkg/cmdmain"
)
// env is the environment that a camput test runs within.
// env is the environment that a pk-put test runs within.
type env struct {
// stdin is the standard input, or /dev/null if nil
stdin io.Reader
@ -88,7 +88,7 @@ func TestUsageOnNoargs(t *testing.T) {
if len(out) != 0 {
t.Errorf("wanted nothing on stdout; got:\n%s", out)
}
if !bytes.Contains(err, []byte("Usage: camput")) {
if !bytes.Contains(err, []byte("Usage: pk-put")) {
t.Errorf("stderr doesn't contain usage. Got:\n%s", err)
}
}
@ -113,8 +113,8 @@ func TestCommandUsage(t *testing.T) {
func TestUploadingChangingDirectory(t *testing.T) {
// TODO(bradfitz):
// $ mkdir /tmp/somedir
// $ cp dev-camput /tmp/somedir
// $ ./dev-camput -file /tmp/somedir/ 2>&1 | tee /tmp/somedir/log
// $ cp dev-pk-put /tmp/somedir
// $ ./dev-pk-put -file /tmp/somedir/ 2>&1 | tee /tmp/somedir/log
// ... verify it doesn't hang.
t.Logf("TODO")
}

View File

@ -28,7 +28,7 @@ import (
type deleteCmd struct{}
func init() {
cmdmain.RegisterCommand("delete", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("delete", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(deleteCmd)
return cmd
})
@ -39,7 +39,7 @@ func (c *deleteCmd) Describe() string {
}
func (c *deleteCmd) Usage() {
cmdmain.Errorf("Usage: camput [globalopts] delete <blobref1> [blobref2]...")
cmdmain.Errorf("Usage: pk-put [globalopts] delete <blobref1> [blobref2]...")
}
func (c *deleteCmd) RunCommand(args []string) error {

View File

@ -14,20 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// TODO(mpl): this doc is not in sync with what the pk-put help outputs. But it should be.
/*
The camput tool mainly pushes blobs, files, and directories. It can also perform various related tasks, such as setting tags, creating permanodes, and creating share blobs.
The pk-put tool mainly pushes blobs, files, and directories. It can also perform various related tasks, such as setting tags, creating permanodes, and creating share blobs.
Usage:
camput [globalopts] <mode> [commandopts] [commandargs]
pk-put [globalopts] <mode> [commandopts] [commandargs]
Modes:
delete: Create and upload a delete claim.
attr: Add, set, or delete a permanode's attribute.
file: Upload file(s).
init: Initialize the camput configuration file. With no option, it tries to use the GPG key found in the default identity secret ring.
init: Initialize the pk-put configuration file. With no option, it tries to use the GPG key found in the default identity secret ring.
permanode: Create and upload a permanode.
rawobj: Upload a custom JSON schema blob.
share: Grant access to a resource by making a "share" blob.
@ -35,31 +37,31 @@ Modes:
Examples:
camput file [opts] <file(s)/director(ies)
camput file --permanode --title='Homedir backup' --tag=backup,homedir $HOME
camput file --filenodes /mnt/camera/DCIM
pk-put file [opts] <file(s)/director(ies)
pk-put file --permanode --title='Homedir backup' --tag=backup,homedir $HOME
pk-put file --filenodes /mnt/camera/DCIM
camput blob <files> (raw, without any metadata)
camput blob --permanode --title='My Blob' --tag=backup,my_blob
camput blob - (read from stdin)
pk-put blob <files> (raw, without any metadata)
pk-put blob --permanode --title='My Blob' --tag=backup,my_blob
pk-put blob - (read from stdin)
camput permanode (create a new permanode)
camput permanode --title="Some Name" --tag=foo,bar (with attributes added)
pk-put permanode (create a new permanode)
pk-put permanode --title="Some Name" --tag=foo,bar (with attributes added)
camput init
camput init --gpgkey=XXXXX
pk-put init
pk-put init --gpgkey=XXXXX
camput share [opts] <blobref to share via haveref>
pk-put share [opts] <blobref to share via haveref>
camput rawobj (debug command)
pk-put rawobj (debug command)
camput attr <permanode> <name> <value> Set attribute
camput attr --add <permanode> <name> <value> Adds attribute (e.g. "tag")
camput attr --del <permanode> <name> [<value>] Deletes named attribute [value
pk-put attr <permanode> <name> <value> Set attribute
pk-put attr --add <permanode> <name> <value> Adds attribute (e.g. "tag")
pk-put attr --del <permanode> <name> [<value>] Deletes named attribute [value
For mode-specific help:
camput <mode> -help
pk-put <mode> -help
Global options:
-help=false: print usage
@ -72,4 +74,4 @@ Global options:
-verbose_http=false: show HTTP request summaries
-version=false: show version
*/
package main // import "perkeep.org/cmd/camput"
package main // import "perkeep.org/cmd/pk-put"

View File

@ -74,7 +74,7 @@ var (
)
func init() {
cmdmain.RegisterCommand("file", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("file", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(fileCmd)
flags.BoolVar(&cmd.makePermanode, "permanode", false, "Create and associate a new permanode for the uploaded file or directory.")
flags.BoolVar(&cmd.filePermanodes, "filenodes", false, "Create (if necessary) content-based permanodes for each uploaded file.")
@ -115,7 +115,7 @@ func (c *fileCmd) Describe() string {
}
func (c *fileCmd) Usage() {
fmt.Fprintf(cmdmain.Stderr, "Usage: camput [globalopts] file [fileopts] <file/director(ies)>\n")
fmt.Fprintf(cmdmain.Stderr, "Usage: pk-put [globalopts] file [fileopts] <file/director(ies)>\n")
}
func (c *fileCmd) Examples() []string {
@ -395,7 +395,7 @@ func (up *Uploader) uploadNode(ctx context.Context, n *node) (*client.PutResult,
case mode&os.ModeNamedPipe != 0: // fifo
bb.SetType("fifo")
default:
return nil, fmt.Errorf("camput.files: unsupported file type %v for file %v", mode, n.fullPath)
return nil, fmt.Errorf("pk-put.files: unsupported file type %v for file %v", mode, n.fullPath)
case fi.IsDir():
ss, err := n.directoryStaticSet()
if err != nil {
@ -436,7 +436,7 @@ func (up *Uploader) statReceiver(n *node) blobserver.StatReceiver {
statReceiver := up.altStatReceiver
if statReceiver == nil {
// TODO(mpl): simplify the altStatReceiver situation as well,
// see TODO in cmd/camput/uploader.go
// see TODO in cmd/pk-put/uploader.go
statReceiver = up.Client
}
if android.IsChild() && n != nil && n.fi.Mode()&os.ModeType == 0 {

View File

@ -46,7 +46,7 @@ type initCmd struct {
}
func init() {
cmdmain.RegisterCommand("init", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("init", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(initCmd)
flags.BoolVar(&cmd.newKey, "newkey", false,
"Automatically generate a new identity in a new secret ring at the default location (~/.config/camlistore/identity-secring.gpg on linux).")
@ -59,11 +59,11 @@ func init() {
}
func (c *initCmd) Describe() string {
return "Initialize the camput configuration file. With no option, it tries to use the GPG key found in the default identity secret ring."
return "Initialize the pk-put configuration file. With no option, it tries to use the GPG key found in the default identity secret ring."
}
func (c *initCmd) Usage() {
usage := "Usage: camput [--server host] init [opts]\n\nExamples:\n"
usage := "Usage: pk-put [--server host] init [opts]\n\nExamples:\n"
for _, v := range c.usageExamples() {
usage += v + "\n"
}
@ -73,10 +73,10 @@ func (c *initCmd) Usage() {
func (c *initCmd) usageExamples() []string {
var examples []string
for _, v := range c.Examples() {
examples = append(examples, "camput init "+v)
examples = append(examples, "pk-put init "+v)
}
return append(examples,
"camput --server=https://localhost:3179 init --userpass=foo:bar --insecure=true")
"pk-put --server=https://localhost:3179 init --userpass=foo:bar --insecure=true")
}
func (c *initCmd) Examples() []string {
@ -106,7 +106,7 @@ func (c *initCmd) initSecretRing() error {
c.secretRing = osutil.SecretRingFile()
}
if _, err := os.Stat(c.secretRing); err != nil {
hint := "\nA GPG key is required, please use 'camput init --newkey'.\n\nOr if you know what you're doing, you can set the global camput flag --secret-keyring, or the CAMLI_SECRET_RING env var, to use your own GPG ring. And --gpgkey=<pubid> or GPGKEY to select which key ID to use."
hint := "\nA GPG key is required, please use 'pk-put init --newkey'.\n\nOr if you know what you're doing, you can set the global pk-put flag --secret-keyring, or the CAMLI_SECRET_RING env var, to use your own GPG ring. And --gpgkey=<pubid> or GPGKEY to select which key ID to use."
return fmt.Errorf("Could not use secret ring file %v: %v.\n%v", c.secretRing, err, hint)
}
return nil

View File

@ -55,7 +55,7 @@ type KvHaveCache struct {
func NewKvHaveCache(gen string) *KvHaveCache {
cleanCacheDir()
fullPath := filepath.Join(osutil.CacheDir(), "camput.havecache."+escapeGen(gen)+".leveldb")
fullPath := filepath.Join(osutil.CacheDir(), "pk-put.havecache."+escapeGen(gen)+".leveldb")
db, err := leveldb.OpenFile(fullPath, nil)
if err != nil {
log.Fatalf("Could not create/open new have cache at %v, %v", fullPath, err)
@ -152,7 +152,7 @@ type KvStatCache struct {
}
func NewKvStatCache(gen string) *KvStatCache {
fullPath := filepath.Join(osutil.CacheDir(), "camput.statcache."+escapeGen(gen)+".leveldb")
fullPath := filepath.Join(osutil.CacheDir(), "pk-put.statcache."+escapeGen(gen)+".leveldb")
db, err := leveldb.OpenFile(fullPath, nil)
if err != nil {
log.Fatalf("Could not create/open new stat cache at %v, %v", fullPath, err)
@ -376,11 +376,11 @@ func cleanCacheDir() {
}
for _, fi := range fis {
if strings.HasPrefix(fi.Name(), "camput.havecache.") {
if strings.HasPrefix(fi.Name(), "pk-put.havecache.") {
haveCache = append(haveCache, fi)
continue
}
if strings.HasPrefix(fi.Name(), "camput.statcache.") {
if strings.HasPrefix(fi.Name(), "pk-put.statcache.") {
statCache = append(statCache, fi)
continue
}

View File

@ -36,7 +36,7 @@ type permanodeCmd struct {
}
func init() {
cmdmain.RegisterCommand("permanode", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("permanode", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(permanodeCmd)
flags.StringVar(&cmd.title, "title", "", "Optional 'title' attribute to set on new permanode")
flags.StringVar(&cmd.tag, "tag", "", "Optional tag(s) to set on new permanode; comma separated.")
@ -51,7 +51,7 @@ func (c *permanodeCmd) Describe() string {
}
func (c *permanodeCmd) Usage() {
cmdmain.Errorf("Usage: camput [globalopts] permanode [permanodeopts]\n")
cmdmain.Errorf("Usage: pk-put [globalopts] permanode [permanodeopts]\n")
}
func (c *permanodeCmd) Examples() []string {

View File

@ -31,7 +31,7 @@ type rawCmd struct {
}
func init() {
cmdmain.RegisterCommand("rawobj", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("rawobj", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(rawCmd)
flags.StringVar(&cmd.vals, "vals", "", "Pipe-separated key=value properties")
flags.BoolVar(&cmd.signed, "signed", true, "whether to sign the JSON object")
@ -44,7 +44,7 @@ func (c *rawCmd) Describe() string {
}
func (c *rawCmd) Usage() {
cmdmain.Errorf("Usage: camput [globalopts] rawobj [rawopts]\n")
cmdmain.Errorf("Usage: pk-put [globalopts] rawobj [rawopts]\n")
}
func (c *rawCmd) Examples() []string {

View File

@ -27,14 +27,14 @@ import (
type removeCmd struct{}
func init() {
cmdmain.RegisterCommand("remove", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("remove", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(removeCmd)
return cmd
})
}
func (c *removeCmd) Usage() {
fmt.Fprintf(cmdmain.Stderr, `Usage: camput remove <blobref(s)>
fmt.Fprintf(cmdmain.Stderr, `Usage: pk-put remove <blobref(s)>
This command is for debugging only. You're not expected to use it in practice.
`)

View File

@ -35,7 +35,7 @@ type shareCmd struct {
}
func init() {
cmdmain.RegisterCommand("share", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("share", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(shareCmd)
flags.StringVar(&cmd.search, "search", "", "share a search result, rather than a single blob. Should be the JSON representation of a search.SearchQuery (see https://perkeep.org/pkg/search/#SearchQuery for details). Exclusive with, and overrides the <blobref> parameter.")
flags.BoolVar(&cmd.transitive, "transitive", false, "share everything reachable from the given blobref")
@ -49,7 +49,7 @@ func (c *shareCmd) Describe() string {
}
func (c *shareCmd) Usage() {
fmt.Fprintf(cmdmain.Stderr, `Usage: camput share [opts] [<blobref>] # blobRef of a file or directory
fmt.Fprintf(cmdmain.Stderr, `Usage: pk-put share [opts] [<blobref>] # blobRef of a file or directory
`)
}

View File

@ -33,7 +33,7 @@ type claimsCmd struct {
}
func init() {
cmdmain.RegisterCommand("claims", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("claims", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(claimsCmd)
flags.StringVar(&cmd.server, "server", "", "Server to fetch claims from. "+serverFlagHelp)
flags.StringVar(&cmd.attr, "attr", "", "Filter claims about a specific attribute. If empty, all claims are returned.")

View File

@ -51,7 +51,7 @@ type dbinitCmd struct {
}
func init() {
cmdmain.RegisterCommand("dbinit", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("dbinit", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(dbinitCmd)
flags.StringVar(&cmd.user, "user", "root", "Admin user.")
flags.StringVar(&cmd.password, "password", "", "Admin password.")

View File

@ -48,7 +48,7 @@ type debugSubMode struct {
type debugCmd struct{}
func init() {
cmdmain.RegisterCommand("debug", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("debug", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return new(debugCmd)
})
}

View File

@ -37,7 +37,7 @@ type desCmd struct {
}
func init() {
cmdmain.RegisterCommand("describe", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("describe", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(desCmd)
flags.StringVar(&cmd.server, "server", "", "Server to query. "+serverFlagHelp)
flags.StringVar(&cmd.at, "at", "", "Describe at what point in time. RFC3339 only for now. Empty string means current time.")

View File

@ -31,7 +31,7 @@ type discoCmd struct {
}
func init() {
cmdmain.RegisterCommand("discovery", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("discovery", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(discoCmd)
flags.StringVar(&cmd.server, "server", "", "Server to do discovery against. "+serverFlagHelp)
flags.BoolVar(&cmd.httpVer, "httpversion", false, "discover the HTTP version")

View File

@ -35,7 +35,7 @@ type reindexdpCmd struct {
}
func init() {
cmdmain.RegisterCommand("reindex-diskpacked",
cmdmain.RegisterMode("reindex-diskpacked",
func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(reindexdpCmd)
flags.BoolVar(&cmd.overwrite, "overwrite", false,

View File

@ -31,7 +31,7 @@ import (
type dumpconfigCmd struct{}
func init() {
cmdmain.RegisterCommand("dumpconfig", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("dumpconfig", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return new(dumpconfigCmd)
})
}

View File

@ -38,7 +38,7 @@ var envMap = map[string]func() string{
type envCmd struct{}
func init() {
cmdmain.RegisterCommand("env", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("env", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return new(envCmd)
})
}

View File

@ -37,7 +37,7 @@ type googinitCmd struct {
}
func init() {
cmdmain.RegisterCommand("googinit", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("googinit", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(googinitCmd)
flags.StringVar(&cmd.storageType, "type", "", "Storage type: drive or cloud")
return cmd

View File

@ -32,7 +32,7 @@ type indexCmd struct {
}
func init() {
cmdmain.RegisterCommand("index", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("index", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(indexCmd)
flags.BoolVar(&cmd.wipe, "wipe", false, "Erase and recreate all discovered indexes. NOOP for now.")
if debug, _ := strconv.ParseBool(os.Getenv("CAMLI_DEBUG")); debug {

View File

@ -39,7 +39,7 @@ type listCmd struct {
}
func init() {
cmdmain.RegisterCommand("list", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("list", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := &listCmd{
syncCmd: &syncCmd{
dest: "stdout",

View File

@ -34,7 +34,7 @@ type makeStaticCmd struct {
}
func init() {
cmdmain.RegisterCommand("makestatic", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("makestatic", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(makeStaticCmd)
flags.StringVar(&cmd.server, "server", "", "Server to search. "+serverFlagHelp)
return cmd

View File

@ -33,7 +33,7 @@ type packBlobsCmd struct {
}
func init() {
cmdmain.RegisterCommand("packblobs", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("packblobs", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(packBlobsCmd)
flags.StringVar(&cmd.server, "server", "", "Server to search. "+serverFlagHelp)
return cmd

74
cmd/pk/put.go Normal file
View File

@ -0,0 +1,74 @@
/*
Copyright 2018 The Perkeep Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"perkeep.org/internal/osutil"
"perkeep.org/pkg/cmdmain"
)
type putCmd struct {
binName string // the executable that is actually called, i.e. "pk-put".
}
func init() {
cmdmain.RegisterCommand("put", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return &putCmd{
binName: "pk-put",
}
// TODO(mpl): do cmdmain.ExtraFlagRegistration = client.AddFlags somewhere, so
// pk has same global flags as pk-put? (which I think it already does anyway).
})
}
func (c *putCmd) Describe() string {
return "Create and upload blobs to a server."
}
func (c *putCmd) Usage() {
panic("pk put Usage should never get called, as we should always end up calling either pk's or pk-put's usage")
}
func (c *putCmd) RunCommand(args []string) error {
// RunCommand is only implemented to satisfy the CommandRunner interface.
panic("pk put RunCommand should never get called, as pk is supposed to invoke pk-put instead.")
}
// LookPath returns the full path to the executable that "pk put" actually
// calls, i.e. "pk-put".
func (c *putCmd) LookPath() (string, error) {
fullPath, err := exec.LookPath(c.binName)
if err == nil {
return fullPath, nil
}
// If not in PATH, also try in bin dir of Perkeep source tree
perkeepPath, err := osutil.GoPackagePath("perkeep.org")
if err != nil {
return "", fmt.Errorf("full path for %v command was not found in either PATH or the bin dir of the source tree", c.binName)
}
fullPath = filepath.Join(perkeepPath, "bin", c.binName)
if _, err := os.Stat(fullPath); err != nil {
return "", fmt.Errorf("full path for %v command was not found in either PATH or %v", c.binName, filepath.Join(perkeepPath, "bin"))
}
return fullPath, nil
}

View File

@ -41,7 +41,7 @@ type searchCmd struct {
}
func init() {
cmdmain.RegisterCommand("search", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("search", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(searchCmd)
flags.StringVar(&cmd.server, "server", "", "Server to search. "+serverFlagHelp)
flags.IntVar(&cmd.limit, "limit", 0, "Limit number of results. 0 is default. Negative means no limit.")

View File

@ -31,7 +31,7 @@ import (
type searchDocCmd struct{}
func init() {
cmdmain.RegisterCommand("searchdoc", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("searchdoc", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return new(searchDocCmd)
})
}

View File

@ -36,10 +36,10 @@ type searchNamesSetCmd struct{}
func init() {
osutil.AddSecretRingFlag()
cmdmain.RegisterCommand("named-search-get", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("named-search-get", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return new(searchNamesGetCmd)
})
cmdmain.RegisterCommand("named-search-set", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("named-search-set", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return new(searchNamesSetCmd)
})
}

View File

@ -55,7 +55,7 @@ type syncCmd struct {
}
func init() {
cmdmain.RegisterCommand("sync", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("sync", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(syncCmd)
flags.StringVar(&cmd.src, "src", "", "Source blobserver. "+serverFlagHelp)
flags.StringVar(&cmd.dest, "dest", "", "Destination blobserver (same format as src), or 'stdout' to just enumerate the --src blobs to stdout.")

View File

@ -32,7 +32,7 @@ import (
type whoamiCmd struct{}
func init() {
cmdmain.RegisterCommand("whoami", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("whoami", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return new(whoamiCmd)
})
}

View File

@ -41,7 +41,7 @@ type getCmd struct {
}
func init() {
cmdmain.RegisterCommand("get", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("get", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := &getCmd{
env: NewCopyEnv(),
}

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// This file adds the "put" subcommand to devcam, to run camput against the dev server.
// This file adds the "put" subcommand to devcam, to run pk-put against the dev server.
package main
@ -40,7 +40,7 @@ type putCmd struct {
}
func init() {
cmdmain.RegisterCommand("put", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("put", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := &putCmd{
env: NewCopyEnv(),
}
@ -53,7 +53,7 @@ func init() {
}
func (c *putCmd) Usage() {
fmt.Fprintf(cmdmain.Stderr, "Usage: devcam put [put_opts] camput_args\n")
fmt.Fprintf(cmdmain.Stderr, "Usage: devcam put [put_opts] pk-put_args\n")
}
func (c *putCmd) Examples() []string {
@ -63,7 +63,7 @@ func (c *putCmd) Examples() []string {
}
func (c *putCmd) Describe() string {
return "run camput in dev mode."
return "run pk-put in dev mode."
}
func (c *putCmd) RunCommand(args []string) error {
@ -72,8 +72,8 @@ func (c *putCmd) RunCommand(args []string) error {
return cmdmain.UsageError(fmt.Sprint(err))
}
if !*noBuild {
if err := build(filepath.Join("cmd", "camput")); err != nil {
return fmt.Errorf("Could not build camput: %v", err)
if err := build(filepath.Join("cmd", "pk-put")); err != nil {
return fmt.Errorf("Could not build pk-put: %v", err)
}
}
c.env.SetCamdevVars(c.altkey)
@ -88,7 +88,7 @@ func (c *putCmd) RunCommand(args []string) error {
blobserver = strings.Replace(blobserver, "http://", "https://", 1)
}
cmdBin := filepath.Join("bin", "camput")
cmdBin := filepath.Join("bin", "pk-put")
cmdArgs := []string{
"-verbose=" + strconv.FormatBool(*cmdmain.FlagVerbose || !quiet),
"-server=" + blobserver,

View File

@ -16,7 +16,7 @@ limitations under the License.
/*
The devcam tool is a collection of wrappers around the camlistore programs
(camistored, camput, pk...) which take care of setup and configuration,
(camistored, pk-put, pk...) which take care of setup and configuration,
so they can be used by developers to ease hacking on camlistore.
Usage:
@ -26,7 +26,7 @@ Usage:
Modes:
get: run camget in dev mode.
put: run camput in dev mode.
put: run pk-put in dev mode.
server: run the stand-alone camlistored in dev mode.
Examples:

View File

@ -85,7 +85,7 @@ type hookCmd struct {
}
func init() {
cmdmain.RegisterCommand("hook", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("hook", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := &hookCmd{}
flags.BoolVar(&cmd.verbose, "verbose", false, "Be verbose.")
// TODO(mpl): "-w" flag to run gofmt -w and devcam fixv -w. for now just print instruction.

View File

@ -36,7 +36,7 @@ type toolCmd struct {
}
func init() {
cmdmain.RegisterCommand("tool", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("tool", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := &toolCmd{
env: NewCopyEnv(),
}

View File

@ -46,7 +46,7 @@ type mountCmd struct {
const mountpoint = "/tmp/pk-mount-dir"
func init() {
cmdmain.RegisterCommand("mount", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("mount", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := &mountCmd{
env: NewCopyEnv(),
}

View File

@ -38,7 +38,7 @@ var (
type reviewCmd struct{}
func init() {
cmdmain.RegisterCommand("review", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("review", func(flags *flag.FlagSet) cmdmain.CommandRunner {
return new(reviewCmd)
})
}

View File

@ -87,7 +87,7 @@ type serverCmd struct {
}
func init() {
cmdmain.RegisterCommand("server", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("server", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := &serverCmd{
env: NewCopyEnv(),
}

View File

@ -39,7 +39,7 @@ type testCmd struct {
}
func init() {
cmdmain.RegisterCommand("test", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmdmain.RegisterMode("test", func(flags *flag.FlagSet) cmdmain.CommandRunner {
cmd := new(testCmd)
flags.BoolVar(&cmd.short, "short", false, "Use '-short' with go test.")
flags.BoolVar(&cmd.precommit, "precommit", true, "Run the pre-commit githook as part of tests.")

View File

@ -62,4 +62,4 @@ on the [mailing list](https://groups.google.com/group/camlistore).
## Video tutorials {#tutorials}
* 2014-03, [Getting started with Camlistore](https://www.youtube.com/watch?v=RUv-8PhnNp8)
* 2014-03, [Getting started with camput and the Camlistore client tools](https://www.youtube.com/watch?v=DdccwBFc5ZI)
* 2014-03, [Getting started with pk put and the Camlistore client tools](https://www.youtube.com/watch?v=DdccwBFc5ZI)

View File

@ -1,6 +1,6 @@
# Configuring a client
The various clients (camput, camget, pk-mount...) use a common JSON config
The various clients (pk put, camget, pk-mount...) use a common JSON config
file. This page documents the configuration parameters in that file. Run
`pk env clientconfig` to see the default location for that file
(**$HOME/.config/camlistore/client-config.json** on linux). In the following
@ -8,7 +8,7 @@ let **$CONFIGDIR** be the location returned by `pk env configdir`.
## Generating a default config file
Run `camput init`.
Run `pk put init`.
On unix,
@ -35,14 +35,14 @@ should look something like:
### Top-level keys
* `identity`: your GPG fingerprint. Run `camput init` for help on how to
* `identity`: your GPG fingerprint. Run `pk put init` for help on how to
generate a new keypair.
* `identitySecretRing`: Optional. If non-empty, it specifies the location of
your GPG secret keyring. Defaults to **$CONFIGDIR/identity-secring.gpg**. Run
`camput init` for help on how to generate a new keypair.
`pk put init` for help on how to generate a new keypair.
* `ignoredFiles`: Optional. The list of of files that camput should ignore and
* `ignoredFiles`: Optional. The list of of files that pk put should ignore and
not try to upload.
### Servers

View File

@ -43,7 +43,7 @@ valid.
launched camlistored.
`CAMLI_DEBUG` (bool)
: Used by camlistored and camput to enable additional commandline options.
: Used by camlistored and pk put to enable additional commandline options.
Used in pkg/schema to enable additional logging.
`CAMLI_DEBUG_CONFIG` (bool)
@ -174,7 +174,7 @@ files to be ignored by [pkg/client](/pkg/client) when uploading.
`CAMLI_NO_FILE_DUP_SEARCH` (bool)
: This will cause the search-for-exists-before-upload step to be skipped when
camput is uploading files.
pk put is uploading files.
`CAMLI_PPROF_START` (string)
: Filename base to write a "<base>.cpu" and "<base>.mem" profile out

View File

@ -164,7 +164,7 @@ See the
[serverconfig.Publish](https://perkeep.org/pkg/types/serverconfig/#Publish)
type for all the configuration parameters.
One can create any permanode with camput or the UI, and set its camliRoot
One can create any permanode with pk put or the UI, and set its camliRoot
attribute to the value set in the config, to use it as the root permanode for
publishing.

View File

@ -44,7 +44,7 @@ in your [server config](/doc/server-config.md).
Now I want to share Hi.txt with you, so I create a share blob:
camput share --transitive sha1-0e5e60f367cc8156ae48198c496b2b2ebdf5313d
pk put share --transitive sha1-0e5e60f367cc8156ae48198c496b2b2ebdf5313d
I've created this, and its name is `sha1-102758fb54521cb6540d256098e7c0f1625b33e3`

View File

@ -38,14 +38,14 @@ pieces and use cases, and where they're at.</p>
<table class='status'>
<tr><th>Item</th><th>Status</th><th>Notes</th></tr>
<tr><td><a href="/cmd/camput">camput</a></td><td>99%</td><td>the kitchen sink tool to inject content into a blobserver. Quite good now. Also <a href="https://plus.google.com/u/0/115863474911002159675/posts/DWmyygSrvt7">used by the Android client</a>, as a child process.</td></tr>
<tr><td><a href="/cmd/pk put">pk put</a></td><td>99%</td><td>the kitchen sink tool to inject content into a blobserver. Quite good now. Also <a href="https://plus.google.com/u/0/115863474911002159675/posts/DWmyygSrvt7">used by the Android client</a>, as a child process.</td></tr>
<tr><td><a href="/cmd/camget">camget</a></td><td>10%</td><td>tool to retrieve content from a blobserver.</td></tr>
<tr><td><a href="/cmd/pk-mount">pk-mount</a></td><td>read-only</td><td>FUSE mounting. Works on Linux and OS X.</td></tr>
<tr><td><a href="/gw/clients/android">Android Uploader</a></td><td>90%</td><td>UI is kinda ugly in spots but it works and
optionally backs up your SD card (photos, etc) to your blob server. Uses camput.
optionally backs up your SD card (photos, etc) to your blob server. Uses pk put.
Can also work in "Share with Perkeep" mode, one resource at a
time.</td></tr>

View File

@ -115,7 +115,7 @@ starts with <code>{</code> and is <a href="http://json.org/">valid JSON</a> in i
on mutating an object (by all creating new, signed mutation schema blobs),
the owner ultimately decides the policies on how the mutations are respected.</p>
<p>Example permanode blob: (as generated with <code><a href="/cmd/camput">camput</a> --permanode</code>)</p>
<p>Example permanode blob: (as generated with <code><a href="/cmd/pk put">pk put</a> --permanode</code>)</p>
<pre class='sty' style="overflow: auto;">{"camliVersion": 1,
"camliSigner": "sha1-c4da9d771661563a27704b91b67989e7ea1e50b8",

View File

@ -7,7 +7,7 @@ are various stages of implementation (as of 2013-06-12).
* **Filesystem backups**: easy initial use case. Since you can easily put
[files & directories and such](/doc/schema/) in Perkeep with
[camput](/cmd/camput), you can use Perkeep for your backups. Incremental
[pk put](/cmd/pk put), you can use Perkeep for your backups. Incremental
backups are basically free.
* **Efficient remote filesystem**: should be easy to do an aggressively caching

View File

@ -54,7 +54,7 @@ var (
all = flag.Bool("all", false, "Force rebuild of everything (go install -a)")
race = flag.Bool("race", false, "Build race-detector version of binaries (they will run slowly)")
verbose = flag.Bool("v", strings.Contains(os.Getenv("CAMLI_DEBUG_X"), "makego"), "Verbose mode")
targets = flag.String("targets", "", "Optional comma-separated list of targets (i.e go packages) to build and install. '*' builds everything. Empty builds defaults for this platform. Example: perkeep.org/server/camlistored,perkeep.org/cmd/camput")
targets = flag.String("targets", "", "Optional comma-separated list of targets (i.e go packages) to build and install. '*' builds everything. Empty builds defaults for this platform. Example: perkeep.org/server/camlistored,perkeep.org/cmd/pk-put")
quiet = flag.Bool("quiet", false, "Don't print anything unless there's a failure.")
buildARCH = flag.String("arch", runtime.GOARCH, "Architecture to build for.")
buildOS = flag.String("os", runtime.GOOS, "Operating system to build for.")
@ -107,7 +107,7 @@ func main() {
targs := []string{
"perkeep.org/dev/devcam",
"perkeep.org/cmd/camget",
"perkeep.org/cmd/camput",
"perkeep.org/cmd/pk-put",
"perkeep.org/cmd/pk",
"perkeep.org/cmd/pk-deploy",
"perkeep.org/server/camlistored",

View File

@ -425,7 +425,7 @@ func packBinaries(ctxDir string) {
binaries := map[string]bool{
exeName("camlistored"): false,
exeName("camget"): false,
exeName("camput"): false,
exeName("pk-put"): false,
exeName("pk"): false,
exeName("publisher"): false,
}

View File

@ -286,7 +286,7 @@ func TLSConfig() (*tls.Config, error) {
return cfg, nil
}
// NoteFileUploaded is a hook for camput to report that a file
// NoteFileUploaded is a hook for pk-put to report that a file
// was uploaded. TODO: move this to pkg/client/android probably.
func NoteFileUploaded(fullPath string, uploaded bool) {
if !IsChild() {
@ -302,7 +302,7 @@ func NoteFileUploaded(fullPath string, uploaded bool) {
// StatusReceiver is a blobserver.StatReceiver wrapper that
// reports the full filename path and size of uploaded blobs.
// The android app wrapping camput watches stdout for this, for progress bars.
// The android app wrapping pk-put watches stdout for this, for progress bars.
type StatusReceiver struct {
Sr blobserver.StatReceiver
Path string

View File

@ -114,8 +114,8 @@ type Client struct {
insecureAnyTLSCert bool
initIgnoredFilesOnce sync.Once
// list of files that camput should ignore.
// Defaults to empty, but camput init creates a config with a non
// list of files that pk-put should ignore.
// Defaults to empty, but pk-put init creates a config with a non
// empty list.
// See IsIgnoredFile for the matching rules.
ignoredFiles []string
@ -255,7 +255,7 @@ func (c *Client) NewPathClient(path string) (*Client, error) {
// TransportConfig contains options for how HTTP requests are made.
type TransportConfig struct {
// Proxy optionally specifies the Proxy for the transport. Useful with
// camput for debugging even localhost requests.
// pk-put for debugging even localhost requests.
Proxy func(*http.Request) (*url.URL, error)
Verbose bool // Verbose enables verbose logging of HTTP requests.
}
@ -1487,7 +1487,7 @@ func (c *Client) UploadPlannedPermanode(ctx context.Context, key string, sigTime
return c.uploadString(ctx, signed, true)
}
// IsIgnoredFile returns whether the file at fullpath should be ignored by camput.
// IsIgnoredFile returns whether the file at fullpath should be ignored by pk-put.
// The fullpath is checked against the ignoredFiles list, trying the following rules in this order:
// 1) star-suffix style matching (.e.g *.jpg).
// 2) Shell pattern match as done by http://golang.org/pkg/path/filepath/#Match

View File

@ -93,9 +93,9 @@ func (c *Client) parseConfig() {
if c != nil && c.isSharePrefix {
return
}
errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'camput init' to generate it.", configPath)
errMsg := fmt.Sprintf("Client configuration file %v does not exist. See 'pk-put init' to generate it.", configPath)
if keyID := serverKeyId(); keyID != "" {
hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'camput init -gpgkey %v'", keyID, osutil.UserServerConfigPath(), keyID)
hint := fmt.Sprintf("\nThe key id %v was found in the server config %v, so you might want:\n'pk-put init -gpgkey %v'", keyID, osutil.UserServerConfigPath(), keyID)
errMsg += hint
}
log.Fatal(errMsg)
@ -220,7 +220,7 @@ func printConfigChangeHelp(conf jsonconfig.Obj) {
}
}
if oldConfig {
configChangedMsg += "Please see https://perkeep.org/doc/client-config, or use camput init to recreate a default one."
configChangedMsg += "Please see https://perkeep.org/doc/client-config, or use pk-put init to recreate a default one."
log.Print(configChangedMsg)
}
}
@ -414,7 +414,7 @@ func (c *Client) initSignerPublicKeyBlobref() {
configOnce.Do(parseConfig)
keyID = config.Identity
if keyID == "" {
log.Fatalf("No 'identity' key in JSON configuration file %q; have you run \"camput init\"?", osutil.UserClientConfigPath())
log.Fatalf("No 'identity' key in JSON configuration file %q; have you run \"pk-put init\"?", osutil.UserClientConfigPath())
}
}
keyRing := c.SecretRingFile()

View File

@ -447,7 +447,7 @@ type FileUploadOptions struct {
// the server, they're not uploaded.
//
// Note: this method is still a work in progress, and might change to accommodate
// the needs of camput file.
// the needs of pk-put file.
func (c *Client) UploadFile(ctx context.Context, filename string, contents io.Reader, opts *FileUploadOptions) (blob.Ref, error) {
fileMap := schema.NewFileMap(filename)
if opts != nil && opts.FileInfo != nil {
@ -483,7 +483,7 @@ func (c *Client) UploadFile(ctx context.Context, filename string, contents io.Re
return schema.WriteFileMap(ctx, c, fileMap, contents)
}
// TODO(mpl): replace up.wholeFileDigest in camput with c.wholeRef maybe.
// TODO(mpl): replace up.wholeFileDigest in pk-put with c.wholeRef maybe.
// wholeRef returns the blob ref(s) of the regular file's contents
// as if it were one entire blob (ignoring blob size limits).

View File

@ -24,8 +24,10 @@ import (
"io"
"log"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"sync"
"perkeep.org/pkg/buildinfo"
@ -68,6 +70,9 @@ var (
modeCommand = make(map[string]CommandRunner)
modeFlags = make(map[string]*flag.FlagSet)
wantHelp = make(map[string]*bool)
// asNewCommand stores whether the mode should actually be run as a new
// independent command.
asNewCommand = make(map[string]bool)
// Indirections for replacement by tests
Stderr io.Writer = os.Stderr
@ -93,6 +98,13 @@ type CommandRunner interface {
RunCommand(args []string) error
}
// ExecRunner is the type that a command mode should implement when that mode
// just calls a new executable that will run as a new command.
type ExecRunner interface {
CommandRunner
LookPath() (string, error)
}
type exampler interface {
Examples() []string
}
@ -101,9 +113,9 @@ type describer interface {
Describe() string
}
// RegisterCommand adds a mode to the list of modes for the main command.
// RegisterMode adds a mode to the list of modes for the main command.
// It is meant to be called in init() for each subcommand.
func RegisterCommand(mode string, makeCmd func(Flags *flag.FlagSet) CommandRunner) {
func RegisterMode(mode string, makeCmd func(Flags *flag.FlagSet) CommandRunner) {
if _, dup := modeCommand[mode]; dup {
log.Fatalf("duplicate command %q registered", mode)
}
@ -117,6 +129,15 @@ func RegisterCommand(mode string, makeCmd func(Flags *flag.FlagSet) CommandRunne
modeCommand[mode] = makeCmd(flags)
}
// RegisterCommand adds a mode to the list of modes for the main command, and
// also specifies that this mode is just another executable that runs as a new
// cmdmain command. The executable to run is determined by the LookPath implementation
// for this mode.
func RegisterCommand(mode string, makeCmd func(Flags *flag.FlagSet) CommandRunner) {
RegisterMode(mode, makeCmd)
asNewCommand[mode] = true
}
func hasFlags(flags *flag.FlagSet) bool {
any := false
flags.VisitAll(func(*flag.Flag) {
@ -249,10 +270,22 @@ func Main() {
usage(fmt.Sprintf("Unknown mode %q", mode))
}
if _, ok := asNewCommand[mode]; ok {
runAsNewCommand(cmd, mode)
return
}
cmdFlags := modeFlags[mode]
cmdFlags.SetOutput(Stderr)
err := cmdFlags.Parse(args[1:])
if err != nil {
// We want -h to behave as -help, but without having to define another flag for
// it, so we handle it here.
// TODO(mpl): maybe even remove -help and just let them both be handled here?
if err == flag.ErrHelp {
help(mode)
return
}
err = ErrUsage
} else {
if *wantHelp[mode] {
@ -285,6 +318,39 @@ func Main() {
}
}
// runAsNewCommand runs the executable specified by cmd's LookPath, which means
// cmd must implement the ExecRunner interface. The executable must be a binary of
// a program that runs Main.
func runAsNewCommand(cmd CommandRunner, mode string) {
execCmd, ok := cmd.(ExecRunner)
if !ok {
panic(fmt.Sprintf("%v does not implement ExecRunner", mode))
}
cmdPath, err := execCmd.LookPath()
if err != nil {
Errorf("Error: %v\n", err)
Exit(2)
}
allArgs := shiftFlags(mode)
if err := runExec(cmdPath, allArgs, newCopyEnv()); err != nil {
panic(fmt.Sprintf("running %v should have ended with an os.Exit, and not leave us with that error: %v", cmdPath, err))
}
}
// shiftFlags prepends all the arguments (global flags) passed before the given
// mode to the list of arguments after that mode, and returns that list.
func shiftFlags(mode string) []string {
modePos := 0
for k, v := range os.Args {
if v == mode {
modePos = k
break
}
}
globalFlags := os.Args[1:modePos]
return append(globalFlags, os.Args[modePos+1:]...)
}
// Errorf prints to Stderr, regardless of FlagVerbose.
func Errorf(format string, args ...interface{}) {
fmt.Fprintf(Stderr, format, args...)
@ -304,3 +370,54 @@ func Logf(format string, v ...interface{}) {
}
logger.Printf(format, v...)
}
// sysExec is set to syscall.Exec on platforms that support it.
var sysExec func(argv0 string, argv []string, envv []string) (err error)
// runExec execs bin. If the platform doesn't support exec, it runs it and waits
// for it to finish.
func runExec(bin string, args []string, e *env) error {
if sysExec != nil {
sysExec(bin, append([]string{filepath.Base(bin)}, args...), e.flat())
}
cmd := exec.Command(bin, args...)
cmd.Env = e.flat()
cmd.Stdout = Stdout
cmd.Stderr = Stderr
return cmd.Run()
}
type env struct {
m map[string]string
order []string
}
func (e *env) set(k, v string) {
_, dup := e.m[k]
e.m[k] = v
if !dup {
e.order = append(e.order, k)
}
}
func (e *env) flat() []string {
vv := make([]string, 0, len(e.order))
for _, k := range e.order {
if v, ok := e.m[k]; ok {
vv = append(vv, k+"="+v)
}
}
return vv
}
func newCopyEnv() *env {
e := &env{make(map[string]string), nil}
for _, kv := range os.Environ() {
eq := strings.Index(kv, "=")
if eq > 0 {
e.set(kv[:eq], kv[eq+1:])
}
}
return e
}

27
pkg/cmdmain/exec.go Normal file
View File

@ -0,0 +1,27 @@
// +build !windows
/*
Copyright 2018 The Perkeep Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmdmain
import (
"syscall"
)
func init() {
sysExec = syscall.Exec
}

View File

@ -69,9 +69,9 @@ const (
// If set, it should be of a "file" schema blob referencing the tweets.zip
// file that Twitter makes available for the full archive download.
// The Twitter API doesn't go back forever in time, so if you started using
// the Perkeep importer too late, you need to "camput file tweets.zip"
// the Perkeep importer too late, you need to "pk-put file tweets.zip"
// once downloading it from Twitter, and then:
// $ camput attr <acct-permanode> twitterArchiveZipFileRef <zip-fileref>
// $ pk-put attr <acct-permanode> twitterArchiveZipFileRef <zip-fileref>
// ... and re-do an import.
acctAttrTweetZip = "twitterArchiveZipFileRef"

View File

@ -63,9 +63,9 @@ func TestCamgetSymlink(t *testing.T) {
t.Fatalf("os.Symlink(): %v", err)
}
out := test.MustRunCmd(t, w.Cmd("camput", "file", srcDir))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", srcDir))
// TODO(mpl): rm call and delete pkg.
asserts.ExpectBool(t, true, out != "", "camput")
asserts.ExpectBool(t, true, out != "", "pk-put")
br := strings.Split(out, "\n")[0]
dstDir, err := ioutil.TempDir("", "camget-test-")
if err != nil {
@ -106,7 +106,7 @@ func TestCamgetFIFO(t *testing.T) {
// Upload the fifo
w := test.GetWorld(t)
out := test.MustRunCmd(t, w.Cmd("camput", "file", fifo))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", fifo))
br := strings.Split(out, "\n")[0]
// Try and get it back
@ -139,7 +139,7 @@ func TestCamgetSocket(t *testing.T) {
// Upload the socket
w := test.GetWorld(t)
out := test.MustRunCmd(t, w.Cmd("camput", "file", socket))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", socket))
br := strings.Split(out, "\n")[0]
// Try and get it back
@ -189,7 +189,7 @@ func TestCamgetFile(t *testing.T) {
}
w := test.GetWorld(t)
out := test.MustRunCmd(t, w.Cmd("camput", "file", filename))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", filename))
br := strings.Split(out, "\n")[0]
_ = test.MustRunCmd(t, w.Cmd("camget", "-o", outDir, "-contents", br))

View File

@ -34,7 +34,7 @@ import (
)
// Test that running:
// $ camput permanode
// $ pk-put permanode
// ... creates and uploads a permanode, and that we can camget it back.
func TestCamputPermanode(t *testing.T) {
w := test.GetWorld(t)
@ -58,7 +58,7 @@ func TestCamputPermanode(t *testing.T) {
func TestWebsocketQuery(t *testing.T) {
w := test.GetWorld(t)
pn := w.NewPermanode(t)
test.MustRunCmd(t, w.Cmd("camput", "attr", pn.String(), "tag", "foo"))
test.MustRunCmd(t, w.Cmd("pk-put", "attr", pn.String(), "tag", "foo"))
check := func(err error) {
if err != nil {
@ -123,10 +123,10 @@ func TestWebsocketQuery(t *testing.T) {
func TestInternalHandler(t *testing.T) {
w := test.GetWorld(t)
tests := map[string]int{
"/no-http-storage/": 401,
"/no-http-handler/": 401,
"/good-status/": 200,
"/bs-and-maybe-also-index/camli": 400,
"/no-http-storage/": 401,
"/no-http-handler/": 401,
"/good-status/": 200,
"/bs-and-maybe-also-index/camli": 400,
"/bs/camli/sha1-b2201302e129a4396a323cb56283cddeef11bbe8": 404,
"/no-http-storage/camli/sha1-b2201302e129a4396a323cb56283cddeef11bbe8": 401,
}
@ -178,8 +178,8 @@ func mustWriteFile(t *testing.T, path, contents string) {
}
}
// Run camput in the environment it runs in under the Android app.
// This matches how camput is used in UploadThread.java.
// Run pk-put in the environment it runs in under the Android app.
// This matches how pk-put is used in UploadThread.java.
func TestAndroidCamputFile(t *testing.T) {
w := test.GetWorld(t)
// UploadThread.java sets:
@ -193,7 +193,7 @@ func TestAndroidCamputFile(t *testing.T) {
"CAMPUT_ANDROID_OUTPUT=1",
"CAMLI_CACHE_DIR=" + cacheDir,
}
cmd := w.CmdWithEnv("camput",
cmd := w.CmdWithEnv("pk-put",
env,
"--server="+w.ServerBaseURL(),
"file",
@ -259,10 +259,10 @@ func TestAndroidCamputFile(t *testing.T) {
}()
select {
case <-time.After(5 * time.Second):
t.Fatal("timeout waiting for camput to end")
t.Fatal("timeout waiting for pk-put to end")
case err := <-waitc:
if err != nil {
t.Errorf("camput exited uncleanly: %v", err)
t.Errorf("pk-put exited uncleanly: %v", err)
}
}
}

View File

@ -50,7 +50,7 @@ func mkTmpFIFO(t *testing.T) (path string, cleanup func()) {
return
}
// Test that `camput' can upload fifos correctly.
// Test that `pk-put' can upload fifos correctly.
func TestCamputFIFO(t *testing.T) {
if runtime.GOOS == "windows" {
t.SkipNow()
@ -61,7 +61,7 @@ func TestCamputFIFO(t *testing.T) {
// Can we successfully upload a fifo?
w := test.GetWorld(t)
out := test.MustRunCmd(t, w.Cmd("camput", "file", fifo))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", fifo))
br := strings.Split(out, "\n")[0]
out = test.MustRunCmd(t, w.Cmd("camget", br))
@ -88,7 +88,7 @@ func mkTmpSocket(t *testing.T) (path string, cleanup func()) {
return
}
// Test that `camput' can upload sockets correctly.
// Test that `pk-put' can upload sockets correctly.
func TestCamputSocket(t *testing.T) {
if runtime.GOOS == "windows" {
t.SkipNow()
@ -99,45 +99,45 @@ func TestCamputSocket(t *testing.T) {
// Can we successfully upload a socket?
w := test.GetWorld(t)
out := test.MustRunCmd(t, w.Cmd("camput", "file", socket))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", socket))
br := strings.Split(out, "\n")[0]
out = test.MustRunCmd(t, w.Cmd("camget", br))
t.Logf("Retrieved stored socket schema: %s", out)
}
// Test that camput twice on the same file only uploads once.
// Test that pk-put twice on the same file only uploads once.
func TestCamputUploadOnce(t *testing.T) {
w := test.GetWorld(t)
camputCmd := func() *exec.Cmd {
pkputCmd := func() *exec.Cmd {
// Use --contents_only because if test is run from devcam,
// server-config.json is going to be the one from within the fake gopath,
// hence with a different cTime and with a different blobRef everytime.
// Also, CAMLI_DEBUG is needed for --contents_only flag.
return w.CmdWithEnv("camput", append(os.Environ(), "CAMLI_DEBUG=1"), "file", "--contents_only=true", filepath.FromSlash("../testdata/server-config.json"))
return w.CmdWithEnv("pk-put", append(os.Environ(), "CAMLI_DEBUG=1"), "file", "--contents_only=true", filepath.FromSlash("../testdata/server-config.json"))
}
wantBlobRef := "sha1-381c42a63078ef49a2f1808318dbbafbb31a81d5"
cmd := camputCmd()
cmd := pkputCmd()
out := test.MustRunCmd(t, cmd)
out = strings.TrimSpace(out)
if out != wantBlobRef {
t.Fatalf("wrong camput output; wanted %v, got %v", wantBlobRef, out)
t.Fatalf("wrong pk-put output; wanted %v, got %v", wantBlobRef, out)
}
cmd = camputCmd()
cmd = pkputCmd()
var stderr bytes.Buffer
cmd.Stderr = &stderr
output, err := cmd.Output()
if err != nil {
t.Fatalf("second camput failed: %v, stdout: %v, stderr: %v", err, output, stderr.String())
t.Fatalf("second pk-put failed: %v, stdout: %v, stderr: %v", err, output, stderr.String())
}
out = strings.TrimSpace(string(output))
if out != wantBlobRef {
t.Fatalf("wrong 2nd camput output; wanted %v, got %v", wantBlobRef, out)
t.Fatalf("wrong 2nd pk-put output; wanted %v, got %v", wantBlobRef, out)
}
wantStats := `[uploadRequests=[blobs=0 bytes=0] uploads=[blobs=0 bytes=0]]`
if !strings.Contains(stderr.String(), wantStats) {
t.Fatalf("Wrong stats for 2nd camput upload; wanted %v, got %v", wantStats, out)
t.Fatalf("Wrong stats for 2nd pk-put upload; wanted %v, got %v", wantStats, out)
}
}

View File

@ -55,7 +55,7 @@ func benchmarkWrite(b *testing.B, cfg string) {
b.Fatalf("could not start server for config: %v\nError: %v", cfg, err)
}
b.StartTimer()
test.MustRunCmd(b, w.Cmd("camput", "file", testFile))
test.MustRunCmd(b, w.Cmd("pk-put", "file", testFile))
b.StopTimer()
w.Stop()
}

View File

@ -45,7 +45,7 @@ func parseJSON(s string) map[string]interface{} {
func TestSetNamed(t *testing.T) {
w := test.GetWorld(t)
// Needed to upload the owner public key
runCmd(t, w, "camput", "permanode")
runCmd(t, w, "pk-put", "permanode")
runCmd(t, w, "pk", "named-search-set", "bar", "is:image and tag:bar")
gno := runCmd(t, w, "pk", "named-search-get", "bar")
@ -58,16 +58,16 @@ func TestSetNamed(t *testing.T) {
func TestGetNamed(t *testing.T) {
w := test.GetWorld(t)
putExprCmd := w.Cmd("camput", "blob", "-")
putExprCmd := w.Cmd("pk-put", "blob", "-")
putExprCmd.Stdin = strings.NewReader("is:pano")
ref, err := test.RunCmd(putExprCmd)
if err != nil {
t.Fatal(err)
}
pn := runCmd(t, w, "camput", "permanode")
runCmd(t, w, "camput", "attr", strings.TrimSpace(pn), "camliNamedSearch", "foo")
runCmd(t, w, "camput", "attr", strings.TrimSpace(pn), "camliContent", strings.TrimSpace(ref))
pn := runCmd(t, w, "pk-put", "permanode")
runCmd(t, w, "pk-put", "attr", strings.TrimSpace(pn), "camliNamedSearch", "foo")
runCmd(t, w, "pk-put", "attr", strings.TrimSpace(pn), "camliContent", strings.TrimSpace(ref))
gno := runCmd(t, w, "pk", "named-search-get", "foo")
gnr := parseJSON(gno)
if gnr["named"] != "foo" || gnr["substitute"] != "is:pano" {
@ -79,7 +79,7 @@ func TestNamedSearch(t *testing.T) {
w := test.GetWorld(t)
runCmd(t, w, "pk", "named-search-set", "favorite", "tag:cats")
pn := runCmd(t, w, "camput", "permanode", "-title", "Felix", "-tag", "cats")
pn := runCmd(t, w, "pk-put", "permanode", "-title", "Felix", "-tag", "cats")
_, lines, err := bufio.ScanLines([]byte(pn), false)
if err != nil {
t.Fatal(err)
@ -97,7 +97,7 @@ func TestNestedNamedSearch(t *testing.T) {
runCmd(t, w, "pk", "named-search-set", "favorite", "tag:cats")
runCmd(t, w, "pk", "named-search-set", "mybest", "named:favorite")
pn := runCmd(t, w, "camput", "permanode", "-title", "Felix", "-tag", "cats")
pn := runCmd(t, w, "pk-put", "permanode", "-title", "Felix", "-tag", "cats")
_, lines, err := bufio.ScanLines([]byte(pn), false)
if err != nil {
t.Fatal(err)

View File

@ -45,7 +45,7 @@ func tempDir(t *testing.T) (path string, cleanup func()) {
return
}
// Test that we can camput and camget a file whose name is not utf8,
// Test that we can pk-put and camget a file whose name is not utf8,
// that we don't panic in the process and that the results are
// correct.
func TestNonUTF8FileName(t *testing.T) {
@ -69,10 +69,10 @@ func TestNonUTF8FileName(t *testing.T) {
fd.Close()
w := test.GetWorld(t)
out := test.MustRunCmd(t, w.Cmd("camput", "file", fd.Name()))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", fd.Name()))
br := strings.Split(out, "\n")[0]
// camput was a success. Can we get the file back in another directory?
// pk-put was a success. Can we get the file back in another directory?
dstDir, cleanup := tempDir(t)
defer cleanup()
@ -84,7 +84,7 @@ func TestNonUTF8FileName(t *testing.T) {
}
}
// Test that we can camput and camget a symbolic link whose target is
// Test that we can pk-put and camget a symbolic link whose target is
// not utf8, that we do no panic in the process and that the results
// are correct.
func TestNonUTF8SymlinkTarget(t *testing.T) {
@ -113,7 +113,7 @@ func TestNonUTF8SymlinkTarget(t *testing.T) {
}
w := test.GetWorld(t)
out := test.MustRunCmd(t, w.Cmd("camput", "file", filepath.Join(srcDir, "link")))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", filepath.Join(srcDir, "link")))
br := strings.Split(out, "\n")[0]
// See if we can camget it back correctly

View File

@ -43,10 +43,10 @@ func TestDirSharing(t *testing.T) {
func share(t *testing.T, file string) {
w := test.GetWorld(t)
out := test.MustRunCmd(t, w.Cmd("camput", "file", file))
out := test.MustRunCmd(t, w.Cmd("pk-put", "file", file))
fileRef := strings.Split(out, "\n")[0]
out = test.MustRunCmd(t, w.Cmd("camput", "share", "-transitive", fileRef))
out = test.MustRunCmd(t, w.Cmd("pk-put", "share", "-transitive", fileRef))
shareRef := strings.Split(out, "\n")[0]
testDir, err := ioutil.TempDir("", "camli-share-test-")

View File

@ -41,7 +41,7 @@ import (
// World defines an integration test world.
//
// It's used to run the actual Perkeep binaries (camlistored,
// camput, camget, pk, etc) together in large tests, including
// pk-put, camget, pk, etc) together in large tests, including
// building them, finding them, and wiring them up in an isolated way.
type World struct {
srcRoot string // typically $GOPATH[0]/src/perkeep.org
@ -249,10 +249,10 @@ func (w *World) NewPermanode(t *testing.T) blob.Ref {
if err := w.Ping(); err != nil {
t.Fatal(err)
}
out := MustRunCmd(t, w.Cmd("camput", "permanode"))
out := MustRunCmd(t, w.Cmd("pk-put", "permanode"))
br, ok := blob.Parse(strings.TrimSpace(out))
if !ok {
t.Fatalf("Expected permanode in camput stdout; got %q", out)
t.Fatalf("Expected permanode in pk-put stdout; got %q", out)
}
return br
}
@ -272,10 +272,10 @@ func (w *World) CmdWithEnv(binary string, env []string, args ...string) *exec.Cm
}
var cmd *exec.Cmd
switch binary {
case "camget", "camput", "pk", "pk-mount":
// TODO(mpl): lift the camput restriction when we have a unified logging mechanism
if binary == "camput" && !hasVerbose() {
// camput and pk are the only ones to have a -verbose flag through cmdmain
case "camget", "pk-put", "pk", "pk-mount":
// TODO(mpl): lift the pk-put restriction when we have a unified logging mechanism
if binary == "pk-put" && !hasVerbose() {
// pk-put and pk are the only ones to have a -verbose flag through cmdmain
// but pk is never used. (and pk-mount does not even have a -verbose).
args = append([]string{"-verbose"}, args...)
}
@ -326,7 +326,7 @@ func GetWorldMaybe(t *testing.T) *World {
}
// RunCmd runs c (which is assumed to be something short-lived, like a
// camput or camget command), capturing its stdout for return, and
// pk-put or camget command), capturing its stdout for return, and
// also capturing its stderr, just in the case of errors.
// If there's an error, the return error fully describes the command and
// all output.

View File

@ -31,7 +31,7 @@ var (
ErrClientNoServer = addCamError("client-no-server", funcStr(func() string {
return fmt.Sprintf("No valid server defined. It can be set with the CAMLI_SERVER environment variable, or the --server flag, or in the \"servers\" section of %q (see https://camlistore.org/doc/client-config).", osutil.UserClientConfigPath())
}))
ErrClientNoPublicKey = addCamError("client-no-public-key", str("No public key configured: see 'camput init'."))
ErrClientNoPublicKey = addCamError("client-no-public-key", str("No public key configured: see 'pk-put init'."))
)
type str string

View File

@ -34,7 +34,7 @@ type Config struct {
Servers map[string]*Server `json:"servers"` // maps server alias to server config.
Identity string `json:"identity"` // GPG identity.
IdentitySecretRing string `json:"identitySecretRing,omitempty"` // location of the secret ring file.
IgnoredFiles []string `json:"ignoredFiles,omitempty"` // list of files that camput should ignore.
IgnoredFiles []string `json:"ignoredFiles,omitempty"` // list of files that pk-put should ignore.
}
// Server holds the values specific to each server found in the JSON client