rclone: implement exit codes - #1136

This commit is contained in:
ishuah
2017-11-15 17:48:37 +03:00
parent 2423fa40e2
commit aec2265be0
12 changed files with 140 additions and 66 deletions
+64 -17
View File
@@ -16,6 +16,7 @@ import (
"runtime/pprof"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -31,6 +32,23 @@ var (
dataRateUnit = fs.StringP("stats-unit", "", "bytes", "Show data rate in stats as either 'bits' or 'bytes'/s")
version bool
retries = fs.IntP("retries", "", 3, "Retry operations this many times if they fail")
// Errors
errorCommandNotFound = errors.New("command not found")
errorUncategorized = errors.New("uncategorized error")
errorNotEnoughArguments = errors.New("not enough arguments")
errorTooManyArguents = errors.New("too many arguments")
errorUsageError = errors.New("usage error")
)
const (
exitCodeSuccess = iota
exitCodeUsageError
exitCodeUncategorizedError
exitCodeDirNotFound
exitCodeFileNotFound
exitCodeRetryError
exitCodeNoRetryError
exitCodeFatalError
)
// Root is the main rclone command
@@ -86,11 +104,11 @@ and configuration walkthroughs.
func runRoot(cmd *cobra.Command, args []string) {
if version {
ShowVersion()
os.Exit(0)
resolveExitCode(nil)
} else {
_ = Root.Usage()
fmt.Fprintf(os.Stderr, "Command not found.\n")
os.Exit(1)
resolveExitCode(errorCommandNotFound)
}
}
@@ -113,7 +131,7 @@ func ShowVersion() {
func newFsFile(remote string) (fs.Fs, string) {
fsInfo, configName, fsPath, err := fs.ParseRemote(remote)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatalf("Failed to create file system for %q: %v", remote, err)
}
f, err := fsInfo.NewFs(configName, fsPath)
@@ -123,7 +141,7 @@ func newFsFile(remote string) (fs.Fs, string) {
case nil:
return f, ""
default:
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatalf("Failed to create file system for %q: %v", remote, err)
}
return nil, ""
@@ -138,13 +156,14 @@ func newFsSrc(remote string) (fs.Fs, string) {
f, fileName := newFsFile(remote)
if fileName != "" {
if !fs.Config.Filter.InActive() {
fs.Stats.Error()
log.Fatalf("Can't limit to single files when using filters: %v", remote)
err := errors.Errorf("Can't limit to single files when using filters: %v", remote)
fs.Stats.Error(err)
log.Fatalf(err.Error())
}
// Limit transfers to this file
err := fs.Config.Filter.AddFile(fileName)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatalf("Failed to limit to single file %q: %v", remote, err)
}
// Set --no-traverse as only one file
@@ -159,7 +178,7 @@ func newFsSrc(remote string) (fs.Fs, string) {
func newFsDst(remote string) fs.Fs {
f, err := fs.NewFs(remote)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatalf("Failed to create file system for %q: %v", remote, err)
}
return f
@@ -271,14 +290,15 @@ func Run(Retry bool, showStats bool, cmd *cobra.Command, f func() error) {
close(stopStats)
}
if err != nil {
log.Fatalf("Failed to %s: %v", cmd.Name(), err)
log.Printf("Failed to %s: %v", cmd.Name(), err)
resolveExitCode(err)
}
if showStats && (fs.Stats.Errored() || *statsInterval > 0) {
fs.Stats.Log()
}
fs.Debugf(nil, "Go routines at exit %d\n", runtime.NumGoroutine())
if fs.Stats.Errored() {
os.Exit(1)
resolveExitCode(fs.Stats.GetLastError())
}
}
@@ -287,11 +307,13 @@ func CheckArgs(MinArgs, MaxArgs int, cmd *cobra.Command, args []string) {
if len(args) < MinArgs {
_ = cmd.Usage()
fmt.Fprintf(os.Stderr, "Command %s needs %d arguments mininum\n", cmd.Name(), MinArgs)
os.Exit(1)
// os.Exit(1)
resolveExitCode(errorNotEnoughArguments)
} else if len(args) > MaxArgs {
_ = cmd.Usage()
fmt.Fprintf(os.Stderr, "Command %s needs %d arguments maximum\n", cmd.Name(), MaxArgs)
os.Exit(1)
// os.Exit(1)
resolveExitCode(errorTooManyArguents)
}
}
@@ -333,12 +355,12 @@ func initConfig() {
fs.Infof(nil, "Creating CPU profile %q\n", *cpuProfile)
f, err := os.Create(*cpuProfile)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatal(err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatal(err)
}
AtExit(func() {
@@ -352,17 +374,17 @@ func initConfig() {
fs.Infof(nil, "Saving Memory profile %q\n", *memProfile)
f, err := os.Create(*memProfile)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatal(err)
}
err = pprof.WriteHeapProfile(f)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatal(err)
}
err = f.Close()
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
log.Fatal(err)
}
})
@@ -375,3 +397,28 @@ func initConfig() {
fs.Config.DataRateUnit = *dataRateUnit
}
}
func resolveExitCode(err error) {
if err == nil {
os.Exit(exitCodeSuccess)
}
err = errors.Cause(err)
switch {
case err == fs.ErrorDirNotFound:
os.Exit(exitCodeDirNotFound)
case err == fs.ErrorObjectNotFound:
os.Exit(exitCodeFileNotFound)
case err == errorUncategorized:
os.Exit(exitCodeUncategorizedError)
case fs.ShouldRetry(err):
os.Exit(exitCodeRetryError)
case fs.IsNoRetryError(err):
os.Exit(exitCodeNoRetryError)
case fs.IsFatalError(err):
os.Exit(exitCodeFatalError)
default:
os.Exit(exitCodeUsageError)
}
}
+5 -4
View File
@@ -72,7 +72,7 @@ func cryptCheck(fdst, fsrc fs.Fs) error {
underlyingDst := cryptDst.UnWrap()
underlyingHash, err := underlyingDst.Hash(hashType)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
fs.Errorf(dst, "Error reading hash from underlying %v: %v", underlyingDst, err)
return true, false
}
@@ -81,7 +81,7 @@ func cryptCheck(fdst, fsrc fs.Fs) error {
}
cryptHash, err := fcrypt.ComputeHash(cryptDst, src, hashType)
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
fs.Errorf(dst, "Error computing hash: %v", err)
return true, false
}
@@ -89,8 +89,9 @@ func cryptCheck(fdst, fsrc fs.Fs) error {
return false, true
}
if cryptHash != underlyingHash {
fs.Stats.Error()
fs.Errorf(src, "hashes differ (%s:%s) %q vs (%s:%s) %q", fdst.Name(), fdst.Root(), cryptHash, fsrc.Name(), fsrc.Root(), underlyingHash)
err = errors.Errorf("hashes differ (%s:%s) %q vs (%s:%s) %q", fdst.Name(), fdst.Root(), cryptHash, fsrc.Name(), fsrc.Root(), underlyingHash)
fs.Stats.Error(err)
fs.Errorf(src, err.Error())
return true, false
}
fs.Debugf(src, "OK")
+1 -1
View File
@@ -27,7 +27,7 @@ var commandDefintion = &cobra.Command{
cmd.Run(false, false, command, func() error {
return fs.Walk(fsrc, "", false, fs.ConfigMaxDepth(recurse), func(path string, entries fs.DirEntries, err error) error {
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
fs.Errorf(path, "error listing: %v", err)
return nil
}
+1 -1
View File
@@ -85,7 +85,7 @@ can be processed line by line as each item is written one to a line.
first := true
err := fs.Walk(fsrc, "", false, fs.ConfigMaxDepth(recurse), func(dirPath string, entries fs.DirEntries, err error) error {
if err != nil {
fs.Stats.Error()
fs.Stats.Error(err)
fs.Errorf(dirPath, "error listing: %v", err)
return nil
}
+1 -1
View File
@@ -142,7 +142,7 @@ type indexData struct {
// error returns an http.StatusInternalServerError and logs the error
func internalError(what interface{}, w http.ResponseWriter, text string, err error) {
fs.Stats.Error()
fs.Stats.Error(err)
fs.Errorf(what, "%s: %v", text, err)
http.Error(w, text+".", http.StatusInternalServerError)
}