Skip to content
This repository has been archived by the owner on Nov 24, 2023. It is now read-only.

dmctl: fix dmctl command to improve its usability (#1750) #1771

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
220 changes: 2 additions & 218 deletions cmd/dm-ctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,72 +14,18 @@
package main

import (
"context"
"flag"
"fmt"
"io"
"os"
"os/signal"
"strings"
"syscall"

"github.com/chzyer/readline"
"github.com/pingcap/errors"

"github.com/pingcap/dm/dm/ctl"
"github.com/pingcap/dm/dm/ctl/common"
"github.com/pingcap/dm/pkg/log"
"github.com/pingcap/dm/pkg/terror"
"github.com/pingcap/dm/pkg/utils"
)

// output:
// Usage: dmctl [global options] command [command options] [arguments...]
//
// Available Commands:
// ...
// query-status query-status [-s source ...] [task-name]
// ...
//
// Special Commands:
// --encrypt encrypt plaintext to ciphertext
// ...
//
// Global Options:
// --V prints version and exit
// ...
func helpUsage(cfg *common.Config) {
fmt.Println("Usage: dmctl [global options] command [command options] [arguments...]")
fmt.Println()
ctl.PrintUsage()
fmt.Println()
fmt.Println("Special Commands:")
f := cfg.FlagSet.Lookup(common.EncryptCmdName)
fmt.Printf(" --%s %s\n", f.Name, f.Usage)
f = cfg.FlagSet.Lookup(common.DecryptCmdName)
fmt.Printf(" --%s %s\n", f.Name, f.Usage)
fmt.Println()
fmt.Println("Global Options:")
cfg.FlagSet.VisitAll(func(flag2 *flag.Flag) {
if flag2.Name == common.EncryptCmdName || flag2.Name == common.DecryptCmdName {
return
}
fmt.Printf(" --%s %s\n", flag2.Name, flag2.Usage)
})
}

func main() {
cfg := common.NewConfig()
args := os.Args[1:]

// no arguments: print help message about dmctl
if len(args) == 0 {
helpUsage(cfg)
os.Exit(0)
}

args = aliasArgs(args)

// now, we use checker in dmctl while it using some pkg which log some thing when running
// to make dmctl output more clear, simply redirect log to file rather output to stdout
err := log.InitLogger(&log.Config{
Expand All @@ -91,100 +37,6 @@ func main() {
os.Exit(2)
}

// try to split one task operation from dmctl command
// because we allow user put task operation at last with two restrictions
// 1. one command one task operation
// 2. put task operation at last
cmdArgs := extractSubCommand(args)
lenArgs := len(args)
lenCmdArgs := len(cmdArgs)
if lenCmdArgs > 0 {
lenArgs -= lenCmdArgs
}

finished, err := cfg.Parse(args[:lenArgs])
if finished {
os.Exit(0)
}

switch errors.Cause(err) {
case nil:
case flag.ErrHelp:
if lenCmdArgs > 0 {
// print help message about special subCommand
ctl.PrintHelp(cmdArgs)
} else {
// print help message about dmctl
helpUsage(cfg)
}
os.Exit(0)
default:
// NOTE: when `--help` in cmdArgs we need to print out the help msg.
for _, cmd := range cmdArgs {
if cmd == "--help" {
ctl.PrintHelp(cmdArgs)
os.Exit(0)
}
}
common.PrintLinesf("parse cmd flags err: %s", terror.Message(err))
os.Exit(2)
}

err = cfg.Validate()
if err != nil {
common.PrintLinesf("flags are not validate: %s", terror.Message(err))
os.Exit(2)
}

err = ctl.Init(cfg)
if err != nil {
common.PrintLinesf("%v", terror.Message(err))
os.Exit(2)
}
if lenCmdArgs > 0 {
if err = commandMode(cmdArgs); err != nil {
os.Exit(2)
}
} else {
interactionMode()
}
}

func extractSubCommand(args []string) []string {
collectedArgs := make([]string, 0, len(args))
subCommand := make([]string, 0, len(args))
for i := 0; i < len(args); i++ {
// check whether has multiple commands
if ctl.HasCommand(strings.ToLower(args[i])) {
subCommand = append(subCommand, args[i])
}
}
if len(subCommand) == 0 {
return collectedArgs
}
if len(subCommand) > 1 {
fmt.Printf("command mode only support one command at a time, find %d:", len(subCommand))
fmt.Println(subCommand)
os.Exit(1)
}

for i := 0; i < len(args); i++ {
if ctl.HasCommand(strings.ToLower(args[i])) {
collectedArgs = append(collectedArgs, args[i:]...)
break
}
}
return collectedArgs
}

func commandMode(args []string) error {
return ctl.Start(args)
}

func interactionMode() {
utils.PrintInfo2("dmctl")
fmt.Println() // print a separater

sc := make(chan os.Signal, 1)
signal.Notify(sc,
syscall.SIGHUP,
Expand All @@ -194,7 +46,7 @@ func interactionMode() {

go func() {
sig := <-sc
fmt.Printf("got signal [%v] to exit", sig)
fmt.Printf("\nGot signal [%v] to exit.\n", sig)
switch sig {
case syscall.SIGTERM:
os.Exit(0)
Expand All @@ -203,73 +55,5 @@ func interactionMode() {
}
}()

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go common.SyncMasterEndpoints(ctx)

loop()

fmt.Println("dmctl exit")
}

func loop() {
l, err := readline.NewEx(&readline.Config{
Prompt: "\033[31m»\033[0m ",
HistoryFile: "/tmp/dmctlreadline.tmp",
InterruptPrompt: "^C",
EOFPrompt: "^D",
})
if err != nil {
panic(err)
}

for {
line, err := l.Readline()
if err != nil {
if err == readline.ErrInterrupt {
break
} else if err == io.EOF {
break
}
continue
}

line = strings.TrimSpace(line)
if line == "exit" {
l.Close()
os.Exit(0)
} else if line == "" {
continue
}

args := strings.Fields(line)
args = aliasArgs(args)
err = ctl.Start(args)
if err != nil {
fmt.Println("fail to run:", args)
}

syncErr := log.L().Sync()
if syncErr != nil {
fmt.Fprintln(os.Stderr, "sync log failed", syncErr)
}
}
l.Close()
}

func aliasArgs(args []string) []string {
args = aliasGetTaskCfgCmd(args)
return args
}

func aliasGetTaskCfgCmd(args []string) []string {
for i, arg := range args {
if arg == "get-task-config" {
args = append(args[:i+1], args[i:]...)
args[i] = "get-config"
args[i+1] = "task"
return args
}
}
return args
ctl.MainStart(common.AdjustArgumentsForPflags(os.Args[1:]))
}
Loading