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 }