// 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.") } } }
// 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()+"’.") } }
// 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() }
// checkListSyntax - validate all the passed arguments func checkListSyntax(ctx *cli.Context) { args := ctx.Args() if !ctx.Args().Present() { args = []string{"."} } for _, arg := range args { if strings.TrimSpace(arg) == "" { fatalIf(errInvalidArgument().Trace(args...), "Unable to validate empty argument.") } } // extract URLs. URLs := ctx.Args() isIncomplete := ctx.Bool("incomplete") for _, url := range URLs { _, _, err := url2Stat(url) if err != nil && !isURLPrefixExists(url, isIncomplete) { // Bucket name empty is a valid error for 'ls myminio', // treat it as such. if _, ok := err.ToGoError().(BucketNameEmpty); ok { continue } fatalIf(err.Trace(url), "Unable to stat ‘"+url+"’.") } } }
// 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 }
// 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+"’.") } }
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 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() }
// main entry point for update command. func mainUpdate(ctx *cli.Context) { // Check for update. if ctx.Bool("experimental") { getReleaseUpdate(minioUpdateExperimentalURL) } else { getReleaseUpdate(minioUpdateStableURL) } }
// checkEventsRemoveSyntax - validate all the passed arguments func checkEventsRemoveSyntax(ctx *cli.Context) { if len(ctx.Args()) == 0 || len(ctx.Args()) > 2 { cli.ShowCommandHelpAndExit(ctx, "remove", 1) // last argument is exit code } if len(ctx.Args()) == 1 && !ctx.Bool("force") { fatalIf(probe.NewError(errors.New("")), "--force flag needs to be passed to remove all bucket notifications") } }
func checkCopySyntax(ctx *cli.Context) { if len(ctx.Args()) < 2 { cli.ShowCommandHelpAndExit(ctx, "cp", 1) // last argument is exit code. } // extract URLs. URLs := ctx.Args() if len(URLs) < 2 { fatalIf(errDummy().Trace(ctx.Args()...), fmt.Sprintf("Unable to parse source and target arguments.")) } srcURLs := URLs[:len(URLs)-1] tgtURL := URLs[len(URLs)-1] isRecursive := ctx.Bool("recursive") /****** Generic Invalid Rules *******/ // Verify if source(s) exists. for _, srcURL := range srcURLs { _, _, err := url2Stat(srcURL) if err != nil { fatalIf(err.Trace(srcURL), fmt.Sprintf("Unable to stat '%s'.", srcURL)) } } // Check if bucket name is passed for URL type arguments. url := newClientURL(tgtURL) if url.Host != "" { // This check is for type URL. if !isURLVirtualHostStyle(url.Host) { if url.Path == string(url.Separator) { fatalIf(errInvalidArgument().Trace(), fmt.Sprintf("Target ‘%s’ does not contain bucket name.", tgtURL)) } } } // Guess CopyURLsType based on source and target URLs. copyURLsType, err := guessCopyURLType(srcURLs, tgtURL, isRecursive) if err != nil { fatalIf(errInvalidArgument().Trace(), "Unable to guess the type of copy operation.") } switch copyURLsType { case copyURLsTypeA: // File -> File. checkCopySyntaxTypeA(srcURLs, tgtURL) case copyURLsTypeB: // File -> Folder. checkCopySyntaxTypeB(srcURLs, tgtURL) case copyURLsTypeC: // Folder... -> Folder. checkCopySyntaxTypeC(srcURLs, tgtURL, isRecursive) case copyURLsTypeD: // File1...FileN -> Folder. checkCopySyntaxTypeD(srcURLs, tgtURL) default: fatalIf(errInvalidArgument().Trace(), "Unable to guess the type of copy operation.") } }
// mainList - is a handler for mc ls command func mainList(ctx *cli.Context) error { // Additional command specific theme customization. console.SetColor("File", color.New(color.Bold)) 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{"."} } var cErr error for _, targetURL := range args { var clnt Client clnt, err := newClient(targetURL) fatalIf(err.Trace(targetURL), "Unable to initialize target ‘"+targetURL+"’.") var st *clientContent if st, err = clnt.Stat(isIncomplete); err != nil { switch err.ToGoError().(type) { case BucketNameEmpty: // For aliases like ``mc ls s3`` it's acceptable to receive BucketNameEmpty error. // Nothing to do. default: fatalIf(err.Trace(targetURL), "Unable to initialize target ‘"+targetURL+"’.") } } else if st.Type.IsDir() { if !strings.HasSuffix(targetURL, string(clnt.GetURL().Separator)) { targetURL = targetURL + string(clnt.GetURL().Separator) } clnt, err = newClient(targetURL) fatalIf(err.Trace(targetURL), "Unable to initialize target ‘"+targetURL+"’.") } if e := doList(clnt, isRecursive, isIncomplete); e != nil { cErr = e } } return cErr }
// main entry point for update command. func mainUpdate(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // Additional command speific theme customization. console.SetColor("Update", color.New(color.FgGreen, color.Bold)) // Check for update. if ctx.Bool("experimental") { getReleaseUpdate(mcUpdateExperimentalURL) } else { getReleaseUpdate(mcUpdateStableURL) } }
// Parse command arguments and set global variables accordingly func setGlobalsFromContext(c *cli.Context) { // Set config dir switch { case c.IsSet("config-dir"): globalConfigDir = c.String("config-dir") case c.GlobalIsSet("config-dir"): globalConfigDir = c.GlobalString("config-dir") } if globalConfigDir == "" { console.Fatalf("Unable to get config file. Config directory is empty.") } // Set global quiet flag. globalQuiet = c.Bool("quiet") || c.GlobalBool("quiet") }
// main for rm command. func mainRm(ctx *cli.Context) { // Set global flags from context. setGlobalsFromContext(ctx) // check 'rm' cli arguments. checkRmSyntax(ctx) // rm specific flags. isForce := ctx.Bool("force") isIncomplete := ctx.Bool("incomplete") isRecursive := ctx.Bool("recursive") isFake := ctx.Bool("fake") // Set color. console.SetColor("Remove", color.New(color.FgGreen, color.Bold)) // Support multiple targets. for _, url := range ctx.Args() { targetAlias, targetURL, _ := mustExpandAlias(url) if isRecursive && isForce { rmAll(targetAlias, targetURL, isRecursive, isIncomplete, isFake) } else { if err := rm(targetAlias, targetURL, isIncomplete, isFake); err != nil { errorIf(err.Trace(url), "Unable to remove ‘"+url+"’.") continue } printMsg(rmMessage{Status: "success", URL: url}) } } }
// Set global states. NOTE: It is deliberately kept monolithic to ensure we dont miss out any flags. func setGlobalsFromContext(ctx *cli.Context) { quiet := ctx.Bool("quiet") || ctx.GlobalBool("quiet") debug := ctx.Bool("debug") || ctx.GlobalBool("debug") json := ctx.Bool("json") || ctx.GlobalBool("json") noColor := ctx.Bool("no-color") || ctx.GlobalBool("no-color") insecure := ctx.Bool("insecure") || ctx.GlobalBool("insecure") setGlobals(quiet, debug, json, noColor, insecure) }
// main for rm command. func mainRm(ctx *cli.Context) { checkRmSyntax(ctx) // rm specific flags. isForce := ctx.Bool("force") isIncomplete := ctx.Bool("incomplete") // Set color. console.SetColor("Remove", color.New(color.FgGreen, color.Bold)) // Parse args. URLs, err := args2URLs(ctx.Args()) fatalIf(err.Trace(ctx.Args()...), "Unable to parse arguments.") // Support multiple targets. for _, url := range URLs { if isURLRecursive(url) && isForce { url := stripRecursiveURL(url) removeTopFolder := false // find if the URL is dir or not. _, content, err := url2Stat(url) fatalIf(err.Trace(url), "Unable to stat ‘"+url+"’.") if content.Type.IsDir() { /* Determine whether to remove the top folder or only its contents. If the URL does not end with a separator, then include the top folder as well, otherwise not. */ u := client.NewURL(url) if !strings.HasSuffix(url, string(u.Separator)) { // Add separator at the end to remove all its contents. url = url + string(u.Separator) // Remember to remove the top most folder. removeTopFolder = true } } // Remove contents of this folder. rmAll(url, isIncomplete) if removeTopFolder { // Remove top folder as well. rm(url, isIncomplete) } } else { rm(url, isIncomplete) } } }
// checkMirrorSyntax(URLs []string) func checkMirrorSyntax(ctx *cli.Context) { if len(ctx.Args()) != 2 { cli.ShowCommandHelpAndExit(ctx, "mirror", 1) // last argument is exit code. } // extract URLs. URLs := ctx.Args() srcURL := URLs[0] tgtURL := URLs[1] /****** Generic rules *******/ if !ctx.Bool("watch") { _, srcContent, err := url2Stat(srcURL) // incomplete uploads are not necessary for copy operation, no need to verify for them. isIncomplete := false if err != nil && !isURLPrefixExists(srcURL, isIncomplete) { errorIf(err.Trace(srcURL), "Unable to stat source ‘"+srcURL+"’.") } if err == nil && !srcContent.Type.IsDir() { fatalIf(errInvalidArgument().Trace(srcContent.URL.String(), srcContent.Type.String()), fmt.Sprintf("Source ‘%s’ is not a folder. Only folders are supported by mirror command.", srcURL)) } } if len(tgtURL) == 0 && tgtURL == "" { fatalIf(errInvalidArgument().Trace(), "Invalid target arguments to mirror command.") } url := newClientURL(tgtURL) if url.Host != "" { if !isURLVirtualHostStyle(url.Host) { if url.Path == string(url.Separator) { fatalIf(errInvalidArgument().Trace(tgtURL), fmt.Sprintf("Target ‘%s’ does not contain bucket name.", tgtURL)) } } } _, _, err := url2Stat(tgtURL) // we die on any error other than PathNotFound - destination directory need not exist. switch err.ToGoError().(type) { case PathNotFound: case ObjectMissing: default: fatalIf(err.Trace(tgtURL), fmt.Sprintf("Unable to stat target ‘%s’.", tgtURL)) } }
// main entry point for update command. func mainUpdate(ctx *cli.Context) { // Error out if 'update' command is issued for development based builds. if Version == "DEVELOPMENT.GOGET" { fatalIf(errors.New(""), "Update mechanism is not supported for ‘go get’ based binary builds. Please download official releases from https://minio.io/#minio") } // Check for update. var updateMsg updateMessage var errMsg string var err error if ctx.Bool("experimental") { updateMsg, errMsg, err = getReleaseUpdate(minioUpdateExperimentalURL) } else { updateMsg, errMsg, err = getReleaseUpdate(minioUpdateStableURL) } fatalIf(err, errMsg) console.Println(updateMsg) }
// main entry point for update command. func mainUpdate(ctx *cli.Context) error { // Set global flags from context. setGlobalsFromContext(ctx) // Additional command speific theme customization. console.SetColor("Update", color.New(color.FgGreen, color.Bold)) var updateMsg updateMessage var errMsg string var err *probe.Error // Check for update. if ctx.Bool("experimental") { updateMsg, errMsg, err = getReleaseUpdate(mcUpdateExperimentalURL) } else { updateMsg, errMsg, err = getReleaseUpdate(mcUpdateStableURL) } fatalIf(err, errMsg) printMsg(updateMsg) return nil }
// "minio control shutdown" entry point. func shutdownControl(c *cli.Context) { if len(c.Args()) != 1 { cli.ShowCommandHelpAndExit(c, "shutdown", 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 := &ShutdownArgs{Restart: c.Bool("restart")} err = client.Call("Controller.ShutdownHandler", args, &GenericReply{}) errorIf(err, "Shutting down Minio server at %s failed.", parsedURL.Host) }
// checkListSyntax - validate all the passed arguments func checkListSyntax(ctx *cli.Context) { args := ctx.Args() if !ctx.Args().Present() { args = []string{"."} } for _, arg := range args { if strings.TrimSpace(arg) == "" { fatalIf(errInvalidArgument().Trace(args...), "Unable to validate empty argument.") } } // extract URLs. URLs := ctx.Args() isIncomplete := ctx.Bool("incomplete") for _, url := range URLs { _, _, err := url2Stat(url) if err != nil && !isURLPrefixExists(url, isIncomplete) { fatalIf(err.Trace(url), "Unable to stat ‘"+url+"’.") } } }
// checkShareUploadSyntax - validate command-line args. func checkShareUploadSyntax(ctx *cli.Context) { args := ctx.Args() if !args.Present() { cli.ShowCommandHelpAndExit(ctx, "upload", 1) // last argument is exit code. } // Set command flags from context. isRecursive := ctx.Bool("recursive") expireArg := ctx.String("expire") // Parse expiry. expiry := shareDefaultExpiry 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 _, targetURL := range ctx.Args() { url := client.NewURL(targetURL) if strings.HasSuffix(targetURL, string(url.Separator)) && !isRecursive { fatalIf(errInvalidArgument().Trace(targetURL), "Use --recursive option to generate curl command for prefixes.") } } }
// main for rm command. func mainRm(ctx *cli.Context) error { // Set global flags from context. setGlobalsFromContext(ctx) // check 'rm' cli arguments. checkRmSyntax(ctx) // rm specific flags. isIncomplete := ctx.Bool("incomplete") isRecursive := ctx.Bool("recursive") isFake := ctx.Bool("fake") isStdin := ctx.Bool("stdin") older := ctx.Int("older-than") // Set color. console.SetColor("Remove", color.New(color.FgGreen, color.Bold)) // Support multiple targets. for _, url := range ctx.Args() { if isRecursive { return removeRecursive(url, isIncomplete, isFake, older) } // else { return removeSingle(url, isIncomplete, isFake, older) } if !isStdin { return nil } scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { url := scanner.Text() if isRecursive { return removeRecursive(url, isIncomplete, isFake, older) } // else { return removeSingle(url, isIncomplete, isFake, older) } return nil }
// Main entry point for mirror command. func mainMirror(ctx *cli.Context) error { // 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)) session := newSessionV8() session.Header.CommandType = "mirror" if v, err := os.Getwd(); err == nil { session.Header.RootPath = v } else { session.Delete() fatalIf(probe.NewError(err), "Unable to get current working folder.") } // Set command flags from context. session.Header.CommandBoolFlags["force"] = ctx.Bool("force") session.Header.CommandBoolFlags["fake"] = ctx.Bool("fake") session.Header.CommandBoolFlags["watch"] = ctx.Bool("watch") session.Header.CommandBoolFlags["remove"] = ctx.Bool("remove") // extract URLs. session.Header.CommandArgs = ctx.Args() ms := newMirrorSession(session) // Mirroring. ms.mirror() // delete will be run when terminating normally, ms.Delete() return nil }
// Validate command line arguments. func checkRmSyntax(ctx *cli.Context) { // Set command flags from context. isForce := ctx.Bool("force") isRecursive := ctx.Bool("recursive") isStdin := ctx.Bool("stdin") if !ctx.Args().Present() && !isStdin { exitCode := 1 cli.ShowCommandHelpAndExit(ctx, "rm", exitCode) } // For all recursive operations make sure to check for 'force' flag. if (isRecursive || isStdin) && !isForce { fatalIf(errDummy().Trace(), "Removal requires --force option. This operational is *IRREVERSIBLE*. Please review carefully before performing this *DANGEROUS* operation.") } }
// Validate command line arguments. func checkRmSyntax(ctx *cli.Context) { // Set command flags from context. isForce := ctx.Bool("force") isRecursive := ctx.Bool("recursive") isIncomplete := ctx.Bool("incomplete") if !ctx.Args().Present() { exitCode := 1 cli.ShowCommandHelpAndExit(ctx, "rm", exitCode) } if !isRecursive && !isIncomplete { for _, url := range ctx.Args() { if _, _, err := url2Stat(url); err != nil { fatalIf(err.Trace(url), "Unable to stat.") } } } if isRecursive && !isForce { fatalIf(errDummy().Trace(), "Recursive removal requires --force option. Please review carefully before performing this *DANGEROUS* operation.") } }
func mainWatch(ctx *cli.Context) error { console.SetColor("Time", color.New(color.FgGreen)) console.SetColor("Size", color.New(color.FgYellow)) console.SetColor("EventType", color.New(color.FgCyan, color.Bold)) console.SetColor("ObjectName", color.New(color.Bold)) setGlobalsFromContext(ctx) checkWatchSyntax(ctx) args := ctx.Args() path := args[0] prefix := ctx.String("prefix") suffix := ctx.String("suffix") events := strings.Split(ctx.String("events"), ",") recursive := ctx.Bool("recursive") s3Client, pErr := newClient(path) if pErr != nil { fatalIf(pErr.Trace(), "Cannot parse the provided url.") } params := watchParams{ recursive: recursive, accountID: fmt.Sprintf("%d", time.Now().Unix()), events: events, prefix: prefix, suffix: suffix, } // Start watching on events wo, err := s3Client.Watch(params) fatalIf(err, "Cannot watch on the specified bucket.") trapCh := signalTrap(os.Interrupt, syscall.SIGTERM) // Initialize.. waitgroup to track the go-routine. wg := sync.WaitGroup{} // Increment wait group to wait subsequent routine. wg.Add(1) // Start routine to watching on events. go func() { defer wg.Done() // Wait for all events. for { select { case <-trapCh: // Signal received we are done. close(wo.done) return case event, ok := <-wo.Events(): if !ok { return } msg := watchMessage{Event: event} printMsg(msg) case err, ok := <-wo.Errors(): if !ok { return } errorIf(err, "Cannot watch on events.") return } } }() // Wait on the routine to be finished or exit. wg.Wait() return nil }