diff --git a/Makefile b/Makefile index d1736d240c8..5f3c7dbde0e 100644 --- a/Makefile +++ b/Makefile @@ -381,6 +381,8 @@ $(BUILD)/gomod-lint: go.mod internal/tools/go.mod common/archiver/gcloud/go.mod # it's a coarse "you probably don't need to re-lint" filter, nothing more. $(BUILD)/code-lint: $(LINT_SRC) $(BIN)/revive | $(BUILD) $Q echo "lint..." + $Q # should probably be in a file but this is easier for now + $Q if grep -F 'cli.NewApp()' $(filter-out ./tools/common/commoncli/cli.go,$(LINT_SRC)); then echo -e "\nuse tools/common/commoncli instead\n" >&2 ; exit 1; fi $Q # non-optional vet checks. unfortunately these are not currently included in `go test`'s default behavior. $Q go vet -copylocks ./... ./common/archiver/gcloud/... $Q $(BIN)/revive -config revive.toml -exclude './vendor/...' -exclude './.gen/...' -formatter stylish ./... diff --git a/cmd/bench/main.go b/cmd/bench/main.go index 3b3fb01e8a5..2462c58a671 100644 --- a/cmd/bench/main.go +++ b/cmd/bench/main.go @@ -32,6 +32,7 @@ import ( "github.com/uber/cadence/bench" "github.com/uber/cadence/bench/lib" "github.com/uber/cadence/common/config" + "github.com/uber/cadence/tools/common/commoncli" ) const ( @@ -101,10 +102,7 @@ func getZone(c *cli.Context) string { } func buildCLI() *cli.App { - app := cli.NewApp() - app.Name = "cadence-bench" - app.Usage = "Cadence bench" - app.Version = "0.0.1" + app := commoncli.New("cadence-bench", "Cadence bench", "") app.Flags = []cli.Flag{ &cli.StringFlag{ @@ -152,8 +150,5 @@ func buildCLI() *cli.App { func main() { app := buildCLI() - if err := app.Run(os.Args); err != nil { - _, _ = fmt.Fprintln(app.ErrWriter, err) - os.Exit(1) - } + _ = app.Run(os.Args) // exits on error } diff --git a/cmd/canary/main.go b/cmd/canary/main.go index cdbf89e94ef..eda997446e1 100644 --- a/cmd/canary/main.go +++ b/cmd/canary/main.go @@ -31,6 +31,7 @@ import ( "github.com/uber/cadence/canary" "github.com/uber/cadence/common/config" + "github.com/uber/cadence/tools/common/commoncli" ) func startHandler(c *cli.Context) error { @@ -87,10 +88,7 @@ func getZone(c *cli.Context) string { } func buildCLI() *cli.App { - app := cli.NewApp() - app.Name = "cadence-canary" - app.Usage = "Cadence canary" - app.Version = "0.0.1" + app := commoncli.New("cadence-canary", "Cadence canary", "") app.Flags = []cli.Flag{ &cli.StringFlag{ @@ -147,8 +145,5 @@ func buildCLI() *cli.App { func main() { app := buildCLI() - if err := app.Run(os.Args); err != nil { - _, _ = fmt.Fprintln(app.ErrWriter, err) - os.Exit(1) - } + _ = app.Run(os.Args) // exits on error } diff --git a/cmd/server/cadence/cadence.go b/cmd/server/cadence/cadence.go index 1417bc2d42c..56941a57df0 100644 --- a/cmd/server/cadence/cadence.go +++ b/cmd/server/cadence/cadence.go @@ -37,6 +37,7 @@ import ( "github.com/uber/cadence/common/persistence/nosql/nosqlplugin/cassandra/gocql" "github.com/uber/cadence/common/service" "github.com/uber/cadence/tools/cassandra" + "github.com/uber/cadence/tools/common/commoncli" "github.com/uber/cadence/tools/sql" ) @@ -168,10 +169,7 @@ func BuildCLI(releaseVersion string, gitRevision string) *cli.App { " Note: Feature version is for compatibility checking between server and clients if enabled feature checking. Server is always backward compatible to older CLI versions, but not accepting newer than it can support.", releaseVersion, gitRevision, client.SupportedCLIVersion, client.SupportedGoSDKVersion, client.SupportedJavaSDKVersion) - app := cli.NewApp() - app.Name = "cadence" - app.Usage = "Cadence server" - app.Version = version + app := commoncli.New("cadence", "Cadence server", version) app.Flags = []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/server/main.go b/cmd/server/main.go index 545515599a0..00f02a11d70 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -21,7 +21,6 @@ package main import ( - "fmt" "os" "github.com/uber/cadence/cmd/server/cadence" @@ -38,8 +37,5 @@ import ( // main entry point for the cadence server func main() { app := cadence.BuildCLI(metrics.ReleaseVersion, metrics.Revision) - if err := app.Run(os.Args); err != nil { - _, _ = fmt.Fprintln(app.ErrWriter, err) - os.Exit(1) - } + _ = app.Run(os.Args) // exits on error } diff --git a/cmd/tools/cassandra/main.go b/cmd/tools/cassandra/main.go index 258cace839b..44199465ccb 100644 --- a/cmd/tools/cassandra/main.go +++ b/cmd/tools/cassandra/main.go @@ -21,7 +21,6 @@ package main import ( - "fmt" "os" "github.com/uber/cadence/tools/cassandra" @@ -30,9 +29,5 @@ import ( ) func main() { - err := cassandra.RunTool(os.Args) - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } + _ = cassandra.BuildCLIOptions().Run(os.Args) // exits on error } diff --git a/cmd/tools/cli/main.go b/cmd/tools/cli/main.go index 68adf9e016a..861e8d4f990 100644 --- a/cmd/tools/cli/main.go +++ b/cmd/tools/cli/main.go @@ -21,7 +21,6 @@ package main import ( - "fmt" "os" "github.com/uber/cadence/tools/cli" @@ -36,8 +35,5 @@ import ( // See cadence/tools/cli/README.md for usage func main() { app := cli.NewCliApp() - if err := app.Run(os.Args); err != nil { - _, _ = fmt.Fprintln(app.ErrWriter, err) - os.Exit(1) - } + _ = app.Run(os.Args) // exits on error } diff --git a/cmd/tools/sql/main.go b/cmd/tools/sql/main.go index af7ac714192..66923d6817b 100644 --- a/cmd/tools/sql/main.go +++ b/cmd/tools/sql/main.go @@ -21,7 +21,6 @@ package main import ( - "fmt" "os" "github.com/uber/cadence/tools/sql" @@ -31,9 +30,5 @@ import ( ) func main() { - err := sql.RunTool(os.Args) - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } + _ = sql.RunTool(os.Args) // exits on error } diff --git a/tools/cassandra/main.go b/tools/cassandra/main.go index 10d341f20cd..bad25c80f54 100644 --- a/tools/cassandra/main.go +++ b/tools/cassandra/main.go @@ -26,13 +26,14 @@ import ( "github.com/urfave/cli/v2" "github.com/uber/cadence/common/persistence/nosql/nosqlplugin/cassandra/gocql" + "github.com/uber/cadence/tools/common/commoncli" "github.com/uber/cadence/tools/common/schema" ) // RunTool runs the cadence-cassandra-tool command line tool func RunTool(args []string) error { app := BuildCLIOptions() - return app.Run(args) + return app.Run(args) // exits on error } // SetupSchema setups the cassandra schema @@ -62,11 +63,7 @@ func cliHandler(c *cli.Context, handler func(c *cli.Context) error) error { } func BuildCLIOptions() *cli.App { - - app := cli.NewApp() - app.Name = "cadence-cassandra-tool" - app.Usage = "Command line tool for cadence cassandra operations" - app.Version = "0.0.1" + app := commoncli.New("cadence-cassandra-tool", "Command line tool for cadence cassandra operations", "") app.Flags = []cli.Flag{ &cli.StringFlag{ diff --git a/tools/cli/admin_async_queue_commands.go b/tools/cli/admin_async_queue_commands.go index 6dc627db997..1261d0abd62 100644 --- a/tools/cli/admin_async_queue_commands.go +++ b/tools/cli/admin_async_queue_commands.go @@ -29,6 +29,7 @@ import ( "github.com/urfave/cli/v2" "github.com/uber/cadence/common/types" + "github.com/uber/cadence/tools/common/commoncli" ) func AdminGetAsyncWFConfig(c *cli.Context) error { @@ -45,7 +46,7 @@ func AdminGetAsyncWFConfig(c *cli.Context) error { resp, err := adminClient.GetDomainAsyncWorkflowConfiguraton(ctx, req) if err != nil { - return PrintableError("Failed to get async wf queue config", err) + return commoncli.Problem("Failed to get async wf queue config", err) } if resp == nil || resp.Configuration == nil { @@ -67,7 +68,7 @@ func AdminUpdateAsyncWFConfig(c *cli.Context) error { var cfg types.AsyncWorkflowConfiguration err := json.Unmarshal([]byte(asyncWFCfgJSON), &cfg) if err != nil { - return PrintableError("Failed to parse async workflow config", err) + return commoncli.Problem("Failed to parse async workflow config", err) } ctx, cancel := newContext(c) @@ -80,7 +81,7 @@ func AdminUpdateAsyncWFConfig(c *cli.Context) error { _, err = adminClient.UpdateDomainAsyncWorkflowConfiguraton(ctx, req) if err != nil { - return PrintableError("Failed to update async workflow queue config", err) + return commoncli.Problem("Failed to update async workflow queue config", err) } fmt.Printf("Successfully updated async workflow queue config for domain %s\n", domainName) diff --git a/tools/cli/admin_cluster_commands.go b/tools/cli/admin_cluster_commands.go index b5623ffe86a..fb2b10a074c 100644 --- a/tools/cli/admin_cluster_commands.go +++ b/tools/cli/admin_cluster_commands.go @@ -32,6 +32,7 @@ import ( "github.com/uber/cadence/common/types" "github.com/uber/cadence/common/visibility" "github.com/uber/cadence/service/worker/failovermanager" + "github.com/uber/cadence/tools/common/commoncli" ) // An indirection for the prompt function so that it can be mocked in the unit tests @@ -41,12 +42,12 @@ var promptFn = prompt func AdminAddSearchAttribute(c *cli.Context) error { key := getRequiredOption(c, FlagSearchAttributesKey) if err := visibility.ValidateSearchAttributeKey(key); err != nil { - return PrintableError("Invalid search-attribute key.", err) + return commoncli.Problem("Invalid search-attribute key.", err) } valType := getRequiredIntOption(c, FlagSearchAttributesType) if !isValueTypeValid(valType) { - return PrintableError("Unknown Search Attributes value type.", nil) + return commoncli.Problem("Unknown Search Attributes value type.", nil) } // ask user for confirmation @@ -66,7 +67,7 @@ func AdminAddSearchAttribute(c *cli.Context) error { err := adminClient.AddSearchAttribute(ctx, request) if err != nil { - return PrintableError("Add search attribute failed.", err) + return commoncli.Problem("Add search attribute failed.", err) } fmt.Println("Success. Note that for a multil-node Cadence cluster, DynamicConfig MUST be updated separately to whitelist the new attributes.") return nil @@ -80,7 +81,7 @@ func AdminDescribeCluster(c *cli.Context) error { defer cancel() response, err := adminClient.DescribeCluster(ctx) if err != nil { - return PrintableError("Operation DescribeCluster failed.", err) + return commoncli.Problem("Operation DescribeCluster failed.", err) } prettyPrintJSONObject(response) @@ -99,13 +100,13 @@ func AdminRebalanceStart(c *cli.Context) error { } input, err := json.Marshal(rbParams) if err != nil { - return PrintableError("Failed to serialize params for failover workflow", err) + return commoncli.Problem("Failed to serialize params for failover workflow", err) } memo, err := getWorkflowMemo(map[string]interface{}{ common.MemoKeyForOperator: getOperator(), }) if err != nil { - return PrintableError("Failed to serialize memo", err) + return commoncli.Problem("Failed to serialize memo", err) } request := &types.StartWorkflowExecutionRequest{ Domain: common.SystemLocalDomainName, @@ -127,7 +128,7 @@ func AdminRebalanceStart(c *cli.Context) error { resp, err := client.StartWorkflowExecution(tcCtx, request) if err != nil { - return PrintableError("Failed to start failover workflow", err) + return commoncli.Problem("Failed to start failover workflow", err) } fmt.Println("Rebalance workflow started") fmt.Println("wid: " + workflowID) diff --git a/tools/cli/admin_commands.go b/tools/cli/admin_commands.go index 6fc4ec6ae50..53f231da69f 100644 --- a/tools/cli/admin_commands.go +++ b/tools/cli/admin_commands.go @@ -36,6 +36,7 @@ import ( "github.com/uber/cadence/common/persistence" "github.com/uber/cadence/common/types" "github.com/uber/cadence/common/types/mapper/thrift" + "github.com/uber/cadence/tools/common/commoncli" ) const ( @@ -63,7 +64,7 @@ func AdminShowWorkflow(c *cli.Context) error { BranchID: &bid, }) if err != nil { - return PrintableError("encoding branch token err", err) + return commoncli.Problem("encoding branch token err", err) } resp, err := histV2.ReadRawHistoryBranch(ctx, &persistence.ReadHistoryBranchRequest{ BranchToken: branchToken, @@ -74,16 +75,16 @@ func AdminShowWorkflow(c *cli.Context) error { DomainName: domainName, }) if err != nil { - return PrintableError("ReadHistoryBranch err", err) + return commoncli.Problem("ReadHistoryBranch err", err) } history = resp.HistoryEventBlobs } else { - return PrintableError("need to specify TreeID/BranchID/ShardID", nil) + return commoncli.Problem("need to specify TreeID/BranchID/ShardID", nil) } if len(history) == 0 { - return PrintableError("no events", nil) + return commoncli.Problem("no events", nil) } allEvents := &shared.History{} totalSize := 0 @@ -92,14 +93,14 @@ func AdminShowWorkflow(c *cli.Context) error { fmt.Printf("======== batch %v, blob len: %v ======\n", idx+1, len(b.Data)) internalHistoryBatch, err := serializer.DeserializeBatchEvents(b) if err != nil { - return PrintableError("DeserializeBatchEvents err", err) + return commoncli.Problem("DeserializeBatchEvents err", err) } historyBatch := thrift.FromHistoryEventArray(internalHistoryBatch) allEvents.Events = append(allEvents.Events, historyBatch...) for _, e := range historyBatch { jsonstr, err := json.Marshal(e) if err != nil { - return PrintableError("json.Marshal err", err) + return commoncli.Problem("json.Marshal err", err) } fmt.Println(string(jsonstr)) } @@ -109,10 +110,10 @@ func AdminShowWorkflow(c *cli.Context) error { if outputFileName != "" { data, err := json.Marshal(allEvents.Events) if err != nil { - return PrintableError("Failed to serialize history data.", err) + return commoncli.Problem("Failed to serialize history data.", err) } if err := ioutil.WriteFile(outputFileName, data, 0666); err != nil { - return PrintableError("Failed to export history data file.", err) + return commoncli.Problem("Failed to export history data file.", err) } } return nil @@ -132,14 +133,14 @@ func AdminDescribeWorkflow(c *cli.Context) error { ms := persistence.WorkflowMutableState{} err := json.Unmarshal([]byte(msStr), &ms) if err != nil { - return PrintableError("json.Unmarshal err", err) + return commoncli.Problem("json.Unmarshal err", err) } currentBranchToken := ms.ExecutionInfo.BranchToken if ms.VersionHistories != nil { // if VersionHistories is set, then all branch infos are stored in VersionHistories currentVersionHistory, err := ms.VersionHistories.GetCurrentVersionHistory() if err != nil { - return PrintableError("ms.VersionHistories.GetCurrentVersionHistory err", err) + return commoncli.Problem("ms.VersionHistories.GetCurrentVersionHistory err", err) } currentBranchToken = currentVersionHistory.GetBranchToken() } @@ -148,7 +149,7 @@ func AdminDescribeWorkflow(c *cli.Context) error { thriftrwEncoder := codec.NewThriftRWEncoder() err = thriftrwEncoder.Decode(currentBranchToken, &branchInfo) if err != nil { - return PrintableError("thriftrwEncoder.Decode err", err) + return commoncli.Problem("thriftrwEncoder.Decode err", err) } prettyPrintJSONObject(branchInfo) if ms.ExecutionInfo.AutoResetPoints != nil { @@ -184,7 +185,7 @@ func describeMutableState(c *cli.Context) (*types.AdminDescribeWorkflowExecution }, ) if err != nil { - return nil, PrintableError("Get workflow mutableState failed", err) + return nil, commoncli.Problem("Get workflow mutableState failed", err) } return resp, nil } @@ -210,7 +211,7 @@ func AdminMaintainCorruptWorkflow(c *cli.Context) error { defer cancel() _, err := adminClient.MaintainCorruptWorkflow(ctx, request) if err != nil { - return PrintableError("Operation AdminMaintainCorruptWorkflow failed.", err) + return commoncli.Problem("Operation AdminMaintainCorruptWorkflow failed.", err) } return err @@ -243,7 +244,7 @@ func AdminDeleteWorkflow(c *cli.Context) error { } _, err := adminClient.DeleteWorkflow(ctx, request) if err != nil { - return PrintableError("Operation AdminMaintainCorruptWorkflow failed.", err) + return commoncli.Problem("Operation AdminMaintainCorruptWorkflow failed.", err) } return nil } @@ -256,14 +257,14 @@ func AdminDeleteWorkflow(c *cli.Context) error { ms := persistence.WorkflowMutableState{} err = json.Unmarshal([]byte(msStr), &ms) if err != nil { - return PrintableError("json.Unmarshal err", err) + return commoncli.Problem("json.Unmarshal err", err) } domainID := ms.ExecutionInfo.DomainID shardID := resp.GetShardID() shardIDInt, err := strconv.Atoi(shardID) if err != nil { - return PrintableError("strconv.Atoi(shardID) err", err) + return commoncli.Problem("strconv.Atoi(shardID) err", err) } histV2 := initializeHistoryManager(c) defer histV2.Close() @@ -283,7 +284,7 @@ func AdminDeleteWorkflow(c *cli.Context) error { for _, branchToken := range branchTokens { err = thriftrwEncoder.Decode(branchToken, &branchInfo) if err != nil { - return PrintableError("thriftrwEncoder.Decode err", err) + return commoncli.Problem("thriftrwEncoder.Decode err", err) } fmt.Println("deleting history events for ...") prettyPrintJSONObject(branchInfo) @@ -296,7 +297,7 @@ func AdminDeleteWorkflow(c *cli.Context) error { if skipError { fmt.Println("failed to delete history, ", err) } else { - return PrintableError("DeleteHistoryBranch err", err) + return commoncli.Problem("DeleteHistoryBranch err", err) } } } @@ -313,7 +314,7 @@ func AdminDeleteWorkflow(c *cli.Context) error { if skipError { fmt.Println("delete mutableState row failed, ", err) } else { - return PrintableError("delete mutableState row failed", err) + return commoncli.Problem("delete mutableState row failed", err) } } fmt.Println("delete mutableState row successfully") @@ -329,7 +330,7 @@ func AdminDeleteWorkflow(c *cli.Context) error { if skipError { fmt.Println("delete current row failed, ", err) } else { - return PrintableError("delete current row failed", err) + return commoncli.Problem("delete current row failed", err) } } fmt.Println("delete current row successfully") @@ -341,7 +342,7 @@ func AdminGetDomainIDOrName(c *cli.Context) error { domainID := c.String(FlagDomainID) domainName := c.String(FlagDomain) if len(domainID) == 0 && len(domainName) == 0 { - return PrintableError("Need either domainName or domainID", nil) + return commoncli.Problem("Need either domainName or domainID", nil) } domainManager := initializeDomainManager(c) @@ -351,13 +352,13 @@ func AdminGetDomainIDOrName(c *cli.Context) error { if len(domainID) > 0 { domain, err := domainManager.GetDomain(ctx, &persistence.GetDomainRequest{ID: domainID}) if err != nil { - return PrintableError("SelectDomain error", err) + return commoncli.Problem("SelectDomain error", err) } fmt.Printf("domainName for domainID %v is %v \n", domainID, domain.Info.Name) } else { domain, err := domainManager.GetDomain(ctx, &persistence.GetDomainRequest{Name: domainName}) if err != nil { - return PrintableError("SelectDomain error", err) + return commoncli.Problem("SelectDomain error", err) } fmt.Printf("domainID for domainName %v is %v \n", domain.Info.ID, domainID) } @@ -370,7 +371,7 @@ func AdminGetShardID(c *cli.Context) error { numberOfShards := c.Int(FlagNumberOfShards) if numberOfShards <= 0 { - return PrintableError("numberOfShards is required", nil) + return commoncli.Problem("numberOfShards is required", nil) } shardID := common.WorkflowIDToHistoryShard(wid, numberOfShards) fmt.Printf("ShardID for workflowID: %v is %v \n", wid, shardID) @@ -403,7 +404,7 @@ func AdminRemoveTask(c *cli.Context) error { err := adminClient.RemoveTask(ctx, req) if err != nil { - return PrintableError("Remove task has failed", err) + return commoncli.Problem("Remove task has failed", err) } return nil } @@ -419,7 +420,7 @@ func AdminDescribeShard(c *cli.Context) error { getShardReq := &persistence.GetShardRequest{ShardID: sid} shard, err := shardManager.GetShard(ctx, getShardReq) if err != nil { - return PrintableError("Failed to describe shard.", err) + return commoncli.Problem("Failed to describe shard.", err) } prettyPrintJSONObject(shard) @@ -437,7 +438,7 @@ func AdminSetShardRangeID(c *cli.Context) error { getShardResp, err := shardManager.GetShard(ctx, &persistence.GetShardRequest{ShardID: sid}) if err != nil { - return PrintableError("Failed to get shardInfo.", err) + return commoncli.Problem("Failed to get shardInfo.", err) } previousRangeID := getShardResp.ShardInfo.RangeID @@ -451,7 +452,7 @@ func AdminSetShardRangeID(c *cli.Context) error { PreviousRangeID: previousRangeID, ShardInfo: updatedShardInfo, }); err != nil { - return PrintableError("Failed to reset shard rangeID.", err) + return commoncli.Problem("Failed to reset shard rangeID.", err) } fmt.Printf("Successfully updated rangeID from %v to %v for shard %v.\n", previousRangeID, rid, sid) @@ -471,7 +472,7 @@ func AdminCloseShard(c *cli.Context) error { err := adminClient.CloseShard(ctx, req) if err != nil { - return PrintableError("Close shard task has failed", err) + return commoncli.Problem("Close shard task has failed", err) } return nil } @@ -495,7 +496,7 @@ func AdminDescribeShardDistribution(c *cli.Context) error { resp, err := adminClient.DescribeShardDistribution(ctx, req) if err != nil { - return PrintableError("Shard list failed", err) + return commoncli.Problem("Shard list failed", err) } fmt.Printf("Total Number of Shards: %d \n", resp.NumberOfShards) @@ -536,7 +537,7 @@ func AdminDescribeHistoryHost(c *cli.Context) error { printFully := c.Bool(FlagPrintFullyDetail) if len(wid) == 0 && !c.IsSet(FlagShardID) && len(addr) == 0 { - return PrintableError("at least one of them is required to provide to lookup host: workflowID, shardID and host address", nil) + return commoncli.Problem("at least one of them is required to provide to lookup host: workflowID, shardID and host address", nil) } ctx, cancel := newContext(c) @@ -555,7 +556,7 @@ func AdminDescribeHistoryHost(c *cli.Context) error { resp, err := adminClient.DescribeHistoryHost(ctx, req) if err != nil { - return PrintableError("Describe history host failed", err) + return commoncli.Problem("Describe history host failed", err) } if !printFully { @@ -584,7 +585,7 @@ func AdminRefreshWorkflowTasks(c *cli.Context) error { }, }) if err != nil { - return PrintableError("Refresh workflow task failed", err) + return commoncli.Problem("Refresh workflow task failed", err) } fmt.Println("Refresh workflow task succeeded.") return nil @@ -609,7 +610,7 @@ func AdminResetQueue(c *cli.Context) error { err := adminClient.ResetQueue(ctx, req) if err != nil { - return PrintableError("Failed to reset queue", err) + return commoncli.Problem("Failed to reset queue", err) } fmt.Println("Reset queue state succeeded") return nil @@ -634,7 +635,7 @@ func AdminDescribeQueue(c *cli.Context) error { resp, err := adminClient.DescribeQueue(ctx, req) if err != nil { - return PrintableError("Failed to describe queue", err) + return commoncli.Problem("Failed to describe queue", err) } for _, state := range resp.ProcessingQueueStates { diff --git a/tools/cli/admin_config_store_commands.go b/tools/cli/admin_config_store_commands.go index 7a82f06e2c8..f1206a5218f 100644 --- a/tools/cli/admin_config_store_commands.go +++ b/tools/cli/admin_config_store_commands.go @@ -30,6 +30,7 @@ import ( "github.com/uber/cadence/common/dynamicconfig" "github.com/uber/cadence/common/types" + "github.com/uber/cadence/tools/common/commoncli" ) type cliEntry struct { @@ -65,7 +66,7 @@ func AdminGetDynamicConfig(c *cli.Context) error { val, err := adminClient.ListDynamicConfig(ctx, req) if err != nil { - return PrintableError("Failed to get dynamic config value(s)", err) + return commoncli.Problem("Failed to get dynamic config value(s)", err) } if val == nil || val.Entries == nil || len(val.Entries) == 0 { @@ -84,7 +85,7 @@ func AdminGetDynamicConfig(c *cli.Context) error { } else { parsedFilters, err := parseInputFilterArray(filters) if err != nil { - return PrintableError("Failed to parse input filter array", err) + return commoncli.Problem("Failed to parse input filter array", err) } req := &types.GetDynamicConfigRequest{ @@ -94,13 +95,13 @@ func AdminGetDynamicConfig(c *cli.Context) error { val, err := adminClient.GetDynamicConfig(ctx, req) if err != nil { - return PrintableError("Failed to get dynamic config value", err) + return commoncli.Problem("Failed to get dynamic config value", err) } var umVal interface{} err = json.Unmarshal(val.Value.Data, &umVal) if err != nil { - return PrintableError("Failed to unmarshal response", err) + return commoncli.Problem("Failed to unmarshal response", err) } if umVal == nil { @@ -131,11 +132,11 @@ func AdminUpdateDynamicConfig(c *cli.Context) error { var parsedInputValue *cliValue err := json.Unmarshal([]byte(valueString), &parsedInputValue) if err != nil { - return PrintableError("Unable to unmarshal value to inputValue", err) + return commoncli.Problem("Unable to unmarshal value to inputValue", err) } parsedValue, err := convertFromInputValue(parsedInputValue) if err != nil { - return PrintableError("Unable to convert from inputValue to DynamicConfigValue", err) + return commoncli.Problem("Unable to convert from inputValue to DynamicConfigValue", err) } parsedValues = append(parsedValues, parsedValue) } @@ -150,7 +151,7 @@ func AdminUpdateDynamicConfig(c *cli.Context) error { err := adminClient.UpdateDynamicConfig(ctx, req) if err != nil { - return PrintableError("Failed to update dynamic config value", err) + return commoncli.Problem("Failed to update dynamic config value", err) } fmt.Printf("Dynamic Config %q updated with %s \n", dcName, dcValues) return nil @@ -168,7 +169,7 @@ func AdminRestoreDynamicConfig(c *cli.Context) error { parsedFilters, err := parseInputFilterArray(filters) if err != nil { - return PrintableError("Failed to parse input filter array", err) + return commoncli.Problem("Failed to parse input filter array", err) } req := &types.RestoreDynamicConfigRequest{ @@ -178,7 +179,7 @@ func AdminRestoreDynamicConfig(c *cli.Context) error { err = adminClient.RestoreDynamicConfig(ctx, req) if err != nil { - return PrintableError("Failed to restore dynamic config value", err) + return commoncli.Problem("Failed to restore dynamic config value", err) } fmt.Printf("Dynamic Config %q restored\n", dcName) return nil @@ -197,7 +198,7 @@ func AdminListDynamicConfig(c *cli.Context) error { val, err := adminClient.ListDynamicConfig(ctx, req) if err != nil { - return PrintableError("Failed to list dynamic config value(s)", err) + return commoncli.Problem("Failed to list dynamic config value(s)", err) } if val == nil || val.Entries == nil || len(val.Entries) == 0 { diff --git a/tools/cli/admin_db_clean_command.go b/tools/cli/admin_db_clean_command.go index dc5d5dba7c9..4208a551700 100644 --- a/tools/cli/admin_db_clean_command.go +++ b/tools/cli/admin_db_clean_command.go @@ -38,6 +38,7 @@ import ( "github.com/uber/cadence/common/reconciliation/invariant" "github.com/uber/cadence/common/reconciliation/store" "github.com/uber/cadence/service/worker/scanner/executions" + "github.com/uber/cadence/tools/common/commoncli" ) // AdminDBClean is the command to clean up unhealthy executions. @@ -46,7 +47,7 @@ func AdminDBClean(c *cli.Context) error { scanType, err := executions.ScanTypeString(getRequiredOption(c, FlagScanType)) if err != nil { - return PrintableError("unknown scan type", err) + return commoncli.Problem("unknown scan type", err) } collectionSlice := c.StringSlice(FlagInvariantCollection) blob := scanType.ToBlobstoreEntity() @@ -55,7 +56,7 @@ func AdminDBClean(c *cli.Context) error { for _, v := range collectionSlice { collection, err := invariant.CollectionString(v) if err != nil { - return PrintableError("unknown invariant collection", err) + return commoncli.Problem("unknown invariant collection", err) } collections = append(collections, collection) } @@ -65,13 +66,13 @@ func AdminDBClean(c *cli.Context) error { logger, err = zap.NewDevelopment() if err != nil { // probably impossible with default config - return PrintableError("could not construct logger", err) + return commoncli.Problem("could not construct logger", err) } } invariants := scanType.ToInvariants(collections, logger) if len(invariants) < 1 { - return PrintableError( + return commoncli.Problem( fmt.Sprintf("no invariants for scantype %q and collections %q", scanType.String(), collectionSlice), @@ -83,7 +84,7 @@ func AdminDBClean(c *cli.Context) error { dec := json.NewDecoder(input) if err != nil { - return PrintableError("", err) + return commoncli.Problem("", err) } var data []*store.ScanOutputEntity diff --git a/tools/cli/admin_db_decode_thrift.go b/tools/cli/admin_db_decode_thrift.go index a382f099efe..1fbfdbae35e 100644 --- a/tools/cli/admin_db_decode_thrift.go +++ b/tools/cli/admin_db_decode_thrift.go @@ -39,6 +39,7 @@ import ( "github.com/uber/cadence/.gen/go/shared" "github.com/uber/cadence/.gen/go/sqlblobs" "github.com/uber/cadence/common/codec" + "github.com/uber/cadence/tools/common/commoncli" ) var decodingTypes = map[string]func() codec.ThriftObject{ @@ -80,11 +81,11 @@ func AdminDBDataDecodeThrift(c *cli.Context) error { encoding := c.String(FlagInputEncoding) data, err := decodeUserInput(input, encoding) if err != nil { - return PrintableError("failed to decode input", err) + return commoncli.Problem("failed to decode input", err) } if _, err := decodeThriftPayload(data); err != nil { - return PrintableError("failed to decode thrift payload", err.err) + return commoncli.Problem("failed to decode thrift payload", err.err) } return nil } diff --git a/tools/cli/admin_db_scan_command.go b/tools/cli/admin_db_scan_command.go index 06281df68c7..792fec116d0 100644 --- a/tools/cli/admin_db_scan_command.go +++ b/tools/cli/admin_db_scan_command.go @@ -41,6 +41,7 @@ import ( "github.com/uber/cadence/common/reconciliation/invariant" "github.com/uber/cadence/common/reconciliation/store" "github.com/uber/cadence/service/worker/scanner/executions" + "github.com/uber/cadence/tools/common/commoncli" ) const ( @@ -52,7 +53,7 @@ func AdminDBScan(c *cli.Context) error { scanType, err := executions.ScanTypeString(c.String(FlagScanType)) if err != nil { - return PrintableError("unknown scan type", err) + return commoncli.Problem("unknown scan type", err) } numberOfShards := getRequiredIntOption(c, FlagNumberOfShards) @@ -62,7 +63,7 @@ func AdminDBScan(c *cli.Context) error { for _, v := range collectionSlice { collection, err := invariant.CollectionString(v) if err != nil { - return PrintableError("unknown invariant collection", err) + return commoncli.Problem("unknown invariant collection", err) } collections = append(collections, collection) } @@ -72,13 +73,13 @@ func AdminDBScan(c *cli.Context) error { logger, err = zap.NewDevelopment() if err != nil { // probably impossible with default config - return PrintableError("could not construct logger", err) + return commoncli.Problem("could not construct logger", err) } } invariants := scanType.ToInvariants(collections, logger) if len(invariants) < 1 { - return PrintableError( + return commoncli.Problem( fmt.Sprintf("no invariants for scan type %q and collections %q", scanType.String(), collectionSlice), @@ -91,7 +92,7 @@ func AdminDBScan(c *cli.Context) error { dec := json.NewDecoder(input) if err != nil { - return PrintableError("", err) + return commoncli.Problem("", err) } var data []fetcher.ExecutionRequest @@ -210,7 +211,7 @@ func listExecutionsByShardID( for executionIterator.HasNext() { result, err := executionIterator.Next() if err != nil { - return PrintableError(fmt.Sprintf("Failed to scan shard ID: %v for unsupported workflow. Please retry.", shardID), err) + return commoncli.Problem(fmt.Sprintf("Failed to scan shard ID: %v for unsupported workflow. Please retry.", shardID), err) } execution := result.(*persistence.ListConcreteExecutionsEntity) executionInfo := execution.ExecutionInfo @@ -222,10 +223,10 @@ func listExecutionsByShardID( executionInfo.RunID, ) if _, err = outputFile.WriteString(outStr); err != nil { - return PrintableError("Failed to write data to file", err) + return commoncli.Problem("Failed to write data to file", err) } if err = outputFile.Sync(); err != nil { - return PrintableError("Failed to sync data to file", err) + return commoncli.Problem("Failed to sync data to file", err) } } } diff --git a/tools/cli/admin_dlq_commands.go b/tools/cli/admin_dlq_commands.go index 679b22595c8..c694dbd7910 100644 --- a/tools/cli/admin_dlq_commands.go +++ b/tools/cli/admin_dlq_commands.go @@ -35,6 +35,7 @@ import ( "github.com/uber/cadence/common" "github.com/uber/cadence/common/persistence" "github.com/uber/cadence/common/types" + "github.com/uber/cadence/tools/common/commoncli" ) const ( @@ -149,7 +150,7 @@ func AdminGetDLQMessages(c *cli.Context) error { resp, err := client.DescribeDomain(ctx, &types.DescribeDomainRequest{UUID: common.StringPtr(domainId)}) if err != nil { - return "", PrintableError("failed to describe domain", err) + return "", commoncli.Problem("failed to describe domain", err) } domainNames[domainId] = resp.DomainInfo.Name return resp.DomainInfo.Name, nil @@ -169,7 +170,7 @@ func AdminGetDLQMessages(c *cli.Context) error { NextPageToken: pageToken, }) if err != nil { - return nil, PrintableError(fmt.Sprintf("fail to read dlq message for shard: %d", shardID), err) + return nil, commoncli.Problem(fmt.Sprintf("fail to read dlq message for shard: %d", shardID), err) } replicationTasks := map[int64]*types.ReplicationTask{} diff --git a/tools/cli/admin_elastic_search_commands.go b/tools/cli/admin_elastic_search_commands.go index 2f44651493d..bafdebf4312 100644 --- a/tools/cli/admin_elastic_search_commands.go +++ b/tools/cli/admin_elastic_search_commands.go @@ -41,6 +41,7 @@ import ( es "github.com/uber/cadence/common/elasticsearch" "github.com/uber/cadence/common/elasticsearch/esql" "github.com/uber/cadence/common/tokenbucket" + "github.com/uber/cadence/tools/common/commoncli" ) const ( @@ -89,7 +90,7 @@ func AdminCatIndices(c *cli.Context) error { ctx := context.Background() resp, err := esClient.CatIndices().Do(ctx) if err != nil { - return PrintableError("Unable to cat indices", err) + return commoncli.Problem("Unable to cat indices", err) } table := []ESIndexRow{} @@ -118,17 +119,17 @@ func AdminIndex(c *cli.Context) error { messages, err := parseIndexerMessage(inputFileName) if err != nil { - return PrintableError("Unable to parse indexer message", err) + return commoncli.Problem("Unable to parse indexer message", err) } bulkRequest := esClient.Bulk() bulkConductFn := func() error { _, err := bulkRequest.Do(context.Background()) if err != nil { - return PrintableError("Bulk failed", err) + return commoncli.Problem("Bulk failed", err) } if bulkRequest.NumberOfActions() != 0 { - return PrintableError(fmt.Sprintf("Bulk request not done, %d", bulkRequest.NumberOfActions()), err) + return commoncli.Problem(fmt.Sprintf("Bulk request not done, %d", bulkRequest.NumberOfActions()), err) } return nil } @@ -160,7 +161,7 @@ func AdminIndex(c *cli.Context) error { Id(docID). VersionType("internal") default: - return PrintableError("Unknown message type", nil) + return commoncli.Problem("Unknown message type", nil) } bulkRequest.Add(req) @@ -191,7 +192,7 @@ func AdminDelete(c *cli.Context) error { // #nosec file, err := os.Open(inputFileName) if err != nil { - return PrintableError("Cannot open input file", nil) + return commoncli.Problem("Cannot open input file", nil) } defer file.Close() scanner := bufio.NewScanner(file) @@ -207,10 +208,10 @@ func AdminDelete(c *cli.Context) error { } _, err := bulkRequest.Do(context.Background()) if err != nil { - return PrintableError(fmt.Sprintf("Bulk failed, current processed row %d", i), err) + return commoncli.Problem(fmt.Sprintf("Bulk failed, current processed row %d", i), err) } if bulkRequest.NumberOfActions() != 0 { - return PrintableError(fmt.Sprintf("Bulk request not done, current processed row %d", i), err) + return commoncli.Problem(fmt.Sprintf("Bulk request not done, current processed row %d", i), err) } return nil } @@ -337,13 +338,13 @@ func GenerateReport(c *cli.Context) error { e.ProcessQueryValue(timeKeyFilter, timeValProcess) dsl, sortFields, err := e.ConvertPrettyCadence(sql, "") if err != nil { - return PrintableError("Fail to convert sql to dsl", err) + return commoncli.Problem("Fail to convert sql to dsl", err) } // query client resp, err := esClient.Search(index).Source(dsl).Do(ctx) if err != nil { - return PrintableError("Fail to talk with ES", err) + return commoncli.Problem("Fail to talk with ES", err) } // Show result to terminal @@ -353,7 +354,7 @@ func GenerateReport(c *cli.Context) error { var buckets []interface{} err = json.Unmarshal(*resp.Aggregations["groupby"], &groupby) if err != nil { - return PrintableError("Fail to parse groupby", err) + return commoncli.Problem("Fail to parse groupby", err) } buckets = groupby["buckets"].([]interface{}) if len(buckets) == 0 { @@ -448,7 +449,7 @@ func GenerateReport(c *cli.Context) error { case "csv", "CSV": return generateCSVReport(reportFilePath, headers, tableData) default: - return PrintableError(fmt.Sprintf(`Report format %v not supported.`, reportFormat), nil) + return commoncli.Problem(fmt.Sprintf(`Report format %v not supported.`, reportFormat), nil) } } @@ -456,7 +457,7 @@ func generateCSVReport(reportFileName string, headers []string, tableData [][]st // write csv report f, err := os.Create(reportFileName) if err != nil { - return PrintableError("Fail to create csv report file", err) + return commoncli.Problem("Fail to create csv report file", err) } csvContent := strings.Join(headers, ",") + "\n" for _, data := range tableData { @@ -473,7 +474,7 @@ func generateHTMLReport(reportFileName string, numBuckKeys int, sorted bool, hea // write html report f, err := os.Create(reportFileName) if err != nil { - return PrintableError("Fail to create html report file", err) + return commoncli.Problem("Fail to create html report file", err) } var htmlContent string m, n := len(headers), len(tableData) diff --git a/tools/cli/admin_failover_commands.go b/tools/cli/admin_failover_commands.go index bd3c90a520d..52150a58e22 100644 --- a/tools/cli/admin_failover_commands.go +++ b/tools/cli/admin_failover_commands.go @@ -34,6 +34,7 @@ import ( "github.com/uber/cadence/common" "github.com/uber/cadence/common/types" "github.com/uber/cadence/service/worker/failovermanager" + "github.com/uber/cadence/tools/common/commoncli" ) const ( @@ -75,7 +76,7 @@ func AdminFailoverStart(c *cli.Context) error { func AdminFailoverPause(c *cli.Context) error { err := executePauseOrResume(c, getFailoverWorkflowID(c), true) if err != nil { - return PrintableError("Failed to pause failover workflow", err) + return commoncli.Problem("Failed to pause failover workflow", err) } fmt.Println("Failover paused on " + getFailoverWorkflowID(c)) return nil @@ -85,7 +86,7 @@ func AdminFailoverPause(c *cli.Context) error { func AdminFailoverResume(c *cli.Context) error { err := executePauseOrResume(c, getFailoverWorkflowID(c), false) if err != nil { - return PrintableError("Failed to resume failover workflow", err) + return commoncli.Problem("Failed to resume failover workflow", err) } fmt.Println("Failover resumed on " + getFailoverWorkflowID(c)) return nil @@ -112,7 +113,7 @@ func AdminFailoverQuery(c *cli.Context) error { descResp, err := client.DescribeWorkflowExecution(tcCtx, request) if err != nil { - return PrintableError("Failed to describe workflow", err) + return commoncli.Problem("Failed to describe workflow", err) } if isWorkflowTerminated(descResp) { result.State = failovermanager.WorkflowAborted @@ -144,7 +145,7 @@ func AdminFailoverAbort(c *cli.Context) error { err := client.TerminateWorkflowExecution(tcCtx, request) if err != nil { - return PrintableError("Failed to abort failover workflow", err) + return commoncli.Problem("Failed to abort failover workflow", err) } fmt.Println("Failover aborted") @@ -176,7 +177,7 @@ func AdminFailoverRollback(c *cli.Context) error { err := client.TerminateWorkflowExecution(tcCtx, request) if err != nil { - return PrintableError("Failed to terminate failover workflow", err) + return commoncli.Problem("Failed to terminate failover workflow", err) } } // query again @@ -230,16 +231,16 @@ func query( } queryResp, err := client.QueryWorkflow(tcCtx, request) if err != nil { - return nil, PrintableError("Failed to query failover workflow", err) + return nil, commoncli.Problem("Failed to query failover workflow", err) } if queryResp.GetQueryResult() == nil { - return nil, PrintableError("QueryResult has no value", nil) + return nil, commoncli.Problem("QueryResult has no value", nil) } var queryResult failovermanager.QueryResult err = json.Unmarshal(queryResp.GetQueryResult(), &queryResult) if err != nil { - return nil, PrintableError("Unable to deserialize QueryResult", nil) + return nil, commoncli.Problem("Unable to deserialize QueryResult", nil) } return &queryResult, nil } @@ -284,7 +285,7 @@ func failoverStart(c *cli.Context, params *startParams) error { common.MemoKeyForOperator: getOperator(), }) if err != nil { - return PrintableError("Failed to serialize memo", err) + return commoncli.Problem("Failed to serialize memo", err) } request := &types.StartWorkflowExecutionRequest{ Domain: common.SystemLocalDomainName, @@ -302,7 +303,7 @@ func failoverStart(c *cli.Context, params *startParams) error { request.CronSchedule = params.cron } else { if len(params.cron) > 0 { - return PrintableError("The drill wait time is required when cron is specified.", nil) + return commoncli.Problem("The drill wait time is required when cron is specified.", nil) } // block if there is an on-going failover drill @@ -313,7 +314,7 @@ func failoverStart(c *cli.Context, params *startParams) error { case *types.WorkflowExecutionAlreadyCompletedError: break default: - return PrintableError("Failed to send pase signal to drill workflow", err) + return commoncli.Problem("Failed to send pase signal to drill workflow", err) } } fmt.Println("The failover drill workflow is paused. Please run 'cadence admin cluster failover resume --fd'" + @@ -331,12 +332,12 @@ func failoverStart(c *cli.Context, params *startParams) error { } input, err := json.Marshal(foParams) if err != nil { - return PrintableError("Failed to serialize Failover Params", err) + return commoncli.Problem("Failed to serialize Failover Params", err) } request.Input = input wf, err := client.StartWorkflowExecution(tcCtx, request) if err != nil { - return PrintableError("Failed to start failover workflow", err) + return commoncli.Problem("Failed to start failover workflow", err) } fmt.Println("Failover workflow started") fmt.Println("wid: " + workflowID) diff --git a/tools/cli/admin_kafka_commands.go b/tools/cli/admin_kafka_commands.go index abb6fbec6e3..2cde87624e2 100644 --- a/tools/cli/admin_kafka_commands.go +++ b/tools/cli/admin_kafka_commands.go @@ -45,6 +45,7 @@ import ( "github.com/uber/cadence/common/persistence" "github.com/uber/cadence/common/types" "github.com/uber/cadence/common/types/mapper/thrift" + "github.com/uber/cadence/tools/common/commoncli" ) type ( @@ -470,7 +471,7 @@ func doRereplicate( EndVersion: endEventVersion, }, ); err != nil { - return PrintableError("Failed to resend ndc workflow", err) + return commoncli.Problem("Failed to resend ndc workflow", err) } fmt.Printf("Done rereplication for wid: %v, rid:%v \n", wid, rid) return nil diff --git a/tools/cli/admin_task_list_commands.go b/tools/cli/admin_task_list_commands.go index 01fc2579b41..1c70cf10a6e 100644 --- a/tools/cli/admin_task_list_commands.go +++ b/tools/cli/admin_task_list_commands.go @@ -28,6 +28,7 @@ import ( "github.com/urfave/cli/v2" "github.com/uber/cadence/common/types" + "github.com/uber/cadence/tools/common/commoncli" ) type ( @@ -67,12 +68,12 @@ func AdminDescribeTaskList(c *cli.Context) error { response, err := frontendClient.DescribeTaskList(ctx, request) if err != nil { - return PrintableError("Operation DescribeTaskList failed.", err) + return commoncli.Problem("Operation DescribeTaskList failed.", err) } taskListStatus := response.GetTaskListStatus() if taskListStatus == nil { - return PrintableError(colorMagenta("No tasklist status information."), nil) + return commoncli.Problem(colorMagenta("No tasklist status information."), nil) } if err := printTaskListStatus(taskListStatus); err != nil { return fmt.Errorf("failed to print task list status: %w", err) @@ -81,7 +82,7 @@ func AdminDescribeTaskList(c *cli.Context) error { pollers := response.Pollers if len(pollers) == 0 { - return PrintableError(colorMagenta("No poller for tasklist: "+taskList), nil) + return commoncli.Problem(colorMagenta("No poller for tasklist: "+taskList), nil) } return printTaskListPollers(pollers, taskListType) } @@ -99,7 +100,7 @@ func AdminListTaskList(c *cli.Context) error { response, err := frontendClient.GetTaskListsByDomain(ctx, request) if err != nil { - return PrintableError("Operation GetTaskListByDomain failed.", err) + return commoncli.Problem("Operation GetTaskListByDomain failed.", err) } fmt.Println("Task Lists for domain " + domain + ":") diff --git a/tools/cli/admin_timers.go b/tools/cli/admin_timers.go index b32a71d97bc..cde92e45ca0 100644 --- a/tools/cli/admin_timers.go +++ b/tools/cli/admin_timers.go @@ -34,6 +34,7 @@ import ( "github.com/uber/cadence/common" "github.com/uber/cadence/common/backoff" "github.com/uber/cadence/common/persistence" + "github.com/uber/cadence/tools/common/commoncli" ) // LoadCloser loads timer task information @@ -183,7 +184,7 @@ func AdminTimers(c *cli.Context) error { case "second": timerFormat = "2006-01-02T15:04:05" default: - return PrintableError("unknown bucket size: "+c.String(FlagBucketSize), nil) + return commoncli.Problem("unknown bucket size: "+c.String(FlagBucketSize), nil) } } printer = NewHistogramPrinter(c, timerFormat) @@ -193,7 +194,7 @@ func AdminTimers(c *cli.Context) error { reporter := NewReporter(c.String(FlagDomainID), timerTypes, loader, printer) if err := reporter.Report(); err != nil { - return PrintableError("Reporter failed", err) + return commoncli.Problem("Reporter failed", err) } return nil } @@ -206,7 +207,7 @@ func (jp *jsonPrinter) Print(timers []*persistence.TimerTaskInfo) error { data, err := json.Marshal(t) if err != nil { if !jp.ctx.Bool(FlagSkipErrorMode) { - return PrintableError("cannot marshal timer to json", err) + return commoncli.Problem("cannot marshal timer to json", err) } fmt.Println(err.Error()) } else { diff --git a/tools/cli/app.go b/tools/cli/app.go index 2a6f4e6e191..952e387bbd4 100644 --- a/tools/cli/app.go +++ b/tools/cli/app.go @@ -27,6 +27,7 @@ import ( "github.com/uber/cadence/common/client" "github.com/uber/cadence/common/metrics" + "github.com/uber/cadence/tools/common/commoncli" ) // SetFactory is used to set the ClientFactory global @@ -42,11 +43,7 @@ func NewCliApp() *cli.App { " Note: CLI feature version is for compatibility checking between server and CLI if enabled feature checking. Server is always backward compatible to older CLI versions, but not accepting newer than it can support.", client.SupportedCLIVersion, metrics.ReleaseVersion, metrics.Revision) - app := cli.NewApp() - app.Name = "cadence" - app.Usage = "A command-line tool for cadence users" - app.Version = version - app.ExitErrHandler = ExitErrHandler + app := commoncli.New("cadence", "A command-line tool for cadence users", version) app.Flags = []cli.Flag{ &cli.StringFlag{ Name: FlagAddress, diff --git a/tools/cli/cluster_commands.go b/tools/cli/cluster_commands.go index 40f524f8419..e1007f089f8 100644 --- a/tools/cli/cluster_commands.go +++ b/tools/cli/cluster_commands.go @@ -25,6 +25,8 @@ import ( "sort" "github.com/urfave/cli/v2" + + "github.com/uber/cadence/tools/common/commoncli" ) type ( @@ -53,7 +55,7 @@ func GetSearchAttributes(c *cli.Context) error { resp, err := wfClient.GetSearchAttributes(ctx) if err != nil { - return PrintableError("Failed to get search attributes.", err) + return commoncli.Problem("Failed to get search attributes.", err) } table := SearchAttributesTable{} diff --git a/tools/cli/domain_commands.go b/tools/cli/domain_commands.go index c1e09fb20c6..e9149a39bf7 100644 --- a/tools/cli/domain_commands.go +++ b/tools/cli/domain_commands.go @@ -37,6 +37,7 @@ import ( "github.com/uber/cadence/common/domain" "github.com/uber/cadence/common/dynamicconfig" "github.com/uber/cadence/common/types" + "github.com/uber/cadence/tools/common/commoncli" "github.com/uber/cadence/tools/common/flag" ) @@ -87,7 +88,7 @@ func (d *domainCLIImpl) RegisterDomain(c *cli.Context) error { if c.IsSet(FlagIsGlobalDomain) { isGlobalDomain, err = strconv.ParseBool(c.String(FlagIsGlobalDomain)) if err != nil { - return PrintableError(fmt.Sprintf("Option %s format is invalid.", FlagIsGlobalDomain), err) + return commoncli.Problem(fmt.Sprintf("Option %s format is invalid.", FlagIsGlobalDomain), err) } } @@ -98,7 +99,7 @@ func (d *domainCLIImpl) RegisterDomain(c *cli.Context) error { if len(requiredDomainDataKeys) > 0 { err = checkRequiredDomainDataKVs(domainData.Value()) if err != nil { - return PrintableError("Domain data missed required data.", err) + return commoncli.Problem("Domain data missed required data.", err) } } @@ -150,9 +151,9 @@ func (d *domainCLIImpl) RegisterDomain(c *cli.Context) error { err = d.registerDomain(ctx, request) if err != nil { if _, ok := err.(*types.DomainAlreadyExistsError); !ok { - return PrintableError("Register Domain operation failed.", err) + return commoncli.Problem("Register Domain operation failed.", err) } - return PrintableError(fmt.Sprintf("Domain %s already registered.", domainName), err) + return commoncli.Problem(fmt.Sprintf("Domain %s already registered.", domainName), err) } fmt.Printf("Domain %s successfully registered.\n", domainName) return nil @@ -187,9 +188,9 @@ func (d *domainCLIImpl) UpdateDomain(c *cli.Context) error { }) if err != nil { if _, ok := err.(*types.EntityNotExistsError); !ok { - return PrintableError("Operation UpdateDomain failed.", err) + return commoncli.Problem("Operation UpdateDomain failed.", err) } - return PrintableError(fmt.Sprintf("Domain %s does not exist.", domainName), err) + return commoncli.Problem(fmt.Sprintf("Domain %s does not exist.", domainName), err) } description := resp.DomainInfo.GetDescription() @@ -226,7 +227,7 @@ func (d *domainCLIImpl) UpdateDomain(c *cli.Context) error { var binBinaries *types.BadBinaries if c.IsSet(FlagAddBadBinary) { if !c.IsSet(FlagReason) { - return PrintableError("Must provide a reason.", nil) + return commoncli.Problem("Must provide a reason.", nil) } binChecksum := c.String(FlagAddBadBinary) reason := c.String(FlagReason) @@ -277,9 +278,9 @@ func (d *domainCLIImpl) UpdateDomain(c *cli.Context) error { _, err := d.updateDomain(ctx, updateRequest) if err != nil { if _, ok := err.(*types.EntityNotExistsError); ok { - return PrintableError(fmt.Sprintf("Domain %s does not exist.", domainName), err) + return commoncli.Problem(fmt.Sprintf("Domain %s does not exist.", domainName), err) } - return PrintableError("Operation UpdateDomain failed.", err) + return commoncli.Problem("Operation UpdateDomain failed.", err) } fmt.Printf("Domain %s successfully updated.\n", domainName) return nil @@ -297,17 +298,17 @@ func (d *domainCLIImpl) DeprecateDomain(c *cli.Context) error { // check if there is any workflow in this domain, if exists, do not deprecate wfs, _, err := listClosedWorkflow(getWorkflowClient(c), 1, 0, time.Now().UnixNano(), domainName, "", "", workflowStatusNotSet, c)(nil) if err != nil { - return PrintableError("Operation DeprecateDomain failed: fail to list closed workflows: ", err) + return commoncli.Problem("Operation DeprecateDomain failed: fail to list closed workflows: ", err) } if len(wfs) > 0 { - return PrintableError("Operation DeprecateDomain failed.", errors.New("workflow history not cleared in this domain")) + return commoncli.Problem("Operation DeprecateDomain failed.", errors.New("workflow history not cleared in this domain")) } wfs, _, err = listOpenWorkflow(getWorkflowClient(c), 1, 0, time.Now().UnixNano(), domainName, "", "", c)(nil) if err != nil { - return PrintableError("Operation DeprecateDomain failed: fail to list open workflows: ", err) + return commoncli.Problem("Operation DeprecateDomain failed: fail to list open workflows: ", err) } if len(wfs) > 0 { - return PrintableError("Operation DeprecateDomain failed.", errors.New("workflow still running in this domain")) + return commoncli.Problem("Operation DeprecateDomain failed.", errors.New("workflow still running in this domain")) } } err := d.deprecateDomain(ctx, &types.DeprecateDomainRequest{ @@ -316,9 +317,9 @@ func (d *domainCLIImpl) DeprecateDomain(c *cli.Context) error { }) if err != nil { if _, ok := err.(*types.EntityNotExistsError); !ok { - return PrintableError("Operation DeprecateDomain failed.", err) + return commoncli.Problem("Operation DeprecateDomain failed.", err) } - return PrintableError(fmt.Sprintf("Domain %s does not exist.", domainName), err) + return commoncli.Problem(fmt.Sprintf("Domain %s does not exist.", domainName), err) } fmt.Printf("Domain %s successfully deprecated.\n", domainName) return nil @@ -376,7 +377,7 @@ func (d *domainCLIImpl) getAllDomains(c *cli.Context) ([]*types.DescribeDomainRe } listResp, err := d.listDomains(ctx, listRequest) if err != nil { - return nil, PrintableError("Error when list domains info", err) + return nil, commoncli.Problem("Error when list domains info", err) } token = listResp.GetNextPageToken() res = append(res, listResp.GetDomains()...) @@ -434,7 +435,7 @@ func (d *domainCLIImpl) DescribeDomain(c *cli.Context) error { request.Name = &domainName } if domainID == "" && domainName == "" { - return PrintableError("At least domainID or domainName must be provided.", nil) + return commoncli.Problem("At least domainID or domainName must be provided.", nil) } ctx, cancel := newContext(c) @@ -442,15 +443,15 @@ func (d *domainCLIImpl) DescribeDomain(c *cli.Context) error { resp, err := d.describeDomain(ctx, &request) if err != nil { if _, ok := err.(*types.EntityNotExistsError); !ok { - return PrintableError("Operation DescribeDomain failed.", err) + return commoncli.Problem("Operation DescribeDomain failed.", err) } - return PrintableError(fmt.Sprintf("Domain %s does not exist.", domainName), err) + return commoncli.Problem(fmt.Sprintf("Domain %s does not exist.", domainName), err) } if printJSON { output, err := json.Marshal(resp) if err != nil { - return PrintableError("Failed to encode domain response into JSON.", err) + return commoncli.Problem("Failed to encode domain response into JSON.", err) } fmt.Println(string(output)) return nil @@ -600,7 +601,7 @@ func (d *domainCLIImpl) ListDomains(c *cli.Context) error { printJSON := c.Bool(FlagPrintJSON) if printAll && printDeprecated { - return PrintableError(fmt.Sprintf("Cannot specify %s and %s flags at the same time.", FlagAll, FlagDeprecated), nil) + return commoncli.Problem(fmt.Sprintf("Cannot specify %s and %s flags at the same time.", FlagAll, FlagDeprecated), nil) } domains, err := d.getAllDomains(c) @@ -636,7 +637,7 @@ func (d *domainCLIImpl) ListDomains(c *cli.Context) error { if printJSON { output, err := json.Marshal(filteredDomains) if err != nil { - return PrintableError("Failed to encode domain results into JSON.", err) + return commoncli.Problem("Failed to encode domain results into JSON.", err) } fmt.Println(string(output)) return nil @@ -735,7 +736,7 @@ func archivalStatus(c *cli.Context, statusFlagName string) (*types.ArchivalStatu case "enabled": return types.ArchivalStatusEnabled.Ptr(), nil default: - return nil, PrintableError(fmt.Sprintf("Option %s format is invalid.", statusFlagName), errors.New("invalid status, valid values are \"disabled\" and \"enabled\"")) + return nil, commoncli.Problem(fmt.Sprintf("Option %s format is invalid.", statusFlagName), errors.New("invalid status, valid values are \"disabled\" and \"enabled\"")) } } return nil, nil diff --git a/tools/cli/isolation-groups.go b/tools/cli/isolation-groups.go index 216a50f0ea5..433f1b81706 100644 --- a/tools/cli/isolation-groups.go +++ b/tools/cli/isolation-groups.go @@ -29,6 +29,7 @@ import ( "github.com/urfave/cli/v2" "github.com/uber/cadence/common/types" + "github.com/uber/cadence/tools/common/commoncli" ) func AdminGetGlobalIsolationGroups(c *cli.Context) error { @@ -40,7 +41,7 @@ func AdminGetGlobalIsolationGroups(c *cli.Context) error { req := &types.GetGlobalIsolationGroupsRequest{} igs, err := adminClient.GetGlobalIsolationGroups(ctx, req) if err != nil { - return PrintableError("failed to get isolation-groups:", err) + return commoncli.Problem("failed to get isolation-groups:", err) } format := c.String(FlagFormat) @@ -68,7 +69,7 @@ func AdminUpdateGlobalIsolationGroups(c *cli.Context) error { false, ) if err != nil { - return PrintableError("invalid args:", err) + return commoncli.Problem("invalid args:", err) } cfg, err := parseIsolationGroupCliInputCfg( @@ -77,14 +78,14 @@ func AdminUpdateGlobalIsolationGroups(c *cli.Context) error { c.Bool(FlagIsolationGroupsRemoveAllDrains), ) if err != nil { - return PrintableError("failed to parse input:", err) + return commoncli.Problem("failed to parse input:", err) } _, err = adminClient.UpdateGlobalIsolationGroups(ctx, &types.UpdateGlobalIsolationGroupsRequest{ IsolationGroups: *cfg, }) if err != nil { - return PrintableError("failed to update isolation-groups", fmt.Errorf("used %#v, got %v", cfg, err)) + return commoncli.Problem("failed to update isolation-groups", fmt.Errorf("used %#v, got %v", cfg, err)) } return nil } @@ -101,7 +102,7 @@ func AdminGetDomainIsolationGroups(c *cli.Context) error { } igs, err := adminClient.GetDomainIsolationGroups(ctx, req) if err != nil { - return PrintableError("failed to get isolation-groups:", err) + return commoncli.Problem("failed to get isolation-groups:", err) } format := c.String(FlagFormat) @@ -127,7 +128,7 @@ func AdminUpdateDomainIsolationGroups(c *cli.Context) error { true, ) if err != nil { - return PrintableError("invalid args:", err) + return commoncli.Problem("invalid args:", err) } ctx, cancel := newContext(c) @@ -139,7 +140,7 @@ func AdminUpdateDomainIsolationGroups(c *cli.Context) error { c.Bool(FlagIsolationGroupsRemoveAllDrains), ) if err != nil { - return PrintableError("failed to parse input:", err) + return commoncli.Problem("failed to parse input:", err) } req := &types.UpdateDomainIsolationGroupsRequest{ @@ -149,7 +150,7 @@ func AdminUpdateDomainIsolationGroups(c *cli.Context) error { _, err = adminClient.UpdateDomainIsolationGroups(ctx, req) if err != nil { - return PrintableError("failed to update isolation-groups", fmt.Errorf("used %#v, got %v", req, err)) + return commoncli.Problem("failed to update isolation-groups", fmt.Errorf("used %#v, got %v", req, err)) } return nil } diff --git a/tools/cli/task_list_commands.go b/tools/cli/task_list_commands.go index 87032ca28e0..5ddd1439d18 100644 --- a/tools/cli/task_list_commands.go +++ b/tools/cli/task_list_commands.go @@ -27,6 +27,7 @@ import ( "github.com/urfave/cli/v2" "github.com/uber/cadence/common/types" + "github.com/uber/cadence/tools/common/commoncli" ) type ( @@ -61,12 +62,12 @@ func DescribeTaskList(c *cli.Context) error { } response, err := wfClient.DescribeTaskList(ctx, request) if err != nil { - return PrintableError("Operation DescribeTaskList failed.", err) + return commoncli.Problem("Operation DescribeTaskList failed.", err) } pollers := response.Pollers if len(pollers) == 0 { - return PrintableError(colorMagenta("No poller for tasklist: "+taskList), nil) + return commoncli.Problem(colorMagenta("No poller for tasklist: "+taskList), nil) } return printTaskListPollers(pollers, taskListType) @@ -87,7 +88,7 @@ func ListTaskListPartitions(c *cli.Context) error { response, err := frontendClient.ListTaskListPartitions(ctx, request) if err != nil { - return PrintableError("Operation ListTaskListPartitions failed.", err) + return commoncli.Problem("Operation ListTaskListPartitions failed.", err) } if len(response.DecisionTaskListPartitions) > 0 { return printTaskListPartitions("Decision", response.DecisionTaskListPartitions) diff --git a/tools/cli/utils.go b/tools/cli/utils.go index 1fb3385a142..236b23ba2c1 100644 --- a/tools/cli/utils.go +++ b/tools/cli/utils.go @@ -51,19 +51,6 @@ import ( "github.com/uber/cadence/common/types" ) -type printableError struct { - msg string - err error -} - -func (e printableError) Error() string { - return e.msg -} - -func (e printableError) Unwrap() error { - return e.err -} - // JSONHistorySerializer is used to encode history event in JSON type JSONHistorySerializer struct{} @@ -537,21 +524,6 @@ func printError(msg string, err error) { } } -// PrintableError returns a printable error (always wrapping) -func PrintableError(msg string, err error) error { - return &printableError{msg: msg, err: err} -} - -// ExitErrHandler print easy to understand error msg first then error detail in a new line -func ExitErrHandler(cCtx *cli.Context, err error) { - var printable *printableError - if errors.As(err, &printable) { - printError(printable.msg, printable.err) - } else { // fall back to default error message - printError("CLI execution failed", err) - } -} - // ErrorAndExit print easy to understand error msg first then error detail in a new line func ErrorAndExit(msg string, err error) { printError(msg, err) diff --git a/tools/cli/workflow_batch_commands.go b/tools/cli/workflow_batch_commands.go index eef39f61c86..5f1897be3e0 100644 --- a/tools/cli/workflow_batch_commands.go +++ b/tools/cli/workflow_batch_commands.go @@ -34,6 +34,7 @@ import ( "github.com/uber/cadence/common" "github.com/uber/cadence/common/types" "github.com/uber/cadence/service/worker/batcher" + "github.com/uber/cadence/tools/common/commoncli" ) // TerminateBatchJob stops abatch job @@ -57,7 +58,7 @@ func TerminateBatchJob(c *cli.Context) error { }, ) if err != nil { - return PrintableError("Failed to terminate batch job", err) + return commoncli.Problem("Failed to terminate batch job", err) } output := map[string]interface{}{ "msg": "batch job is terminated", @@ -85,7 +86,7 @@ func DescribeBatchJob(c *cli.Context) error { }, ) if err != nil { - return PrintableError("Failed to describe batch job", err) + return commoncli.Problem("Failed to describe batch job", err) } output := map[string]interface{}{} @@ -102,7 +103,7 @@ func DescribeBatchJob(c *cli.Context) error { hbd := batcher.HeartBeatDetails{} err := json.Unmarshal(hbdBinary, &hbd) if err != nil { - return PrintableError("Failed to describe batch job", err) + return commoncli.Problem("Failed to describe batch job", err) } output["progress"] = hbd } @@ -129,7 +130,7 @@ func ListBatchJobs(c *cli.Context) error { }, ) if err != nil { - return PrintableError("Failed to list batch jobs", err) + return commoncli.Problem("Failed to list batch jobs", err) } output := make([]interface{}, 0, len(resp.Executions)) for _, wf := range resp.Executions { @@ -161,7 +162,7 @@ func StartBatchJob(c *cli.Context) error { batchType := getRequiredOption(c, FlagBatchType) if !validateBatchType(batchType) { - return PrintableError("batchType is not valid, supported:"+strings.Join(batcher.AllBatchTypes, ","), nil) + return commoncli.Problem("batchType is not valid, supported:"+strings.Join(batcher.AllBatchTypes, ","), nil) } operator := getCurrentUserFromEnv() var sigName, sigVal string @@ -192,7 +193,7 @@ func StartBatchJob(c *cli.Context) error { }, ) if err != nil { - return PrintableError("Failed to count impacting workflows for starting a batch job", err) + return commoncli.Problem("Failed to count impacting workflows for starting a batch job", err) } fmt.Printf("This batch job will be operating on %v workflows.\n", resp.GetCount()) if !c.Bool(FlagYes) { @@ -201,7 +202,7 @@ func StartBatchJob(c *cli.Context) error { fmt.Print("Please confirm[Yes/No]:") text, err := reader.ReadString('\n') if err != nil { - return PrintableError("Failed to get confirmation for starting a batch job", err) + return commoncli.Problem("Failed to get confirmation for starting a batch job", err) } if strings.EqualFold(strings.TrimSpace(text), "yes") { break @@ -236,20 +237,20 @@ func StartBatchJob(c *cli.Context) error { } input, err := json.Marshal(params) if err != nil { - return PrintableError("Failed to encode batch job parameters", err) + return commoncli.Problem("Failed to encode batch job parameters", err) } memo, err := getWorkflowMemo(map[string]interface{}{ "Reason": reason, }) if err != nil { - return PrintableError("Failed to encode batch job memo", err) + return commoncli.Problem("Failed to encode batch job memo", err) } searchAttributes, err := serializeSearchAttributes(map[string]interface{}{ "CustomDomain": domain, "Operator": operator, }) if err != nil { - return PrintableError("Failed to encode batch job search attributes", err) + return commoncli.Problem("Failed to encode batch job search attributes", err) } workflowID := uuid.NewRandom().String() request := &types.StartWorkflowExecutionRequest{ @@ -266,7 +267,7 @@ func StartBatchJob(c *cli.Context) error { } _, err = svcClient.StartWorkflowExecution(tcCtx, request) if err != nil { - return PrintableError("Failed to start batch job", err) + return commoncli.Problem("Failed to start batch job", err) } output := map[string]interface{}{ "msg": "batch job is started", diff --git a/tools/cli/workflow_commands.go b/tools/cli/workflow_commands.go index 31931a5044b..11e939bd7aa 100644 --- a/tools/cli/workflow_commands.go +++ b/tools/cli/workflow_commands.go @@ -46,6 +46,7 @@ import ( "github.com/uber/cadence/common/clock" "github.com/uber/cadence/common/types" "github.com/uber/cadence/service/history/execution" + "github.com/uber/cadence/tools/common/commoncli" ) // RestartWorkflow restarts a workflow execution @@ -70,7 +71,7 @@ func RestartWorkflow(c *cli.Context) error { ) if err != nil { - return PrintableError("Restart workflow failed.", err) + return commoncli.Problem("Restart workflow failed.", err) } fmt.Printf("Restarted Workflow Id: %s, run Id: %s\n", wid, resp.GetRunID()) return nil @@ -99,7 +100,7 @@ func DiagnoseWorkflow(c *cli.Context) error { ) if err != nil { - return PrintableError("Diagnose workflow failed.", err) + return commoncli.Problem("Diagnose workflow failed.", err) } fmt.Println("Workflow diagnosis started. Query the diagnostic workflow to get diagnostics report.") fmt.Println("============Diagnostic Workflow details============") @@ -117,7 +118,7 @@ func ShowHistory(c *cli.Context) error { // ShowHistoryWithWID shows the history of given workflow with workflow_id func ShowHistoryWithWID(c *cli.Context) error { if !c.Args().Present() { - return PrintableError("Argument workflow_id is required.", nil) + return commoncli.Problem("Argument workflow_id is required.", nil) } wid := c.Args().First() rid := "" @@ -146,7 +147,7 @@ func showHistoryHelper(c *cli.Context, wid, rid string) error { defer cancel() history, err := GetHistory(ctx, wfClient, domain, wid, rid) if err != nil { - return PrintableError(fmt.Sprintf("Failed to get history on workflow id: %s, run id: %s.", wid, rid), err) + return commoncli.Problem(fmt.Sprintf("Failed to get history on workflow id: %s, run id: %s.", wid, rid), err) } prevEvent := types.HistoryEvent{} @@ -164,7 +165,7 @@ func showHistoryHelper(c *cli.Context, wid, rid string) error { } else if c.IsSet(FlagEventID) { // only dump that event eventID := c.Int(FlagEventID) if eventID <= 0 || eventID > len(history.Events) { - return PrintableError("EventId out of range.", fmt.Errorf("number should be 1 - %d inclusive", len(history.Events))) + return commoncli.Problem("EventId out of range.", fmt.Errorf("number should be 1 - %d inclusive", len(history.Events))) } e := history.Events[eventID-1] fmt.Println(anyToString(e, true, 0)) @@ -203,10 +204,10 @@ func showHistoryHelper(c *cli.Context, wid, rid string) error { serializer := &JSONHistorySerializer{} data, err := serializer.Serialize(history) if err != nil { - return PrintableError("Failed to serialize history data.", err) + return commoncli.Problem("Failed to serialize history data.", err) } if err := ioutil.WriteFile(outputFileName, data, 0666); err != nil { - return PrintableError("Failed to export history data file.", err) + return commoncli.Problem("Failed to export history data file.", err) } } @@ -221,9 +222,9 @@ func showHistoryHelper(c *cli.Context, wid, rid string) error { }) if err != nil { if _, ok := err.(*types.EntityNotExistsError); ok { - return PrintableError("workflow not exist", err) + return commoncli.Problem("workflow not exist", err) } - return PrintableError("Describe workflow execution failed, cannot get information of pending activities", err) + return commoncli.Problem("Describe workflow execution failed, cannot get information of pending activities", err) } fmt.Println("History Source: Default Storage") @@ -265,7 +266,7 @@ func startWorkflowHelper(c *cli.Context, shouldPrintProgress bool) error { resp, err := serviceClient.StartWorkflowExecution(tcCtx, startRequest) if err != nil { - return PrintableError("Failed to create workflow.", err) + return commoncli.Problem("Failed to create workflow.", err) } fmt.Printf("Started Workflow Id: %s, run Id: %s\n", wid, resp.GetRunID()) return nil @@ -277,7 +278,7 @@ func startWorkflowHelper(c *cli.Context, shouldPrintProgress bool) error { resp, err := serviceClient.StartWorkflowExecution(tcCtx, startRequest) if err != nil { - return PrintableError("Failed to run workflow.", err) + return commoncli.Problem("Failed to run workflow.", err) } // print execution summary @@ -312,7 +313,7 @@ func constructStartWorkflowRequest(c *cli.Context) (*types.StartWorkflowExecutio workflowType := getRequiredOption(c, FlagWorkflowType) et := c.Int(FlagExecutionTimeout) if et == 0 { - return nil, PrintableError(fmt.Sprintf("Option %s format is invalid.", FlagExecutionTimeout), nil) + return nil, commoncli.Problem(fmt.Sprintf("Option %s format is invalid.", FlagExecutionTimeout), nil) } dt := c.Int(FlagDecisionTimeout) wid := c.String(FlagWorkflowID) @@ -373,7 +374,7 @@ func constructStartWorkflowRequest(c *cli.Context) (*types.StartWorkflowExecutio if c.IsSet(FirstRunAtTime) { t, err := time.Parse(time.RFC3339, c.String(FirstRunAtTime)) if err != nil { - return nil, PrintableError("First_run_at time format invalid, please use RFC3339", err) + return nil, commoncli.Problem("First_run_at time format invalid, please use RFC3339", err) } startRequest.FirstRunAtTimeStamp = common.Int64Ptr(t.UnixNano()) } @@ -437,7 +438,7 @@ func processHeader(c *cli.Context) (map[string][]byte, error) { headerValues := processMultipleJSONValues(processJSONInputHelper(c, jsonTypeHeader)) if len(headerKeys) != len(headerValues) { - return nil, PrintableError("Number of header keys and values are not equal.", nil) + return nil, commoncli.Problem("Number of header keys and values are not equal.", nil) } return mapFromKeysValues(headerKeys, headerValues), nil @@ -541,7 +542,7 @@ func TerminateWorkflow(c *cli.Context) error { ) if err != nil { - return PrintableError("Terminate workflow failed.", err) + return commoncli.Problem("Terminate workflow failed.", err) } fmt.Println("Terminate workflow succeeded.") @@ -574,7 +575,7 @@ func CancelWorkflow(c *cli.Context) error { }, ) if err != nil { - return PrintableError("Cancel workflow failed.", err) + return commoncli.Problem("Cancel workflow failed.", err) } fmt.Println("Cancel workflow succeeded.") return nil @@ -608,7 +609,7 @@ func SignalWorkflow(c *cli.Context) error { ) if err != nil { - return PrintableError("Signal workflow failed.", err) + return commoncli.Problem("Signal workflow failed.", err) } fmt.Println("Signal workflow succeeded.") return nil @@ -628,7 +629,7 @@ func SignalWithStartWorkflowExecution(c *cli.Context) error { resp, err := serviceClient.SignalWithStartWorkflowExecution(tcCtx, signalWithStartRequest) if err != nil { - return PrintableError("SignalWithStart workflow failed.", err) + return commoncli.Problem("SignalWithStart workflow failed.", err) } fmt.Printf("SignalWithStart workflow succeeded. Workflow Id: %s, run Id: %s\n", signalWithStartRequest.GetWorkflowID(), resp.GetRunID()) return nil @@ -718,7 +719,7 @@ func queryWorkflowHelper(c *cli.Context, queryType string) error { case "not_completed_cleanly": rejectCondition = types.QueryRejectConditionNotCompletedCleanly default: - return PrintableError(fmt.Sprintf("invalid reject condition %v, valid values are \"not_open\" and \"not_completed_cleanly\"", c.String(FlagQueryRejectCondition)), nil) + return commoncli.Problem(fmt.Sprintf("invalid reject condition %v, valid values are \"not_open\" and \"not_completed_cleanly\"", c.String(FlagQueryRejectCondition)), nil) } queryRequest.QueryRejectCondition = &rejectCondition } @@ -731,13 +732,13 @@ func queryWorkflowHelper(c *cli.Context, queryType string) error { case "strong": consistencyLevel = types.QueryConsistencyLevelStrong default: - return PrintableError(fmt.Sprintf("invalid query consistency level %v, valid values are \"eventual\" and \"strong\"", c.String(FlagQueryConsistencyLevel)), nil) + return commoncli.Problem(fmt.Sprintf("invalid query consistency level %v, valid values are \"eventual\" and \"strong\"", c.String(FlagQueryConsistencyLevel)), nil) } queryRequest.QueryConsistencyLevel = &consistencyLevel } queryResponse, err := serviceClient.QueryWorkflow(tcCtx, queryRequest) if err != nil { - return PrintableError("Query workflow failed.", err) + return commoncli.Problem("Query workflow failed.", err) } if queryResponse.QueryRejected != nil { @@ -801,7 +802,7 @@ func CountWorkflow(c *cli.Context) error { defer cancel() response, err := wfClient.CountWorkflowExecutions(ctx, request) if err != nil { - return PrintableError("Failed to count workflow.", err) + return commoncli.Problem("Failed to count workflow.", err) } fmt.Println(response.GetCount()) @@ -828,7 +829,7 @@ func DescribeWorkflow(c *cli.Context) error { // DescribeWorkflowWithID show information about the specified workflow execution func DescribeWorkflowWithID(c *cli.Context) error { if !c.Args().Present() { - return PrintableError("Argument workflow_id is required.", nil) + return commoncli.Problem("Argument workflow_id is required.", nil) } wid := c.Args().First() rid := "" @@ -857,7 +858,7 @@ func describeWorkflowHelper(c *cli.Context, wid, rid string) error { }, }) if err != nil { - return PrintableError("Describe workflow execution failed", err) + return commoncli.Problem("Describe workflow execution failed", err) } if printResetPointsOnly { @@ -1272,7 +1273,7 @@ func listWorkflowExecutions(client frontend.Client, pageSize int, domain, query defer cancel() response, err := client.ListWorkflowExecutions(ctx, request) if err != nil { - return nil, nil, PrintableError("Failed to list workflow.", err) + return nil, nil, commoncli.Problem("Failed to list workflow.", err) } return response.Executions, response.NextPageToken, nil } @@ -1300,7 +1301,7 @@ func listOpenWorkflow(client frontend.Client, pageSize int, earliestTime, latest defer cancel() response, err := client.ListOpenWorkflowExecutions(ctx, request) if err != nil { - return nil, nil, PrintableError("Failed to list open workflow.", err) + return nil, nil, commoncli.Problem("Failed to list open workflow.", err) } return response.Executions, response.NextPageToken, nil } @@ -1331,7 +1332,7 @@ func listClosedWorkflow(client frontend.Client, pageSize int, earliestTime, late defer cancel() response, err := client.ListClosedWorkflowExecutions(ctx, request) if err != nil { - return nil, nil, PrintableError("Failed to list closed workflow.", err) + return nil, nil, commoncli.Problem("Failed to list closed workflow.", err) } return response.Executions, response.NextPageToken, nil } @@ -1355,7 +1356,7 @@ func listWorkflows(c *cli.Context) (getWorkflowPageFn, error) { var err error if c.IsSet(FlagWorkflowStatus) { if queryOpen { - return nil, PrintableError(optionErr, errors.New("you can only filter on status for closed workflow, not open workflow")) + return nil, commoncli.Problem(optionErr, errors.New("you can only filter on status for closed workflow, not open workflow")) } workflowStatus, err = getWorkflowStatus(c.String(FlagWorkflowStatus)) if err != nil { @@ -1366,7 +1367,7 @@ func listWorkflows(c *cli.Context) (getWorkflowPageFn, error) { } if len(workflowID) > 0 && len(workflowType) > 0 { - return nil, PrintableError(optionErr, errors.New("you can filter on workflow_id or workflow_type, but not on both")) + return nil, commoncli.Problem(optionErr, errors.New("you can filter on workflow_id or workflow_type, but not on both")) } ctx, cancel := newContextForLongPoll(c) @@ -1381,7 +1382,7 @@ func listWorkflows(c *cli.Context) (getWorkflowPageFn, error) { if err == nil { _, err = fmt.Fprintf(os.Stderr, "Fetching %v workflows...\n", resp.GetCount()) if err != nil { - return nil, PrintableError("Failed to print to stderr", err) + return nil, commoncli.Problem("Failed to print to stderr", err) } } @@ -1424,7 +1425,7 @@ func listArchivedWorkflows(c *cli.Context) getWorkflowPageFn { result, err := wfClient.ListArchivedWorkflowExecutions(ctx, request) if err != nil { cancel() - return nil, nil, PrintableError("Failed to list archived workflow.", err) + return nil, nil, commoncli.Problem("Failed to list archived workflow.", err) } return result.Executions, result.NextPageToken, nil } @@ -1456,7 +1457,7 @@ func scanWorkflowExecutions(client frontend.Client, pageSize int, nextPageToken defer cancel() response, err := client.ScanWorkflowExecutions(ctx, request) if err != nil { - return nil, nil, PrintableError("Failed to list workflow.", err) + return nil, nil, commoncli.Problem("Failed to list workflow.", err) } return response.Executions, response.NextPageToken, nil } @@ -1465,7 +1466,7 @@ func getWorkflowStatus(statusStr string) (types.WorkflowExecutionCloseStatus, er if status, ok := workflowClosedStatusMap[strings.ToLower(statusStr)]; ok { return status, nil } - return -1, PrintableError(optionErr, errors.New("option status is not one of allowed values "+ + return -1, commoncli.Problem(optionErr, errors.New("option status is not one of allowed values "+ "[completed, failed, canceled, terminated, continued_as_new, timed_out]")) } @@ -1514,18 +1515,18 @@ func ResetWorkflow(c *cli.Context) error { wid := getRequiredOption(c, FlagWorkflowID) reason := getRequiredOption(c, FlagReason) if len(reason) == 0 { - return PrintableError("wrong reason", fmt.Errorf("reason cannot be empty")) + return commoncli.Problem("wrong reason", fmt.Errorf("reason cannot be empty")) } eventID := c.Int64(FlagEventID) resetType := c.String(FlagResetType) decisionOffset := c.Int(FlagDecisionOffset) if decisionOffset > 0 { - return PrintableError("Only decision offset <=0 is supported", nil) + return commoncli.Problem("Only decision offset <=0 is supported", nil) } extraForResetType, ok := resetTypesMap[resetType] if !ok && eventID <= 0 { - return PrintableError("Must specify valid eventID or valid resetType", nil) + return commoncli.Problem("Must specify valid eventID or valid resetType", nil) } if ok && len(extraForResetType) > 0 { getRequiredOption(c, extraForResetType) @@ -1540,7 +1541,7 @@ func ResetWorkflow(c *cli.Context) error { if rid == "" { rid, err = getCurrentRunID(ctx, domain, wid, frontendClient) if err != nil { - return PrintableError("Cannot get latest RunID as default", err) + return commoncli.Problem("Cannot get latest RunID as default", err) } } @@ -1549,7 +1550,7 @@ func ResetWorkflow(c *cli.Context) error { if resetType != "" { resetBaseRunID, decisionFinishID, err = getResetEventIDByType(ctx, c, resetType, decisionOffset, domain, wid, rid, frontendClient) if err != nil { - return PrintableError("getResetEventIDByType failed", err) + return commoncli.Problem("getResetEventIDByType failed", err) } } resp, err := frontendClient.ResetWorkflowExecution(ctx, &types.ResetWorkflowExecutionRequest{ @@ -1564,7 +1565,7 @@ func ResetWorkflow(c *cli.Context) error { SkipSignalReapply: c.Bool(FlagSkipSignalReapply), }) if err != nil { - return PrintableError("reset failed", err) + return commoncli.Problem("reset failed", err) } prettyPrintJSONObject(resp) return nil @@ -1618,7 +1619,7 @@ func ResetInBatch(c *cli.Context) error { resetType := getRequiredOption(c, FlagResetType) decisionOffset := c.Int(FlagDecisionOffset) if decisionOffset > 0 { - return PrintableError("Only decision offset <=0 is supported", nil) + return commoncli.Problem("Only decision offset <=0 is supported", nil) } inFileName := c.String(FlagInputFile) @@ -1633,13 +1634,13 @@ func ResetInBatch(c *cli.Context) error { extraForResetType, ok := resetTypesMap[resetType] if !ok { - return PrintableError("Not supported reset type", nil) + return commoncli.Problem("Not supported reset type", nil) } else if len(extraForResetType) > 0 { getRequiredOption(c, extraForResetType) } if excludeFileName != "" && excludeQuery != "" { - return PrintableError("Only one of the excluding option is allowed", nil) + return commoncli.Problem("Only one of the excluding option is allowed", nil) } batchResetParams := batchResetParamsType{ @@ -1655,7 +1656,7 @@ func ResetInBatch(c *cli.Context) error { } if inFileName == "" && query == "" { - return PrintableError("Must provide input file or list query to get target workflows to reset", nil) + return commoncli.Problem("Must provide input file or list query to get target workflows to reset", nil) } wg := &sync.WaitGroup{} @@ -1685,7 +1686,7 @@ func ResetInBatch(c *cli.Context) error { if len(inFileName) > 0 { inFile, err := os.Open(inFileName) if err != nil { - return PrintableError("Open failed", err) + return commoncli.Problem("Open failed", err) } defer inFile.Close() scanner := bufio.NewScanner(inFile) @@ -1699,7 +1700,7 @@ func ResetInBatch(c *cli.Context) error { } cols := strings.Split(line, separator) if len(cols) < 1 { - return PrintableError("Split failed", fmt.Errorf("line %v has less than 1 cols separated by comma, only %v ", idx, len(cols))) + return commoncli.Problem("Split failed", fmt.Errorf("line %v has less than 1 cols separated by comma, only %v ", idx, len(cols))) } fmt.Printf("Start processing line %v ...\n", idx) wid := strings.TrimSpace(cols[0]) @@ -2170,7 +2171,7 @@ func CompleteActivity(c *cli.Context) error { rid := getRequiredOption(c, FlagRunID) activityID := getRequiredOption(c, FlagActivityID) if len(activityID) == 0 { - return PrintableError("Invalid activityID", fmt.Errorf("activityID cannot be empty")) + return commoncli.Problem("Invalid activityID", fmt.Errorf("activityID cannot be empty")) } result := getRequiredOption(c, FlagResult) identity := getRequiredOption(c, FlagIdentity) @@ -2187,7 +2188,7 @@ func CompleteActivity(c *cli.Context) error { Identity: identity, }) if err != nil { - return PrintableError("Completing activity failed", err) + return commoncli.Problem("Completing activity failed", err) } fmt.Println("Complete activity successfully.") return nil @@ -2200,7 +2201,7 @@ func FailActivity(c *cli.Context) error { rid := getRequiredOption(c, FlagRunID) activityID := getRequiredOption(c, FlagActivityID) if len(activityID) == 0 { - return PrintableError("Invalid activityID", fmt.Errorf("activityID cannot be empty")) + return commoncli.Problem("Invalid activityID", fmt.Errorf("activityID cannot be empty")) } reason := getRequiredOption(c, FlagReason) detail := getRequiredOption(c, FlagDetail) @@ -2219,7 +2220,7 @@ func FailActivity(c *cli.Context) error { Identity: identity, }) if err != nil { - return PrintableError("Failing activity failed", err) + return commoncli.Problem("Failing activity failed", err) } fmt.Println("Fail activity successfully.") return nil @@ -2229,7 +2230,7 @@ func FailActivity(c *cli.Context) error { func ObserveHistoryWithID(c *cli.Context) error { domain := getRequiredOption(c, FlagDomain) if !c.Args().Present() { - return PrintableError("Argument workflow_id is required.", nil) + return commoncli.Problem("Argument workflow_id is required.", nil) } wid := c.Args().First() rid := "" diff --git a/tools/common/commoncli/cli.go b/tools/common/commoncli/cli.go new file mode 100644 index 00000000000..75182251acf --- /dev/null +++ b/tools/common/commoncli/cli.go @@ -0,0 +1,218 @@ +// The MIT License (MIT) + +// Copyright (c) 2017-2020 Uber Technologies Inc. + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package commoncli + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/fatih/color" + "github.com/urfave/cli/v2" +) + +var ( + colorRed = color.New(color.FgRed).SprintFunc() + colorMagenta = color.New(color.FgMagenta).SprintFunc() +) + +func New(name, usage, version string) *cli.App { + if version == "" { + version = "0.0.1" // common "unversioned" value across many, tho likely worth dropping + } + + app := cli.NewApp() + app.Name = name + app.Usage = usage + app.Version = version + app.ExitErrHandler = exitHandler + return app +} + +func exitHandler(ctx *cli.Context, err error) { + if err == nil { + return // no forced exit(0), just let the process end normally + } + + // try to let the default urfave error handler take care of it. + // this exits if it finds a special error type. + // + // in particular, this takes care of "bad command" failures, which are + // returned as a urfave ExitCoder error, which we do not use anywhere. + cli.HandleExitCoder(err) + + // if we're still here, it didn't exit the process, + // so it's apparently worth handling. + _ = printErr(err, os.Stderr) + + // all errors are "fatal", unlike default behavior which only fails if you + // return an ExitCoder error. + os.Exit(1) +} + +// prints this (possibly printable) error to the given io.Writer. +// write-errors will be returned, if any are encountered. +func printErr(err error, to io.Writer) (writeErr error) { + // the way Go does error wrapping is really a massive pain, as the stdlib encourages + // people to wrap *and duplicate* basically every error message, because they don't + // provide any API for getting "just this error" content. + // + // we can live with that. + // build strings recursively, stripping out matching suffixes, and hope for the best. + // it's quite wasteful, but it's only done once per process so it's fine. + + // error-coalescing write-helper. + // if an error is encountered, the first will be saved, and later calls will no-op. + write := func(format string, a ...any) { + if writeErr != nil { + return + } + _, tmpErr := fmt.Fprintf(to, format, a...) + if tmpErr != nil { + writeErr = tmpErr + } + } + + var topPrintable *printableErr + _ = errors.As(err, &topPrintable) + + var unwrapped []error + unwrapped = append(unwrapped, err) + current := err + for i := 0; i < 1000; i++ { // 1k would be an insane depth, likely a loop + next := errors.Unwrap(current) + if next != nil { + current = next + unwrapped = append(unwrapped, next) + } else { + break + } + } + + type parsed struct { + err error + msg string + } + allParsed := make([]parsed, len(unwrapped)) + for i := range unwrapped { + p := parsed{err: unwrapped[i]} + if perr, ok := unwrapped[i].(*printableErr); ok { + p.msg = perr.display + if i > 0 && perr == topPrintable { // must be same instance somewhere deeper in the stack of errors + p.msg = "Error (above): " + p.msg + } else { + p.msg = "Error: " + p.msg + } + } else { + p.msg = unwrapped[i].Error() + } + if i > 0 { + // trim current from previous (needs to be err.Error() instead of p.msg or the special "Error: " prefixing above will break trimming), + // and attempt to trim off any trailing ": " because it's so common. + // this is not an exact science, but neither are error messages so it's just a hopeful hack. + allParsed[i-1].msg = strings.TrimSuffix( + strings.TrimSpace( + strings.TrimSuffix( + strings.TrimSpace(allParsed[i-1].msg), + strings.TrimSpace(p.err.Error()), + ), + ), + ":", + ) + } + allParsed[i] = p + } + + if topPrintable != nil { + write("%s %s\n", colorRed("Error:"), topPrintable.display) + if allParsed[0].err == topPrintable { + allParsed = allParsed[1:] // already printed, skip + } + } else { // len(allParsed) > 0 + write("%s %s\n", colorRed("Error:"), allParsed[0].msg) + allParsed = allParsed[1:] // already printed, skip + } + + if len(allParsed) == 0 { + return // no details to print + } + + indent := " " + write("%s\n", colorMagenta("Error details:")) // only the top level gets color + // and now write the rest + needDetails := false + for _, this := range allParsed { + lines := strings.Split(this.msg, "\n") + if needDetails { + write("%s%s\n", indent, "Error details:") + indent += " " // next errors indent further + needDetails = false + } + for _, line := range lines { + write("%s%s\n", indent, line) + } + if _, ok := this.err.(*printableErr); ok { // already unwrapped + needDetails = true + } + } + return +} + +// Problem returns a typed error that will report this message "nicely" to the +// user if it exits the CLI app. The message will be used as the top-level +// "Error: ..." string regardless of where in the error stack it is, and other +// wrapped errors will be printed line by line beneath it. +// +// Nested Problem messages will nest structurally, like: +// +// Error: msg +// Details: +// some error +// some other error +// Error: message +// ErrorDetails: +// more nested errors +func Problem(msg string, err error) error { + return &printableErr{msg, err} +} + +type printableErr struct { + display string + cause error +} + +func (p *printableErr) Error() string { + buf := strings.Builder{} + buf.WriteString(p.display) + if p.cause != nil { + buf.WriteString(": ") + buf.WriteString(p.cause.Error()) + } + return buf.String() +} + +func (p *printableErr) Unwrap() error { + return p.cause +} diff --git a/tools/common/commoncli/cli_test.go b/tools/common/commoncli/cli_test.go new file mode 100644 index 00000000000..cc406cb78a9 --- /dev/null +++ b/tools/common/commoncli/cli_test.go @@ -0,0 +1,155 @@ +// The MIT License (MIT) + +// Copyright (c) 2017-2020 Uber Technologies Inc. + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +package commoncli + +import ( + "errors" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + // largely for coverage, but currently this always returns a non-nil value. + assert.NotNil(t, New("", "", "")) + assert.NotNil(t, New("a", "b", "c")) +} + +func TestPrintErr(t *testing.T) { + run := func(t *testing.T, err error) string { + t.Helper() + buf := strings.Builder{} + // improves t.Log display by placing it all on a common start column, and easier to read the Equal below + buf.WriteString("\n") + failed := printErr(err, &buf) + assert.NoError(t, failed, "error during printing") + t.Log(buf.String()) + return buf.String() + } + t.Run("printable only", func(t *testing.T) { + str := run(t, Problem("a problem", nil)) + assert.Equal(t, ` +Error: a problem +`, str) + }) + t.Run("printable top", func(t *testing.T) { + str := run(t, + Problem("a problem", + fmt.Errorf("wrapper: %w", + errors.New("cause")))) + // causes are nested flat, chains are cleaned up + assert.Equal(t, ` +Error: a problem +Error details: + wrapper + cause +`, str) + }) + t.Run("printable top fancy middle", func(t *testing.T) { + str := run(t, + Problem("a problem", + fmt.Errorf("wrapper caused by -> %w", + errors.New("cause")))) + // cleans up other kinds of suffixes + assert.Equal(t, ` +Error: a problem +Error details: + wrapper caused by -> + cause +`, str) + }) + t.Run("printable top unfixable middle", func(t *testing.T) { + str := run(t, + Problem("a problem", + fmt.Errorf("wrapper (caused by: %w)", + errors.New("cause")))) + // does not clean up something that isn't a suffix + assert.Equal(t, ` +Error: a problem +Error details: + wrapper (caused by: cause) + cause +`, str) + }) + t.Run("printable bottom", func(t *testing.T) { + str := run(t, + fmt.Errorf("msg: %w", + Problem("a problem", nil))) + // note: the Problem is the displayed err, even though there is an error wrapper above it. + // all contents are visible for troubleshooting purposes, it just tries to make the "problem" clearer. + assert.Equal(t, ` +Error: a problem +Error details: + msg + Error (above): a problem +`, str) + }) + t.Run("printable mid", func(t *testing.T) { + str := run(t, + fmt.Errorf("msg: %w", + Problem("one layer deep", + errors.New("cause")))) + assert.Equal(t, ` +Error: one layer deep +Error details: + msg + Error (above): one layer deep + Error details: + cause +`, str) + }) + t.Run("printable nested", func(t *testing.T) { + str := run(t, + Problem("top", + fmt.Errorf("wrapper: %w", + Problem("bottom", + errors.New("cause"))))) + assert.Equal(t, ` +Error: top +Error details: + wrapper + Error: bottom + Error details: + cause +`, str) + }) + t.Run("multi-line error", func(t *testing.T) { + str := run(t, fmt.Errorf(`what if +it has +multiple lines: %w`, errors.New(`even +when +nested`))) + // maybe not ideal but it works well enough I think + assert.Equal(t, ` +Error: what if +it has +multiple lines +Error details: + even + when + nested +`, str) + }) +} diff --git a/tools/sql/main.go b/tools/sql/main.go index e577dfbfe03..d489f0df81c 100644 --- a/tools/sql/main.go +++ b/tools/sql/main.go @@ -25,6 +25,7 @@ import ( "github.com/urfave/cli/v2" + "github.com/uber/cadence/tools/common/commoncli" cliflag "github.com/uber/cadence/tools/common/flag" "github.com/uber/cadence/tools/common/schema" ) @@ -52,11 +53,7 @@ func cliHandler(c *cli.Context, handler func(c *cli.Context) error) error { // BuildCLIOptions builds the options for cli func BuildCLIOptions() *cli.App { - - app := cli.NewApp() - app.Name = "cadence-sql-tool" - app.Usage = "Command line tool for cadence sql operations" - app.Version = "0.0.1" + app := commoncli.New("cadence-sql-tool", "Command line tool for cadence sql operations", "") app.Flags = []cli.Flag{ &cli.StringFlag{