github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/ctl/ctl.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package ctl
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"io"
    20  	"os"
    21  	"strings"
    22  
    23  	"github.com/chzyer/readline"
    24  	"github.com/pingcap/errors"
    25  	"github.com/pingcap/tiflow/dm/ctl/common"
    26  	"github.com/pingcap/tiflow/dm/ctl/master"
    27  	"github.com/pingcap/tiflow/dm/pb"
    28  	"github.com/pingcap/tiflow/dm/pkg/log"
    29  	"github.com/pingcap/tiflow/pkg/version"
    30  	"github.com/spf13/cobra"
    31  	"go.uber.org/zap/zapcore"
    32  )
    33  
    34  var commandMasterFlags = CommandMasterFlags{}
    35  
    36  // CommandMasterFlags are flags that used in all commands for dm-master.
    37  type CommandMasterFlags struct {
    38  	workers []string // specify workers to control on these dm-workers
    39  }
    40  
    41  // Reset clears cache of CommandMasterFlags.
    42  func (c CommandMasterFlags) Reset() {
    43  	//nolint:staticcheck
    44  	c.workers = c.workers[:0]
    45  }
    46  
    47  // NewRootCmd generates a new rootCmd.
    48  func NewRootCmd() *cobra.Command {
    49  	cmd := &cobra.Command{
    50  		Use:           "dmctl",
    51  		Short:         "DM control",
    52  		SilenceUsage:  true,
    53  		SilenceErrors: true,
    54  		CompletionOptions: cobra.CompletionOptions{
    55  			DisableDefaultCmd: true,
    56  		},
    57  	}
    58  	// --worker worker1 -w worker2 --worker=worker3,worker4 -w=worker5,worker6
    59  	cmd.PersistentFlags().StringSliceVarP(&commandMasterFlags.workers, "source", "s", []string{}, "MySQL Source ID.")
    60  	cmd.AddCommand(
    61  		master.NewStartTaskCmd(),
    62  		master.NewStopTaskCmd(),
    63  		master.NewPauseTaskCmd(),
    64  		master.NewResumeTaskCmd(),
    65  		master.NewCheckTaskCmd(),
    66  		//	master.NewUpdateTaskCmd(),
    67  		master.NewQueryStatusCmd(),
    68  		master.NewShowDDLLocksCmd(),
    69  		master.NewUnlockDDLLockCmd(),
    70  		master.NewPauseRelayCmd(),
    71  		master.NewResumeRelayCmd(),
    72  		master.NewPurgeRelayCmd(),
    73  		master.NewOperateSourceCmd(),
    74  		master.NewOfflineMemberCmd(),
    75  		master.NewOperateLeaderCmd(),
    76  		master.NewListMemberCmd(),
    77  		master.NewOperateSchemaCmd(),
    78  		master.NewGetCfgCmd(),
    79  		master.NewHandleErrorCmd(),
    80  		master.NewTransferSourceCmd(),
    81  		master.NewStartRelayCmd(),
    82  		master.NewStopRelayCmd(),
    83  		master.NewBinlogCmd(),
    84  		master.NewShardDDLLockCmd(),
    85  		master.NewSourceTableSchemaCmd(),
    86  		master.NewConfigCmd(),
    87  		master.NewValidationCmd(),
    88  		newEncryptCmd(),
    89  	)
    90  	// copied from (*cobra.Command).InitDefaultHelpCmd
    91  	helpCmd := &cobra.Command{
    92  		Use:   "help [command]",
    93  		Short: "Gets help about any command",
    94  		Long: `Help provides help for any command in the application.
    95  Simply type ` + cmd.Name() + ` help [path to command] for full details.`,
    96  
    97  		Run: func(c *cobra.Command, args []string) {
    98  			cmd2, _, e := c.Root().Find(args)
    99  			if cmd2 == nil || e != nil {
   100  				c.Printf("Unknown help topic %#q\n", args)
   101  				_ = c.Root().Usage()
   102  			} else {
   103  				cmd2.InitDefaultHelpFlag() // make possible 'help' flag to be shown
   104  				_ = cmd2.Help()
   105  			}
   106  		},
   107  	}
   108  	cmd.SetHelpCommand(helpCmd)
   109  	return cmd
   110  }
   111  
   112  // Init initializes dm-control.
   113  func Init(cfg *common.Config) error {
   114  	// set the log level temporarily
   115  	log.SetLevel(zapcore.InfoLevel)
   116  
   117  	return errors.Trace(common.InitUtils(cfg))
   118  }
   119  
   120  // Start starts running a command.
   121  func Start(args []string) (cmd *cobra.Command, err error) {
   122  	commandMasterFlags.Reset()
   123  	rootCmd := NewRootCmd()
   124  	rootCmd.SetArgs(args)
   125  	return rootCmd.ExecuteC()
   126  }
   127  
   128  func loop() error {
   129  	l, err := readline.NewEx(&readline.Config{
   130  		Prompt:          "\033[31m»\033[0m ",
   131  		HistoryFile:     "/tmp/dmctlreadline.tmp",
   132  		InterruptPrompt: "^C",
   133  		EOFPrompt:       "^D",
   134  	})
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	for {
   140  		line, err := l.Readline()
   141  		if err != nil {
   142  			if err == readline.ErrInterrupt {
   143  				break
   144  			} else if err == io.EOF {
   145  				break
   146  			}
   147  			continue
   148  		}
   149  
   150  		line = strings.TrimSpace(line)
   151  		if line == "exit" {
   152  			l.Close()
   153  			os.Exit(0)
   154  		} else if line == "" {
   155  			continue
   156  		}
   157  
   158  		args := common.SplitArgsRespectQuote(line)
   159  		c, err := Start(args)
   160  		if err != nil {
   161  			fmt.Println("fail to run:", args)
   162  			fmt.Println("Error:", err)
   163  			if c.CalledAs() == "" {
   164  				fmt.Printf("Run '%v --help' for usage.\n", c.CommandPath())
   165  			}
   166  		}
   167  
   168  		syncErr := log.L().Sync()
   169  		if syncErr != nil {
   170  			fmt.Fprintln(os.Stderr, "sync log failed", syncErr)
   171  		}
   172  	}
   173  	return l.Close()
   174  }
   175  
   176  // MainStart starts running a command.
   177  func MainStart(args []string) {
   178  	rootCmd := NewRootCmd()
   179  	rootCmd.RunE = func(cmd *cobra.Command, args []string) error {
   180  		if len(args) == 0 {
   181  			return loop()
   182  		}
   183  		return cmd.Help()
   184  	}
   185  
   186  	rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
   187  		if printVersion, err := cmd.Flags().GetBool("version"); err != nil {
   188  			return errors.Trace(err)
   189  		} else if printVersion {
   190  			cmd.Println(version.GetRawInfo())
   191  			os.Exit(0)
   192  		}
   193  
   194  		cfg := common.NewConfig(cmd.Flags())
   195  		err := cfg.Adjust()
   196  		if err != nil {
   197  			return err
   198  		}
   199  
   200  		err = cfg.Validate()
   201  		if err != nil {
   202  			return err
   203  		}
   204  
   205  		return Init(cfg)
   206  	}
   207  	common.DefineConfigFlagSet(rootCmd.PersistentFlags())
   208  	rootCmd.SetArgs(args)
   209  	if c, err := rootCmd.ExecuteC(); err != nil {
   210  		rootCmd.Println("Error:", err)
   211  		if c.CalledAs() == "" {
   212  			rootCmd.Printf("Run '%v --help' for usage.\n", c.CommandPath())
   213  		}
   214  		os.Exit(1)
   215  	}
   216  }
   217  
   218  func newEncryptCmd() *cobra.Command {
   219  	return &cobra.Command{
   220  		Use:   "encrypt <plain-text>",
   221  		Short: "Encrypts plain text to cipher text",
   222  		RunE: func(cmd *cobra.Command, args []string) error {
   223  			if len(args) != 1 {
   224  				return cmd.Help()
   225  			}
   226  			ctx, cancel := context.WithCancel(context.Background())
   227  			defer cancel()
   228  			resp := &pb.EncryptResponse{}
   229  			err := common.SendRequest(
   230  				ctx,
   231  				"Encrypt",
   232  				&pb.EncryptRequest{
   233  					Plaintext: args[0],
   234  				},
   235  				&resp,
   236  			)
   237  			if err != nil {
   238  				return err
   239  			}
   240  			if !resp.Result {
   241  				return errors.New(resp.Msg)
   242  			}
   243  			fmt.Println(resp.Ciphertext)
   244  			return nil
   245  		},
   246  	}
   247  }