github.com/pingcap/tiup@v1.15.1/components/dm/command/root.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 command 15 16 import ( 17 "encoding/json" 18 "fmt" 19 "os" 20 "path" 21 "strings" 22 23 "github.com/fatih/color" 24 "github.com/joomcode/errorx" 25 perrs "github.com/pingcap/errors" 26 "github.com/pingcap/tiup/components/dm/spec" 27 "github.com/pingcap/tiup/pkg/cluster/executor" 28 "github.com/pingcap/tiup/pkg/cluster/manager" 29 operator "github.com/pingcap/tiup/pkg/cluster/operation" 30 cspec "github.com/pingcap/tiup/pkg/cluster/spec" 31 tiupmeta "github.com/pingcap/tiup/pkg/environment" 32 "github.com/pingcap/tiup/pkg/localdata" 33 "github.com/pingcap/tiup/pkg/logger" 34 logprinter "github.com/pingcap/tiup/pkg/logger/printer" 35 "github.com/pingcap/tiup/pkg/proxy" 36 "github.com/pingcap/tiup/pkg/repository" 37 "github.com/pingcap/tiup/pkg/tui" 38 "github.com/pingcap/tiup/pkg/utils" 39 "github.com/pingcap/tiup/pkg/version" 40 "github.com/spf13/cobra" 41 "go.uber.org/zap" 42 ) 43 44 var ( 45 // nolint 46 errNS = errorx.NewNamespace("cmd") 47 rootCmd *cobra.Command 48 gOpt operator.Options 49 skipConfirm bool 50 log = logprinter.NewLogger("") // init default logger 51 ) 52 53 var dmspec *cspec.SpecManager 54 var cm *manager.Manager 55 56 func init() { 57 logger.InitGlobalLogger() 58 59 tui.AddColorFunctionsForCobra() 60 61 cobra.EnableCommandSorting = false 62 63 nativeEnvVar := strings.ToLower(os.Getenv(localdata.EnvNameNativeSSHClient)) 64 if nativeEnvVar == "true" || nativeEnvVar == "1" || nativeEnvVar == "enable" { 65 gOpt.NativeSSH = true 66 } 67 68 rootCmd = &cobra.Command{ 69 Use: tui.OsArgs0(), 70 Short: "(EXPERIMENTAL) Deploy a DM cluster", 71 Long: `EXPERIMENTAL: This is an experimental feature, things may or may not work, 72 please backup your data before process.`, 73 SilenceUsage: true, 74 SilenceErrors: true, 75 Version: version.NewTiUPVersion().String(), 76 PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 77 // populate logger 78 log.SetDisplayModeFromString(gOpt.DisplayMode) 79 80 var err error 81 var env *tiupmeta.Environment 82 if err = cspec.Initialize("dm"); err != nil { 83 return err 84 } 85 86 dmspec = spec.GetSpecManager() 87 logger.EnableAuditLog(cspec.AuditDir()) 88 cm = manager.NewManager("dm", dmspec, log) 89 90 // Running in other OS/ARCH Should be fine we only download manifest file. 91 env, err = tiupmeta.InitEnv(repository.Options{ 92 GOOS: "linux", 93 GOARCH: "amd64", 94 }, repository.MirrorOptions{}) 95 if err != nil { 96 return err 97 } 98 tiupmeta.SetGlobalEnv(env) 99 100 if gOpt.NativeSSH { 101 gOpt.SSHType = executor.SSHTypeSystem 102 zap.L().Info("System ssh client will be used", 103 zap.String(localdata.EnvNameNativeSSHClient, os.Getenv(localdata.EnvNameNativeSSHClient))) 104 fmt.Println("The --native-ssh flag has been deprecated, please use --ssh=system") 105 } 106 107 err = proxy.MaybeStartProxy( 108 gOpt.SSHProxyHost, 109 gOpt.SSHProxyPort, 110 gOpt.SSHProxyUser, 111 gOpt.SSHProxyUsePassword, 112 gOpt.SSHProxyIdentity, 113 log, 114 ) 115 if err != nil { 116 return perrs.Annotate(err, "start http-proxy") 117 } 118 119 return nil 120 }, 121 PersistentPostRunE: func(cmd *cobra.Command, args []string) error { 122 proxy.MaybeStopProxy() 123 return tiupmeta.GlobalEnv().V1Repository().Mirror().Close() 124 }, 125 } 126 127 tui.BeautifyCobraUsageAndHelp(rootCmd) 128 129 rootCmd.PersistentFlags().Uint64Var(&gOpt.SSHTimeout, "ssh-timeout", 5, "Timeout in seconds to connect host via SSH, ignored for operations that don't need an SSH connection.") 130 rootCmd.PersistentFlags().Uint64Var(&gOpt.OptTimeout, "wait-timeout", 120, "Timeout in seconds to wait for an operation to complete, ignored for operations that don't fit.") 131 rootCmd.PersistentFlags().BoolVarP(&skipConfirm, "yes", "y", false, "Skip all confirmations and assumes 'yes'") 132 rootCmd.PersistentFlags().BoolVar(&gOpt.NativeSSH, "native-ssh", gOpt.NativeSSH, "Use the SSH client installed on local system instead of the build-in one.") 133 rootCmd.PersistentFlags().StringVar((*string)(&gOpt.SSHType), "ssh", "", "The executor type: 'builtin', 'system', 'none'") 134 rootCmd.PersistentFlags().IntVarP(&gOpt.Concurrency, "concurrency", "c", 5, "max number of parallel tasks allowed") 135 rootCmd.PersistentFlags().StringVar(&gOpt.DisplayMode, "format", "default", "(EXPERIMENTAL) The format of output, available values are [default, json]") 136 rootCmd.PersistentFlags().StringVar(&gOpt.SSHProxyHost, "ssh-proxy-host", "", "The SSH proxy host used to connect to remote host.") 137 rootCmd.PersistentFlags().StringVar(&gOpt.SSHProxyUser, "ssh-proxy-user", utils.CurrentUser(), "The user name used to login the proxy host.") 138 rootCmd.PersistentFlags().IntVar(&gOpt.SSHProxyPort, "ssh-proxy-port", 22, "The port used to login the proxy host.") 139 rootCmd.PersistentFlags().StringVar(&gOpt.SSHProxyIdentity, "ssh-proxy-identity-file", path.Join(utils.UserHome(), ".ssh", "id_rsa"), "The identity file used to login the proxy host.") 140 rootCmd.PersistentFlags().BoolVar(&gOpt.SSHProxyUsePassword, "ssh-proxy-use-password", false, "Use password to login the proxy host.") 141 rootCmd.PersistentFlags().Uint64Var(&gOpt.SSHProxyTimeout, "ssh-proxy-timeout", 5, "Timeout in seconds to connect the proxy host via SSH, ignored for operations that don't need an SSH connection.") 142 _ = rootCmd.PersistentFlags().MarkHidden("native-ssh") 143 _ = rootCmd.PersistentFlags().MarkHidden("ssh-proxy-host") 144 _ = rootCmd.PersistentFlags().MarkHidden("ssh-proxy-user") 145 _ = rootCmd.PersistentFlags().MarkHidden("ssh-proxy-port") 146 _ = rootCmd.PersistentFlags().MarkHidden("ssh-proxy-identity-file") 147 _ = rootCmd.PersistentFlags().MarkHidden("ssh-proxy-use-password") 148 _ = rootCmd.PersistentFlags().MarkHidden("ssh-proxy-timeout") 149 150 rootCmd.AddCommand( 151 newDeployCmd(), 152 newStartCmd(), 153 newStopCmd(), 154 newRestartCmd(), 155 newListCmd(), 156 newDestroyCmd(), 157 newAuditCmd(), 158 newExecCmd(), 159 newEditConfigCmd(), 160 newDisplayCmd(), 161 newPruneCmd(), 162 newReloadCmd(), 163 newUpgradeCmd(), 164 newPatchCmd(), 165 newScaleOutCmd(), 166 newScaleInCmd(), 167 newImportCmd(), 168 newEnableCmd(), 169 newDisableCmd(), 170 newReplayCmd(), 171 newTemplateCmd(), 172 newMetaCmd(), 173 newRotateSSHCmd(), 174 ) 175 } 176 177 func printErrorMessageForNormalError(err error) { 178 _, _ = tui.ColorErrorMsg.Fprintf(os.Stderr, "\nError: %s\n", err.Error()) 179 } 180 181 func printErrorMessageForErrorX(err *errorx.Error) { 182 msg := "" 183 ident := 0 184 causeErrX := err 185 for causeErrX != nil { 186 if ident > 0 { 187 msg += strings.Repeat(" ", ident) + "caused by: " 188 } 189 currentErrMsg := causeErrX.Message() 190 if len(currentErrMsg) > 0 { 191 if ident == 0 { 192 // Print error code only for top level error 193 msg += fmt.Sprintf("%s (%s)\n", currentErrMsg, causeErrX.Type().FullName()) 194 } else { 195 msg += fmt.Sprintf("%s\n", currentErrMsg) 196 } 197 ident++ 198 } 199 cause := causeErrX.Cause() 200 if c := errorx.Cast(cause); c != nil { 201 causeErrX = c 202 } else { 203 if cause != nil { 204 if ident > 0 { 205 // Out most error may have empty message. In this case we treat it as a transparent error. 206 // Thus `ident == 0` can be possible. 207 msg += strings.Repeat(" ", ident) + "caused by: " 208 } 209 msg += fmt.Sprintf("%s\n", cause.Error()) 210 } 211 break 212 } 213 } 214 _, _ = tui.ColorErrorMsg.Fprintf(os.Stderr, "\nError: %s", msg) 215 } 216 217 func extractSuggestionFromErrorX(err *errorx.Error) string { 218 cause := err 219 for cause != nil { 220 v, ok := cause.Property(utils.ErrPropSuggestion) 221 if ok { 222 if s, ok := v.(string); ok { 223 return s 224 } 225 } 226 cause = errorx.Cast(cause.Cause()) 227 } 228 229 return "" 230 } 231 232 // Execute executes the root command 233 func Execute() { 234 zap.L().Info("Execute command", zap.String("command", tui.OsArgs())) 235 zap.L().Debug("Environment variables", zap.Strings("env", os.Environ())) 236 237 code := 0 238 err := rootCmd.Execute() 239 if err != nil { 240 code = 1 241 } 242 243 zap.L().Info("Execute command finished", zap.Int("code", code), zap.Error(err)) 244 245 if err != nil { 246 switch strings.ToLower(gOpt.DisplayMode) { 247 case "json": 248 obj := struct { 249 Err string `json:"error"` 250 }{ 251 Err: err.Error(), 252 } 253 data, err := json.Marshal(obj) 254 if err != nil { 255 fmt.Printf("{\"error\": \"%s\"}", err) 256 break 257 } 258 fmt.Fprintln(os.Stderr, string(data)) 259 default: 260 if errx := errorx.Cast(err); errx != nil { 261 printErrorMessageForErrorX(errx) 262 } else { 263 printErrorMessageForNormalError(err) 264 } 265 266 if !errorx.HasTrait(err, utils.ErrTraitPreCheck) { 267 logger.OutputDebugLog("tiup-dm") 268 } 269 270 if errx := errorx.Cast(err); errx != nil { 271 if suggestion := extractSuggestionFromErrorX(errx); len(suggestion) > 0 { 272 _, _ = fmt.Fprintf(os.Stderr, "\n%s\n", suggestion) 273 } 274 } 275 } 276 } 277 278 err = logger.OutputAuditLogIfEnabled() 279 if err != nil { 280 zap.L().Warn("Write audit log file failed", zap.Error(err)) 281 code = 1 282 } 283 284 color.Unset() 285 286 if code != 0 { 287 os.Exit(code) 288 } 289 }