github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/ctl/common/config.go (about) 1 // Copyright 2019 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 common 15 16 import ( 17 "encoding/json" 18 "fmt" 19 "net" 20 "os" 21 "strings" 22 "time" 23 24 "github.com/BurntSushi/toml" 25 "github.com/google/shlex" 26 "github.com/pingcap/errors" 27 "github.com/pingcap/tiflow/dm/config/security" 28 "github.com/pingcap/tiflow/dm/pkg/log" 29 "github.com/pingcap/tiflow/dm/pkg/utils" 30 "github.com/spf13/pflag" 31 "go.uber.org/zap" 32 ) 33 34 const ( 35 defaultRPCTimeout = "10m" 36 37 // Master specifies member master type. 38 Master = "master" 39 // Worker specifies member worker type. 40 Worker = "worker" 41 42 dialTimeout = 3 * time.Second 43 keepaliveTimeout = 3 * time.Second 44 keepaliveTime = 3 * time.Second 45 46 // DefaultErrorCnt represents default count of errors to display for check-task. 47 DefaultErrorCnt = 10 48 // DefaultWarnCnt represents count of warns to display for check-task. 49 DefaultWarnCnt = 10 50 ) 51 52 var argsNeedAdjust = [...]string{"-version", "-config", "-master-addr", "-rpc-timeout", "-ssl-ca", "-ssl-cert", "-ssl-key"} 53 54 // NewConfig creates a new base config for dmctl. 55 func NewConfig(fs *pflag.FlagSet) *Config { 56 cfg := &Config{} 57 cfg.FlagSet = fs 58 return cfg 59 } 60 61 // DefineConfigFlagSet defines flag definitions for configs. 62 func DefineConfigFlagSet(fs *pflag.FlagSet) { 63 fs.BoolP("version", "V", false, "Prints version and exit.") 64 fs.String("config", "", "Path to config file.") 65 fs.String("master-addr", "", "Master API server address, this parameter is required when interacting with the dm-master") 66 fs.String("rpc-timeout", defaultRPCTimeout, fmt.Sprintf("RPC timeout, default is %s.", defaultRPCTimeout)) 67 fs.String("ssl-ca", "", "Path of file that contains list of trusted SSL CAs for connection.") 68 fs.String("ssl-cert", "", "Path of file that contains X509 certificate in PEM format for connection.") 69 fs.String("ssl-key", "", "Path of file that contains X509 key in PEM format for connection.") 70 } 71 72 // AdjustArgumentsForPflags adjust flag format args to pflags format. 73 func AdjustArgumentsForPflags(args []string) []string { 74 for i, arg := range args { 75 arg = strings.TrimSpace(arg) 76 for _, adjustArg := range argsNeedAdjust { 77 // -master-addr 127.0.0.1:8261 and -master-addr=127.0.0.1:8261 78 if arg == adjustArg || strings.HasPrefix(arg, adjustArg+"=") { 79 args[i] = "-" + arg 80 } 81 } 82 } 83 return args 84 } 85 86 func (c *Config) getConfigFromFlagSet() error { 87 var err error 88 fs := c.FlagSet 89 c.ConfigFile, err = fs.GetString("config") 90 if err != nil { 91 return err 92 } 93 c.MasterAddr, err = fs.GetString("master-addr") 94 if err != nil { 95 return err 96 } 97 c.RPCTimeoutStr, err = fs.GetString("rpc-timeout") 98 if err != nil { 99 return err 100 } 101 c.SSLCA, err = fs.GetString("ssl-ca") 102 if err != nil { 103 return err 104 } 105 c.SSLCert, err = fs.GetString("ssl-cert") 106 if err != nil { 107 return err 108 } 109 c.SSLKey, err = fs.GetString("ssl-key") 110 return err 111 } 112 113 // Config is the configuration. 114 type Config struct { 115 *pflag.FlagSet `json:"-"` 116 117 MasterAddr string `toml:"master-addr" json:"master-addr"` 118 119 RPCTimeoutStr string `toml:"rpc-timeout" json:"rpc-timeout"` 120 RPCTimeout time.Duration `json:"-"` 121 122 ConfigFile string `json:"config-file"` 123 124 security.Security 125 } 126 127 func (c *Config) String() string { 128 //nolint:staticcheck 129 cfg, err := json.Marshal(c) 130 if err != nil { 131 fmt.Printf("marshal config to json error %v", err) 132 } 133 return string(cfg) 134 } 135 136 // Adjust parses flag definitions from the argument list. 137 func (c *Config) Adjust() error { 138 err := c.getConfigFromFlagSet() 139 if err != nil { 140 return errors.Trace(err) 141 } 142 143 // Load config file if specified. 144 if c.ConfigFile != "" { 145 err = c.configFromFile(c.ConfigFile) 146 if err != nil { 147 return errors.Annotatef(err, "the current command parameter: --config is invalid or used incorrectly") 148 } 149 } 150 151 // Parse again to replace with command line options. 152 err = c.getConfigFromFlagSet() 153 if err != nil { 154 return errors.Trace(err) 155 } 156 157 // try get master Addr from env "DM_MASTER_ADDR" if this flag is empty. 158 if c.MasterAddr == "" { 159 c.MasterAddr = os.Getenv("DM_MASTER_ADDR") 160 } 161 if c.MasterAddr == "" { 162 return errors.Errorf("--master-addr not provided, this parameter is required when interacting with the dm-master, you can also use environment variable 'DM_MASTER_ADDR' to specify the value. Use `dmctl --help` to see more help messages") 163 } 164 165 return errors.Trace(c.adjust()) 166 } 167 168 // Validate check config is ready to execute command. 169 func (c *Config) Validate() error { 170 if c.MasterAddr == "" { 171 return errors.New("--master-addr not provided") 172 } 173 if err := validateAddr(c.MasterAddr); err != nil { 174 return errors.Annotatef(err, "specify master addr %s", c.MasterAddr) 175 } 176 return nil 177 } 178 179 // configFromFile loads config from file. 180 func (c *Config) configFromFile(path string) error { 181 _, err := toml.DecodeFile(path, c) 182 return errors.Trace(err) 183 } 184 185 // adjust adjusts configs. 186 func (c *Config) adjust() error { 187 if c.RPCTimeoutStr == "" { 188 c.RPCTimeoutStr = defaultRPCTimeout 189 } 190 timeout, err := time.ParseDuration(c.RPCTimeoutStr) 191 if err != nil { 192 return errors.Trace(err) 193 } 194 if timeout <= time.Duration(0) { 195 return errors.Errorf("invalid time duration: %s", c.RPCTimeoutStr) 196 } 197 c.RPCTimeout = timeout 198 return nil 199 } 200 201 // validate host:port format address. 202 func validateAddr(addr string) error { 203 endpoints := strings.Split(addr, ",") 204 for _, endpoint := range endpoints { 205 if _, _, err := net.SplitHostPort(utils.UnwrapScheme(endpoint)); err != nil { 206 return errors.Trace(err) 207 } 208 } 209 return nil 210 } 211 212 // SplitArgsRespectQuote splits args by space, but won't split space inside single or double quotes. 213 func SplitArgsRespectQuote(line string) []string { 214 ret, err := shlex.Split(line) 215 if err != nil { 216 log.L().Error("split args error", zap.Error(err)) 217 return []string{line} 218 } 219 return ret 220 }