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 }