github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/config/configsetget.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  
     6  package config
     7  
     8  import (
     9  	"fmt"
    10  	"os"
    11  	"regexp"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/spf13/cobra"
    16  	"gopkg.in/yaml.v2"
    17  
    18  	"github.com/iotexproject/iotex-core/ioctl/output"
    19  	"github.com/iotexproject/iotex-core/ioctl/validator"
    20  )
    21  
    22  // Regexp patterns
    23  const (
    24  	_ipPattern               = `((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)`
    25  	_domainPattern           = `[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}(\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,62})*(\.[a-zA-Z][a-zA-Z0-9]{0,10}){1}`
    26  	_urlPattern              = `[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)`
    27  	_localPattern            = "localhost"
    28  	_endpointPattern         = "(" + _ipPattern + "|(" + _domainPattern + ")" + "|(" + _localPattern + "))" + `(:\d{1,5})?`
    29  	_defaultEndpoint         = "api.iotex.one:443"
    30  	_defaultAnalyserEndpoint = "https://iotex-analyser-api-mainnet.chainanalytics.org"
    31  	// _defaultWsEndpoint default w3bstream endpoint
    32  	_defaultWsEndpoint = "sprout-staging.w3bstream.com:9000"
    33  	// _defaultIPFSEndpoint default IPFS endpoint for uploading
    34  	_defaultIPFSEndpoint = "ipfs.mainnet.iotex.io"
    35  	// _defaultIPFSGateway default IPFS gateway for resource fetching
    36  	_defaultIPFSGateway = "https://ipfs.io"
    37  	// _defaultWsRegisterContract  default project register contract address
    38  	_defaultWsRegisterContract = "0x184C72E39a642058CCBc369485c7fd614B40a03d"
    39  )
    40  
    41  var (
    42  	_supportedLanguage = []string{"English", "中文"}
    43  	_validArgs         = []string{"endpoint", "wallet", "explorer", "defaultacc", "language", "nsv2height", "wsEndpoint", "ipfsEndpoint", "ipfsGateway", "wsRegisterContract"}
    44  	_validGetArgs      = []string{"endpoint", "wallet", "explorer", "defaultacc", "language", "nsv2height", "analyserEndpoint", "wsEndpoint", "ipfsEndpoint", "ipfsGateway", "wsRegisterContract", "all"}
    45  	_validExpl         = []string{"iotexscan", "iotxplorer"}
    46  	_endpointCompile   = regexp.MustCompile("^" + _endpointPattern + "$")
    47  )
    48  
    49  // _configGetCmd represents the config get command
    50  var _configGetCmd = &cobra.Command{
    51  	Use:       "get VARIABLE",
    52  	Short:     "Get config fields from ioctl",
    53  	Long:      "Get config fields from ioctl\nValid Variables: [" + strings.Join(_validGetArgs, ", ") + "]",
    54  	ValidArgs: _validGetArgs,
    55  	Args: func(cmd *cobra.Command, args []string) error {
    56  		if len(args) != 1 {
    57  			return fmt.Errorf("accepts 1 arg(s), received %d\n"+
    58  				"Valid arg(s): %s", len(args), _validGetArgs)
    59  		}
    60  		return cobra.OnlyValidArgs(cmd, args)
    61  	},
    62  	RunE: func(cmd *cobra.Command, args []string) error {
    63  		cmd.SilenceUsage = true
    64  		err := Get(args[0])
    65  		return output.PrintError(err)
    66  	},
    67  }
    68  
    69  // _configSetCmd represents the config set command
    70  var _configSetCmd = &cobra.Command{
    71  	Use:       "set VARIABLE VALUE",
    72  	Short:     "Set config fields for ioctl",
    73  	Long:      "Set config fields for ioctl\nValid Variables: [" + strings.Join(_validArgs, ", ") + "]",
    74  	ValidArgs: _validArgs,
    75  	Args: func(cmd *cobra.Command, args []string) error {
    76  		if len(args) != 2 {
    77  			return fmt.Errorf("accepts 2 arg(s), received %d\n"+
    78  				"Valid arg(s): %s", len(args), _validArgs)
    79  		}
    80  		return cobra.OnlyValidArgs(cmd, args[:1])
    81  	},
    82  	RunE: func(cmd *cobra.Command, args []string) error {
    83  		cmd.SilenceUsage = true
    84  		err := set(args)
    85  		return output.PrintError(err)
    86  	},
    87  }
    88  
    89  // _configResetCmd represents the config reset command
    90  var _configResetCmd = &cobra.Command{
    91  	Use:   "reset",
    92  	Short: "Reset config to default",
    93  	RunE: func(cmd *cobra.Command, args []string) error {
    94  		cmd.SilenceUsage = true
    95  		err := reset()
    96  		return output.PrintError(err)
    97  	},
    98  }
    99  
   100  type endpointMessage struct {
   101  	Endpoint      string `json:"endpoint"`
   102  	SecureConnect bool   `json:"secureConnect"`
   103  }
   104  
   105  func (m *endpointMessage) String() string {
   106  	if output.Format == "" {
   107  		message := fmt.Sprint(m.Endpoint, "    secure connect(TLS):", m.SecureConnect)
   108  		return message
   109  	}
   110  	return output.FormatString(output.Result, m)
   111  }
   112  
   113  func (m *Context) String() string {
   114  	if output.Format == "" {
   115  		message := output.JSONString(m)
   116  		return message
   117  	}
   118  	return output.FormatString(output.Result, m)
   119  }
   120  
   121  func (m *Config) String() string {
   122  	if output.Format == "" {
   123  		message := output.JSONString(m)
   124  		return message
   125  	}
   126  	return output.FormatString(output.Result, m)
   127  }
   128  
   129  func init() {
   130  	_configSetCmd.Flags().BoolVar(&Insecure, "insecure", false,
   131  		"set insecure connection as default")
   132  }
   133  
   134  // Get gets config variable
   135  func Get(arg string) error {
   136  	switch arg {
   137  	default:
   138  		return output.NewError(output.ConfigError, ErrConfigNotMatch.Error(), nil)
   139  	case "endpoint":
   140  		if ReadConfig.Endpoint == "" {
   141  			return output.NewError(output.ConfigError, ErrEmptyEndpoint.Error(), nil)
   142  		}
   143  		message := endpointMessage{Endpoint: ReadConfig.Endpoint, SecureConnect: ReadConfig.SecureConnect}
   144  		fmt.Println(message.String())
   145  	case "wallet":
   146  		output.PrintResult(ReadConfig.Wallet)
   147  	case "defaultacc":
   148  		if ReadConfig.DefaultAccount.AddressOrAlias == "" {
   149  			return output.NewError(output.ConfigError, "default account did not set", nil)
   150  		}
   151  		fmt.Println(ReadConfig.DefaultAccount.String())
   152  	case "explorer":
   153  		output.PrintResult(ReadConfig.Explorer)
   154  	case "language":
   155  		output.PrintResult(ReadConfig.Language)
   156  	case "nsv2height":
   157  		fmt.Println(ReadConfig.Nsv2height)
   158  	case "analyserEndpoint":
   159  		fmt.Println(ReadConfig.AnalyserEndpoint)
   160  	case "wsEndpoint":
   161  		fmt.Println(ReadConfig.WsEndpoint)
   162  	case "ipfsEndpoint":
   163  		fmt.Println(ReadConfig.IPFSEndpoint)
   164  	case "ipfsGateway":
   165  		fmt.Println(ReadConfig.IPFSGateway)
   166  	case "wsRegisterContract":
   167  		fmt.Println(ReadConfig.WsRegisterContract)
   168  	case "all":
   169  		fmt.Println(ReadConfig.String())
   170  	}
   171  	return nil
   172  }
   173  
   174  // GetContextAddressOrAlias gets current context
   175  func GetContextAddressOrAlias() (string, error) {
   176  	defaultAccount := ReadConfig.DefaultAccount
   177  	if strings.EqualFold(defaultAccount.AddressOrAlias, "") {
   178  		return "", output.NewError(output.ConfigError,
   179  			`use "ioctl config set defaultacc ADDRESS|ALIAS" to config default account first`, nil)
   180  	}
   181  	return defaultAccount.AddressOrAlias, nil
   182  }
   183  
   184  // GetAddressOrAlias gets address from args or context
   185  func GetAddressOrAlias(in string) (address string, err error) {
   186  	if !strings.EqualFold(in, "") {
   187  		address = in
   188  	} else {
   189  		address, err = GetContextAddressOrAlias()
   190  	}
   191  	return
   192  }
   193  
   194  // isValidEndpoint makes sure the endpoint matches the endpoint match pattern
   195  func isValidEndpoint(endpoint string) bool {
   196  	return _endpointCompile.MatchString(endpoint)
   197  }
   198  
   199  // isValidExplorer checks if the explorer is a valid option
   200  func isValidExplorer(arg string) bool {
   201  	for _, exp := range _validExpl {
   202  		if arg == exp {
   203  			return true
   204  		}
   205  	}
   206  	return false
   207  }
   208  
   209  // isSupportedLanguage checks if the language is a supported option and returns index when supported
   210  func isSupportedLanguage(arg string) Language {
   211  	if index, err := strconv.Atoi(arg); err == nil && index >= 0 && index < len(_supportedLanguage) {
   212  		return Language(index)
   213  	}
   214  	for i, lang := range _supportedLanguage {
   215  		if strings.EqualFold(arg, lang) {
   216  			return Language(i)
   217  		}
   218  	}
   219  	return Language(-1)
   220  }
   221  
   222  // writeConfig writes to config file
   223  func writeConfig() error {
   224  	out, err := yaml.Marshal(&ReadConfig)
   225  	if err != nil {
   226  		return output.NewError(output.SerializationError, "failed to marshal config", err)
   227  	}
   228  	if err := os.WriteFile(DefaultConfigFile, out, 0600); err != nil {
   229  		return output.NewError(output.WriteFileError,
   230  			fmt.Sprintf("failed to write to config file %s", DefaultConfigFile), err)
   231  	}
   232  	return nil
   233  }
   234  
   235  // set sets config variable
   236  func set(args []string) error {
   237  	switch args[0] {
   238  	default:
   239  		return output.NewError(output.ConfigError, ErrConfigNotMatch.Error(), nil)
   240  	case "endpoint":
   241  		if !isValidEndpoint(args[1]) {
   242  			return output.NewError(output.ConfigError, fmt.Sprintf("endpoint %s is not valid", args[1]), nil)
   243  		}
   244  		ReadConfig.Endpoint = args[1]
   245  		ReadConfig.SecureConnect = !Insecure
   246  	case "analyserEndpoint":
   247  		ReadConfig.AnalyserEndpoint = args[1]
   248  	case "wallet":
   249  		ReadConfig.Wallet = args[1]
   250  	case "explorer":
   251  		lowArg := strings.ToLower(args[1])
   252  		switch {
   253  		case isValidExplorer(lowArg):
   254  			ReadConfig.Explorer = lowArg
   255  		case args[1] == "custom":
   256  			output.PrintQuery(`Please enter a custom link below:("Example: iotexscan.io/action/")`)
   257  			var link string
   258  			if _, err := fmt.Scanln(&link); err != nil {
   259  				return output.NewError(output.InputError, "failed to input link", err)
   260  			}
   261  			match, err := regexp.MatchString(_urlPattern, link)
   262  			if err != nil {
   263  				return output.NewError(output.UndefinedError, "failed to validate link", nil)
   264  			}
   265  			if match {
   266  				ReadConfig.Explorer = link
   267  			} else {
   268  				return output.NewError(output.ValidationError, "invalid link", err)
   269  			}
   270  		default:
   271  			return output.NewError(output.ConfigError,
   272  				fmt.Sprintf("Explorer %s is not valid\nValid explorers: %s",
   273  					args[1], append(_validExpl, "custom")), nil)
   274  		}
   275  	case "defaultacc":
   276  		err1 := validator.ValidateAlias(args[1])
   277  		err2 := validator.ValidateAddress(args[1])
   278  		if err1 != nil && err2 != nil {
   279  			return output.NewError(output.ValidationError, "failed to validate alias or address", nil)
   280  		}
   281  		ReadConfig.DefaultAccount.AddressOrAlias = args[1]
   282  	case "language":
   283  		language := isSupportedLanguage(args[1])
   284  		if language == -1 {
   285  			return output.NewError(output.ConfigError,
   286  				fmt.Sprintf("Language %s is not supported\nSupported languages: %s",
   287  					args[1], _supportedLanguage), nil)
   288  		}
   289  		ReadConfig.Language = _supportedLanguage[language]
   290  	case "nsv2height":
   291  		height, err := strconv.ParseUint(args[1], 10, 64)
   292  		if err != nil {
   293  			return output.NewError(output.ValidationError, "invalid height", nil)
   294  		}
   295  		ReadConfig.Nsv2height = height
   296  	case "wsEndpoint":
   297  		ReadConfig.WsEndpoint = args[1]
   298  	case "ipfsEndpoint":
   299  		ReadConfig.IPFSEndpoint = args[1]
   300  	case "ipfsGateway":
   301  		ReadConfig.IPFSGateway = args[1]
   302  	case "wsRegisterContract":
   303  		ReadConfig.WsRegisterContract = args[1]
   304  	}
   305  	err := writeConfig()
   306  	if err != nil {
   307  		return err
   308  	}
   309  	output.PrintResult(strings.Title(args[0]) + " is set to " + args[1])
   310  	return nil
   311  }
   312  
   313  // reset resets all values of config
   314  func reset() error {
   315  	ReadConfig.Wallet = ConfigDir
   316  	ReadConfig.Endpoint = ""
   317  	ReadConfig.SecureConnect = true
   318  	ReadConfig.DefaultAccount = *new(Context)
   319  	ReadConfig.Explorer = "iotexscan"
   320  	ReadConfig.Language = "English"
   321  	ReadConfig.AnalyserEndpoint = _defaultAnalyserEndpoint
   322  	ReadConfig.WsEndpoint = _defaultWsEndpoint
   323  	ReadConfig.IPFSEndpoint = _defaultIPFSEndpoint
   324  	ReadConfig.IPFSGateway = _defaultIPFSGateway
   325  	ReadConfig.WsRegisterContract = _defaultWsRegisterContract
   326  
   327  	err := writeConfig()
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	output.PrintResult("Config set to default values")
   333  	return nil
   334  }