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  }