func TestApp_RunAsSubcommandParseFlags(t *testing.T) { var context *cli.Context a := cli.NewApp() a.Commands = []cli.Command{ { Name: "foo", Action: func(c *cli.Context) { context = c }, Flags: []cli.Flag{ cli.StringFlag{ Name: "lang", Value: "english", Usage: "language for the greeting", }, }, Before: func(_ *cli.Context) error { return nil }, }, } a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) expect(t, context.Args().Get(0), "abcd") expect(t, context.String("lang"), "spanish") }
func runMinioInstall(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "install", 1) // last argument is exit code } minioGenerate := command{exec.Command("godep", "go", "generate", "./..."), &bytes.Buffer{}, &bytes.Buffer{}} minioBuild := command{exec.Command("godep", "go", "build", "-a", "./..."), &bytes.Buffer{}, &bytes.Buffer{}} minioTest := command{exec.Command("godep", "go", "test", "-race", "./..."), &bytes.Buffer{}, &bytes.Buffer{}} minioInstall := command{exec.Command("godep", "go", "install", "-a", "github.com/minio/minio"), &bytes.Buffer{}, &bytes.Buffer{}} minioGenerateErr := minioGenerate.runCommand() if minioGenerateErr != nil { fmt.Print(minioGenerate) os.Exit(1) } fmt.Print(minioGenerate) minioBuildErr := minioBuild.runCommand() if minioBuildErr != nil { fmt.Print(minioBuild) os.Exit(1) } fmt.Print(minioBuild) minioTestErr := minioTest.runCommand() if minioTestErr != nil { fmt.Println(minioTest) os.Exit(1) } fmt.Print(minioTest) minioInstallErr := minioInstall.runCommand() if minioInstallErr != nil { fmt.Println(minioInstall) os.Exit(1) } fmt.Print(minioInstall) }
// mainCopy is bound to sub-command func mainCopy(ctx *cli.Context) { checkCopySyntax(ctx) setCopyPalette(ctx.GlobalString("colors")) session := newSessionV2() var e error session.Header.CommandType = "cp" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // extract URLs. var err *probe.Error session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Delete() fatalIf(err.Trace(), "One or more unknown URL types passed.") } doCopySession(session) session.Delete() }
// mainDiff main for 'diff'. func mainDiff(ctx *cli.Context) { checkDiffSyntax(ctx) // Additional command speific theme customization. console.SetColor("DiffMessage", color.New(color.FgGreen, color.Bold)) console.SetColor("DiffOnlyInFirst", color.New(color.FgRed, color.Bold)) console.SetColor("DiffType", color.New(color.FgYellow, color.Bold)) console.SetColor("DiffSize", color.New(color.FgMagenta, color.Bold)) config := mustGetMcConfig() firstArg := ctx.Args().First() secondArg := ctx.Args().Last() firstURL := getAliasURL(firstArg, config.Aliases) secondURL := getAliasURL(secondArg, config.Aliases) newFirstURL := stripRecursiveURL(firstURL) for diff := range doDiffMain(newFirstURL, secondURL, isURLRecursive(firstURL)) { if diff.Error != nil { // Print in new line and adjust to top so that we don't print over the ongoing scan bar if !globalQuietFlag && !globalJSONFlag { console.Eraseline() } } fatalIf(diff.Error.Trace(newFirstURL, secondURL), "Failed to diff ‘"+firstURL+"’ and ‘"+secondURL+"’.") printMsg(diff) } // Print in new line and adjust to top so that we don't print over the ongoing scan bar if !globalQuietFlag && !globalJSONFlag { console.Eraseline() } console.Println(console.Colorize("DiffMessage", "Done.")) }
// checkShareDownloadSyntax - validate command-line args. func checkShareDownloadSyntax(ctx *cli.Context) { args := ctx.Args() if !args.Present() { cli.ShowCommandHelpAndExit(ctx, "download", 1) // last argument is exit code. } // Parse expiry. expiry := shareDefaultExpiry expireArg := ctx.String("expire") if expireArg != "" { var e error expiry, e = time.ParseDuration(expireArg) fatalIf(probe.NewError(e), "Unable to parse expire=‘"+expireArg+"’.") } // Validate expiry. if expiry.Seconds() < 1 { fatalIf(errDummy().Trace(expiry.String()), "Expiry cannot be lesser than 1 second.") } if expiry.Seconds() > 604800 { fatalIf(errDummy().Trace(expiry.String()), "Expiry cannot be larger than 7 days.") } for _, url := range ctx.Args() { _, _, err := url2Stat(url) fatalIf(err.Trace(url), "Unable to stat ‘"+url+"’.") } }
func mainEventsRemove(ctx *cli.Context) error { console.SetColor("Events", color.New(color.FgGreen, color.Bold)) setGlobalsFromContext(ctx) checkEventsRemoveSyntax(ctx) args := ctx.Args() path := args.Get(0) arn := "" if len(args) == 2 { arn = args.Get(1) } client, err := newClient(path) if err != nil { fatalIf(err.Trace(), "Cannot parse the provided url.") } s3Client, ok := client.(*s3Client) if !ok { fatalIf(errDummy().Trace(), "The provided url doesn't point to a S3 server.") } err = s3Client.RemoveNotificationConfig(arn) fatalIf(err, "Cannot enable notification on the specified bucket.") printMsg(eventsRemoveMessage{ARN: arn}) return nil }
func mainMirror(ctx *cli.Context) { checkMirrorSyntax(ctx) setMirrorPalette(ctx.GlobalString("colors")) var e error session := newSessionV2() session.Header.CommandType = "mirror" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // extract URLs. var err *probe.Error session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Delete() fatalIf(err.Trace(ctx.Args()...), fmt.Sprintf("One or more unknown argument types found in ‘%s’.", ctx.Args())) } doMirrorSession(session) session.Delete() }
func mainVersion(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code } t, _ := time.Parse(time.RFC3339Nano, Version) if t.IsZero() { console.Println("") return } type Version struct { Value time.Time `json:"value"` Format string `json:"format"` } if globalJSONFlag { tB, e := json.Marshal( struct { Version Version `json:"version"` }{Version: Version{t, "RFC3339Nano"}}, ) fatalIf(probe.NewError(e), "Unable to construct version string.") console.Println(string(tB)) return } console.Println(t.Format(http.TimeFormat)) }
// mainCopy is the entry point for cp command. func mainCopy(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check 'copy' cli arguments. checkCopySyntax(ctx) // Additional command speific theme customization. console.SetColor("Copy", color.New(color.FgGreen, color.Bold)) session := newSessionV6() session.Header.CommandType = "cp" session.Header.CommandBoolFlags["recursive"] = ctx.Bool("recursive") var e error if session.Header.RootPath, e = os.Getwd(); e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // extract URLs. session.Header.CommandArgs = ctx.Args() doCopySession(session) session.Delete() }
func runCastCmd(ctx *cli.Context) { checkCastSyntax(ctx) session := newSessionV2() var err error session.Header.CommandType = "cast" session.Header.RootPath, err = os.Getwd() if err != nil { session.Close() session.Delete() console.Fatalf("Unable to get current working folder. %s\n", err) } // extract URLs. session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Close() session.Delete() console.Fatalf("One or more unknown URL types found in %s. %s\n", ctx.Args(), err) } doCastCmdSession(session) session.Close() session.Delete() }
// mainConfig is the handle for "mc config" sub-command. writes configuration data in json format to config file. func mainConfig(ctx *cli.Context) { checkConfigSyntax(ctx) // set new custom coloring console.SetCustomTheme(map[string]*color.Color{ "Alias": color.New(color.FgCyan, color.Bold), "AliasMessage": color.New(color.FgGreen, color.Bold), "URL": color.New(color.FgWhite), }) arg := ctx.Args().First() tailArgs := ctx.Args().Tail() switch strings.TrimSpace(arg) { case "add": if strings.TrimSpace(tailArgs.First()) == "alias" { addAlias(tailArgs.Get(1), tailArgs.Get(2)) } case "remove": if strings.TrimSpace(tailArgs.First()) == "alias" { removeAlias(tailArgs.Get(1)) } case "list": if strings.TrimSpace(tailArgs.First()) == "alias" { listAliases() } } }
// mainShare - main handler for mc share command func mainShare(ctx *cli.Context) { if !ctx.Args().Present() || ctx.Args().First() == "help" { cli.ShowAppHelp(ctx) } // sub-commands like "upload" and "download" have their own main. }
func mainConfigVersion(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code } config, err := loadConfigV2() fatalIf(err.Trace(), "Unable to load config", nil) // convert interface{} back to its original struct newConf := config type Version struct { Value string `json:"value"` } if globalJSONFlag { tB, e := json.Marshal( struct { Version Version `json:"version"` }{Version: Version{newConf.Version}}, ) fatalIf(probe.NewError(e), "Unable to construct version string.", nil) Println(string(tB)) return } Println(newConf.Version) }
// mainList - is a handler for mc ls command func mainList(ctx *cli.Context) { // Additional command speific theme customization. console.SetColor("File", color.New(color.FgWhite)) console.SetColor("Dir", color.New(color.FgCyan, color.Bold)) console.SetColor("Size", color.New(color.FgYellow)) console.SetColor("Time", color.New(color.FgGreen)) // check 'ls' cli arguments checkListSyntax(ctx) args := ctx.Args() isIncomplete := ctx.Bool("incomplete") // mimic operating system tool behavior if globalMimicFlag && !ctx.Args().Present() { args = []string{"."} } targetURLs, err := args2URLs(args.Head()) fatalIf(err.Trace(args...), "One or more unknown URL types passed.") for _, targetURL := range targetURLs { // if recursive strip off the "..." var clnt client.Client clnt, err = url2Client(stripRecursiveURL(targetURL)) fatalIf(err.Trace(targetURL), "Unable to initialize target ‘"+targetURL+"’.") err = doList(clnt, isURLRecursive(targetURL), isIncomplete) fatalIf(err.Trace(clnt.GetURL().String()), "Unable to list target ‘"+clnt.GetURL().String()+"’.") } }
func mainVersion(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code } setVersionPalette(ctx.GlobalString("colors")) if globalJSONFlag { tB, e := json.Marshal( struct { Version struct { Value string `json:"value"` Format string `json:"format"` } `json:"version"` }{ Version: struct { Value string `json:"value"` Format string `json:"format"` }{ Value: mcVersion, Format: "RFC2616", }, }, ) fatalIf(probe.NewError(e), "Unable to construct version string.") console.Println(string(tB)) return } msg := console.Colorize("Version", fmt.Sprintf("Version: %s\n", mcVersion)) msg += console.Colorize("Version", fmt.Sprintf("Release-Tag: %s", mcReleaseTag)) console.Println(msg) }
func mainMirror(ctx *cli.Context) { checkMirrorSyntax(ctx) // Additional command speific theme customization. console.SetColor("Mirror", color.New(color.FgGreen, color.Bold)) var e error session := newSessionV3() session.Header.CommandType = "mirror" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // If force flag is set save it with in session session.Header.CommandBoolFlag.Key = "force" session.Header.CommandBoolFlag.Value = ctx.Bool("force") // extract URLs. var err *probe.Error session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Delete() fatalIf(err.Trace(ctx.Args()...), fmt.Sprintf("One or more unknown argument types found in ‘%s’.", ctx.Args())) } doMirrorSession(session) session.Delete() }
// mainCopy is bound to sub-command func mainCopy(ctx *cli.Context) { checkCopySyntax(ctx) console.SetCustomTheme(map[string]*color.Color{ "Copy": color.New(color.FgGreen, color.Bold), }) session := newSessionV2() var e error session.Header.CommandType = "cp" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // extract URLs. var err *probe.Error session.Header.CommandArgs, err = args2URLs(ctx.Args()) if err != nil { session.Delete() fatalIf(err.Trace(), "One or more unknown URL types passed.") } doCopyCmdSession(session) session.Delete() }
func mainConfigVersion(ctx *cli.Context) { if ctx.Args().First() == "help" { cli.ShowCommandHelpAndExit(ctx, "version", 1) // last argument is exit code } config, err := newConfig() fatalIf(err.Trace(), "Failed to initialize ‘quick’ configuration data structure.") configPath := mustGetMcConfigPath() err = config.Load(configPath) fatalIf(err.Trace(configPath), "Unable to load config path") // convert interface{} back to its original struct newConf := config.Data().(*configV5) type Version struct { Value string `json:"value"` } if globalJSONFlag { tB, e := json.Marshal( struct { Version Version `json:"version"` }{Version: Version{newConf.Version}}, ) fatalIf(probe.NewError(e), "Unable to construct version string.") console.Println(string(tB)) return } console.Println(newConf.Version) }
// main for share download. func mainShareDownload(ctx *cli.Context) { // setup share data folder and file. shareDataSetup() // Additional command speific theme customization. shareSetColor() // check input arguments. checkShareDownloadSyntax(ctx) args := ctx.Args() config := mustGetMcConfig() url := args.Get(0) // default expiration is 7days expires := time.Duration(604800) * time.Second if len(args) == 2 { var err error expires, err = time.ParseDuration(args.Get(1)) fatalIf(probe.NewError(err), "Unable to parse time argument.") } targetURL := getAliasURL(url, config.Aliases) // if recursive strip off the "..." err := doShareDownloadURL(stripRecursiveURL(targetURL), isURLRecursive(targetURL), expires) fatalIf(err.Trace(targetURL), "Unable to generate URL for download.") return }
// mainList - is a handler for mc ls command func mainList(ctx *cli.Context) { // Additional command speific theme customization. console.SetColor("File", color.New(color.FgWhite)) console.SetColor("Dir", color.New(color.FgCyan, color.Bold)) console.SetColor("Size", color.New(color.FgYellow)) console.SetColor("Time", color.New(color.FgGreen)) // Set global flags from context. setGlobalsFromContext(ctx) // check 'ls' cli arguments. checkListSyntax(ctx) // Set command flags from context. isRecursive := ctx.Bool("recursive") isIncomplete := ctx.Bool("incomplete") args := ctx.Args() // mimic operating system tool behavior. if !ctx.Args().Present() { args = []string{"."} } for _, targetURL := range args { var clnt client.Client clnt, err := newClient(targetURL) fatalIf(err.Trace(targetURL), "Unable to initialize target ‘"+targetURL+"’.") err = doList(clnt, isRecursive, isIncomplete) if err != nil { errorIf(err.Trace(clnt.GetURL().String()), "Unable to list target ‘"+clnt.GetURL().String()+"’.") continue } } }
// Validate command line arguments. func checkRmSyntax(ctx *cli.Context) { args := ctx.Args() ishelp := ctx.GlobalBool("help") isForce := ctx.Bool("force") if !args.Present() || ishelp { exitCode := 1 cli.ShowCommandHelpAndExit(ctx, "rm", exitCode) } URLs, err := args2URLs(args) fatalIf(err.Trace(ctx.Args()...), "Unable to parse arguments.") // If input validation fails then provide context sensitive help without displaying generic help message. // The context sensitive help is shown per argument instead of all arguments to keep the help display // as well as the code simple. Also most of the times there will be just one arg for _, url := range URLs { u := client.NewURL(url) if strings.HasSuffix(url, string(u.Separator)) { fatalIf(errDummy().Trace(), "‘"+url+"’ is a folder. To remove this folder recursively, please try ‘"+url+"...’ as argument.") } if isURLRecursive(url) && !isForce { fatalIf(errDummy().Trace(), "Recursive removal requires --force option. Please review carefully before performing this operation.") } } }
// mainMakeBucket is entry point for mb command. func mainMakeBucket(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check 'mb' cli arguments. checkMakeBucketSyntax(ctx) // Additional command speific theme customization. console.SetColor("MakeBucket", color.New(color.FgGreen, color.Bold)) for _, targetURL := range ctx.Args() { // Instantiate client for URL. clnt, err := newClient(targetURL) fatalIf(err.Trace(targetURL), "Invalid target ‘"+targetURL+"’.") // Make bucket. err = clnt.MakeBucket() // Upon error print error and continue. if err != nil { errorIf(err.Trace(targetURL), "Unable to make bucket ‘"+targetURL+"’.") continue } // Successfully created a bucket. printMsg(makeBucketMessage{Status: "success", Bucket: targetURL}) } }
// main for share download. func mainShareDownload(ctx *cli.Context) error { // Set global flags from context. setGlobalsFromContext(ctx) // check input arguments. checkShareDownloadSyntax(ctx) // Initialize share config folder. initShareConfig() // Additional command speific theme customization. shareSetColor() // Set command flags from context. isRecursive := ctx.Bool("recursive") expiry := shareDefaultExpiry if ctx.String("expire") != "" { var e error expiry, e = time.ParseDuration(ctx.String("expire")) fatalIf(probe.NewError(e), "Unable to parse expire=‘"+ctx.String("expire")+"’.") } for _, targetURL := range ctx.Args() { err := doShareDownloadURL(targetURL, isRecursive, expiry) if err != nil { switch err.ToGoError().(type) { case APINotImplemented: fatalIf(err.Trace(), "Unable to share a non S3 url ‘"+targetURL+"’.") default: fatalIf(err.Trace(targetURL), "Unable to share target ‘"+targetURL+"’.") } } } return nil }
// checkPolicySyntax check for incoming syntax. func checkPolicySyntax(ctx *cli.Context) { argsLength := len(ctx.Args()) // Always print a help message when we have extra arguments if argsLength > 2 { cli.ShowCommandHelpAndExit(ctx, "policy", 1) // last argument is exit code. } // Always print a help message when no arguments specified if argsLength < 1 { cli.ShowCommandHelpAndExit(ctx, "policy", 1) } firstArg := ctx.Args().Get(0) // More syntax checking switch accessPerms(firstArg) { case accessNone, accessDownload, accessUpload, accessPublic: // Always expect two arguments when a policy permission is provided if argsLength != 2 { cli.ShowCommandHelpAndExit(ctx, "policy", 1) } case "list": // Always expect an argument after list cmd if argsLength != 2 { cli.ShowCommandHelpAndExit(ctx, "policy", 1) } default: if argsLength == 2 { fatalIf(errDummy().Trace(), "Unrecognized permission ‘"+string(firstArg)+"’. Allowed values are [none, download, upload, public].") } } }
// mainDiff - is a handler for mc diff command func mainDiff(ctx *cli.Context) { checkDiffSyntax(ctx) console.SetCustomTheme(map[string]*color.Color{ "DiffMessage": color.New(color.FgGreen, color.Bold), "DiffOnlyInFirst": color.New(color.FgRed, color.Bold), "DiffType": color.New(color.FgYellow, color.Bold), "DiffSize": color.New(color.FgMagenta, color.Bold), }) config := mustGetMcConfig() firstArg := ctx.Args().First() secondArg := ctx.Args().Last() firstURL, err := getCanonicalizedURL(firstArg, config.Aliases) fatalIf(err.Trace(firstArg), "Unable to parse first argument ‘"+firstArg+"’.") secondURL, err := getCanonicalizedURL(secondArg, config.Aliases) fatalIf(err.Trace(secondArg), "Unable to parse second argument ‘"+secondArg+"’.") newFirstURL := stripRecursiveURL(firstURL) for diff := range doDiffCmd(newFirstURL, secondURL, isURLRecursive(firstURL)) { fatalIf(diff.Error.Trace(newFirstURL, secondURL), "Failed to diff ‘"+firstURL+"’ and ‘"+secondURL+"’.") console.Println(diff.String()) } }
func mainEventsAdd(ctx *cli.Context) error { console.SetColor("Events", color.New(color.FgGreen, color.Bold)) setGlobalsFromContext(ctx) checkEventsAddSyntax(ctx) args := ctx.Args() path := args[0] arn := args[1] events := strings.Split(ctx.String("events"), ",") prefix := ctx.String("prefix") suffix := ctx.String("suffix") client, err := newClient(path) if err != nil { fatalIf(err.Trace(), "Cannot parse the provided url.") } s3Client, ok := client.(*s3Client) if !ok { fatalIf(errDummy().Trace(), "The provided url doesn't point to a S3 server.") } err = s3Client.AddNotificationConfig(arn, events, prefix, suffix) fatalIf(err, "Cannot enable notification on the specified bucket.") printMsg(eventsAddMessage{ ARN: arn, Events: events, Prefix: prefix, Suffix: suffix, }) return nil }
// main for share upload command. func mainShareUpload(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check input arguments. checkShareUploadSyntax(ctx) // Initialize share config folder. initShareConfig() // Additional command speific theme customization. shareSetColor() // Set command flags from context. isRecursive := ctx.Bool("recursive") expireArg := ctx.String("expire") expiry := shareDefaultExpiry contentType := ctx.String("content-type") if expireArg != "" { var e error expiry, e = time.ParseDuration(expireArg) fatalIf(probe.NewError(e), "Unable to parse expire=‘"+expireArg+"’.") } for _, targetURL := range ctx.Args() { err := doShareUploadURL(targetURL, isRecursive, expiry, contentType) fatalIf(err.Trace(targetURL), "Unable to generate curl command for upload ‘"+targetURL+"’.") } }
// main for share upload command. func mainShareUpload(ctx *cli.Context) { // setup share data folder and file. shareDataSetup() // Additional command speific theme customization. shareSetColor() // check input arguments. checkShareUploadSyntax(ctx) var expires time.Duration var err error args := ctx.Args() config := mustGetMcConfig() if strings.TrimSpace(args.Get(1)) == "" { expires = time.Duration(604800) * time.Second } else { expires, err = time.ParseDuration(strings.TrimSpace(args.Get(1))) if err != nil { fatalIf(probe.NewError(err), "Unable to parse time argument.") } } contentType := strings.TrimSpace(args.Get(2)) targetURL := getAliasURL(strings.TrimSpace(args.Get(0)), config.Aliases) e := doShareUploadURL(stripRecursiveURL(targetURL), isURLRecursive(targetURL), expires, contentType) fatalIf(e.Trace(targetURL), "Unable to generate URL for upload.") }
// "minio control lock" entry point. func lockControl(c *cli.Context) { if len(c.Args()) != 1 { cli.ShowCommandHelpAndExit(c, "lock", 1) } parsedURL, err := url.Parse(c.Args()[0]) fatalIf(err, "Unable to parse URL.") authCfg := &authConfig{ accessKey: serverConfig.GetCredential().AccessKeyID, secretKey: serverConfig.GetCredential().SecretAccessKey, address: parsedURL.Host, path: path.Join(reservedBucket, controlPath), loginMethod: "Controller.LoginHandler", } client := newAuthClient(authCfg) args := &GenericArgs{} reply := &SystemLockState{} err = client.Call("Controller.LockInfo", args, reply) // logs the error and returns if err != nil. fatalIf(err, "RPC Controller.LockInfo call failed") // print the lock info on the console. b, err := json.MarshalIndent(*reply, "", " ") fatalIf(err, "Failed to parse the RPC lock info response") fmt.Print(string(b)) }
// Main entry point for mirror command. func mainMirror(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check 'mirror' cli arguments. checkMirrorSyntax(ctx) // Additional command speific theme customization. console.SetColor("Mirror", color.New(color.FgGreen, color.Bold)) var e error session := newSessionV6() session.Header.CommandType = "mirror" session.Header.RootPath, e = os.Getwd() if e != nil { session.Delete() fatalIf(probe.NewError(e), "Unable to get current working folder.") } // Set command flags from context. isForce := ctx.Bool("force") session.Header.CommandBoolFlags["force"] = isForce // extract URLs. session.Header.CommandArgs = ctx.Args() doMirrorSession(session) session.Delete() }