github.com/clem109/go-ethereum@v1.8.3-0.20180316121352-fe6cf00f480a/cmd/puppeth/wizard.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of go-ethereum. 3 // 4 // go-ethereum is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // go-ethereum is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "bufio" 21 "encoding/json" 22 "fmt" 23 "io/ioutil" 24 "math/big" 25 "net" 26 "os" 27 "path/filepath" 28 "sort" 29 "strconv" 30 "strings" 31 "sync" 32 33 "github.com/ethereum/go-ethereum/common" 34 "github.com/ethereum/go-ethereum/core" 35 "github.com/ethereum/go-ethereum/log" 36 "golang.org/x/crypto/ssh/terminal" 37 ) 38 39 // config contains all the configurations needed by puppeth that should be saved 40 // between sessions. 41 type config struct { 42 path string // File containing the configuration values 43 bootnodes []string // Bootnodes to always connect to by all nodes 44 ethstats string // Ethstats settings to cache for node deploys 45 46 Genesis *core.Genesis `json:"genesis,omitempty"` // Genesis block to cache for node deploys 47 Servers map[string][]byte `json:"servers,omitempty"` 48 } 49 50 // servers retrieves an alphabetically sorted list of servers. 51 func (c config) servers() []string { 52 servers := make([]string, 0, len(c.Servers)) 53 for server := range c.Servers { 54 servers = append(servers, server) 55 } 56 sort.Strings(servers) 57 58 return servers 59 } 60 61 // flush dumps the contents of config to disk. 62 func (c config) flush() { 63 os.MkdirAll(filepath.Dir(c.path), 0755) 64 65 out, _ := json.MarshalIndent(c, "", " ") 66 if err := ioutil.WriteFile(c.path, out, 0644); err != nil { 67 log.Warn("Failed to save puppeth configs", "file", c.path, "err", err) 68 } 69 } 70 71 type wizard struct { 72 network string // Network name to manage 73 conf config // Configurations from previous runs 74 75 servers map[string]*sshClient // SSH connections to servers to administer 76 services map[string][]string // Ethereum services known to be running on servers 77 78 in *bufio.Reader // Wrapper around stdin to allow reading user input 79 lock sync.Mutex // Lock to protect configs during concurrent service discovery 80 } 81 82 // read reads a single line from stdin, trimming if from spaces. 83 func (w *wizard) read() string { 84 fmt.Printf("> ") 85 text, err := w.in.ReadString('\n') 86 if err != nil { 87 log.Crit("Failed to read user input", "err", err) 88 } 89 return strings.TrimSpace(text) 90 } 91 92 // readString reads a single line from stdin, trimming if from spaces, enforcing 93 // non-emptyness. 94 func (w *wizard) readString() string { 95 for { 96 fmt.Printf("> ") 97 text, err := w.in.ReadString('\n') 98 if err != nil { 99 log.Crit("Failed to read user input", "err", err) 100 } 101 if text = strings.TrimSpace(text); text != "" { 102 return text 103 } 104 } 105 } 106 107 // readDefaultString reads a single line from stdin, trimming if from spaces. If 108 // an empty line is entered, the default value is returned. 109 func (w *wizard) readDefaultString(def string) string { 110 fmt.Printf("> ") 111 text, err := w.in.ReadString('\n') 112 if err != nil { 113 log.Crit("Failed to read user input", "err", err) 114 } 115 if text = strings.TrimSpace(text); text != "" { 116 return text 117 } 118 return def 119 } 120 121 // readInt reads a single line from stdin, trimming if from spaces, enforcing it 122 // to parse into an integer. 123 func (w *wizard) readInt() int { 124 for { 125 fmt.Printf("> ") 126 text, err := w.in.ReadString('\n') 127 if err != nil { 128 log.Crit("Failed to read user input", "err", err) 129 } 130 if text = strings.TrimSpace(text); text == "" { 131 continue 132 } 133 val, err := strconv.Atoi(strings.TrimSpace(text)) 134 if err != nil { 135 log.Error("Invalid input, expected integer", "err", err) 136 continue 137 } 138 return val 139 } 140 } 141 142 // readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing 143 // it to parse into an integer. If an empty line is entered, the default value is 144 // returned. 145 func (w *wizard) readDefaultInt(def int) int { 146 for { 147 fmt.Printf("> ") 148 text, err := w.in.ReadString('\n') 149 if err != nil { 150 log.Crit("Failed to read user input", "err", err) 151 } 152 if text = strings.TrimSpace(text); text == "" { 153 return def 154 } 155 val, err := strconv.Atoi(strings.TrimSpace(text)) 156 if err != nil { 157 log.Error("Invalid input, expected integer", "err", err) 158 continue 159 } 160 return val 161 } 162 } 163 164 // readDefaultBigInt reads a single line from stdin, trimming if from spaces, 165 // enforcing it to parse into a big integer. If an empty line is entered, the 166 // default value is returned. 167 func (w *wizard) readDefaultBigInt(def *big.Int) *big.Int { 168 for { 169 fmt.Printf("> ") 170 text, err := w.in.ReadString('\n') 171 if err != nil { 172 log.Crit("Failed to read user input", "err", err) 173 } 174 if text = strings.TrimSpace(text); text == "" { 175 return def 176 } 177 val, ok := new(big.Int).SetString(text, 0) 178 if !ok { 179 log.Error("Invalid input, expected big integer") 180 continue 181 } 182 return val 183 } 184 } 185 186 /* 187 // readFloat reads a single line from stdin, trimming if from spaces, enforcing it 188 // to parse into a float. 189 func (w *wizard) readFloat() float64 { 190 for { 191 fmt.Printf("> ") 192 text, err := w.in.ReadString('\n') 193 if err != nil { 194 log.Crit("Failed to read user input", "err", err) 195 } 196 if text = strings.TrimSpace(text); text == "" { 197 continue 198 } 199 val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) 200 if err != nil { 201 log.Error("Invalid input, expected float", "err", err) 202 continue 203 } 204 return val 205 } 206 } 207 */ 208 209 // readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing 210 // it to parse into a float. If an empty line is entered, the default value is returned. 211 func (w *wizard) readDefaultFloat(def float64) float64 { 212 for { 213 fmt.Printf("> ") 214 text, err := w.in.ReadString('\n') 215 if err != nil { 216 log.Crit("Failed to read user input", "err", err) 217 } 218 if text = strings.TrimSpace(text); text == "" { 219 return def 220 } 221 val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) 222 if err != nil { 223 log.Error("Invalid input, expected float", "err", err) 224 continue 225 } 226 return val 227 } 228 } 229 230 // readPassword reads a single line from stdin, trimming it from the trailing new 231 // line and returns it. The input will not be echoed. 232 func (w *wizard) readPassword() string { 233 fmt.Printf("> ") 234 text, err := terminal.ReadPassword(int(os.Stdin.Fd())) 235 if err != nil { 236 log.Crit("Failed to read password", "err", err) 237 } 238 fmt.Println() 239 return string(text) 240 } 241 242 // readAddress reads a single line from stdin, trimming if from spaces and converts 243 // it to an Ethereum address. 244 func (w *wizard) readAddress() *common.Address { 245 for { 246 // Read the address from the user 247 fmt.Printf("> 0x") 248 text, err := w.in.ReadString('\n') 249 if err != nil { 250 log.Crit("Failed to read user input", "err", err) 251 } 252 if text = strings.TrimSpace(text); text == "" { 253 return nil 254 } 255 // Make sure it looks ok and return it if so 256 if len(text) != 40 { 257 log.Error("Invalid address length, please retry") 258 continue 259 } 260 bigaddr, _ := new(big.Int).SetString(text, 16) 261 address := common.BigToAddress(bigaddr) 262 return &address 263 } 264 } 265 266 // readDefaultAddress reads a single line from stdin, trimming if from spaces and 267 // converts it to an Ethereum address. If an empty line is entered, the default 268 // value is returned. 269 func (w *wizard) readDefaultAddress(def common.Address) common.Address { 270 for { 271 // Read the address from the user 272 fmt.Printf("> 0x") 273 text, err := w.in.ReadString('\n') 274 if err != nil { 275 log.Crit("Failed to read user input", "err", err) 276 } 277 if text = strings.TrimSpace(text); text == "" { 278 return def 279 } 280 // Make sure it looks ok and return it if so 281 if len(text) != 40 { 282 log.Error("Invalid address length, please retry") 283 continue 284 } 285 bigaddr, _ := new(big.Int).SetString(text, 16) 286 return common.BigToAddress(bigaddr) 287 } 288 } 289 290 // readJSON reads a raw JSON message and returns it. 291 func (w *wizard) readJSON() string { 292 var blob json.RawMessage 293 294 for { 295 fmt.Printf("> ") 296 if err := json.NewDecoder(w.in).Decode(&blob); err != nil { 297 log.Error("Invalid JSON, please try again", "err", err) 298 continue 299 } 300 return string(blob) 301 } 302 } 303 304 // readIPAddress reads a single line from stdin, trimming if from spaces and 305 // returning it if it's convertible to an IP address. The reason for keeping 306 // the user input format instead of returning a Go net.IP is to match with 307 // weird formats used by ethstats, which compares IPs textually, not by value. 308 func (w *wizard) readIPAddress() string { 309 for { 310 // Read the IP address from the user 311 fmt.Printf("> ") 312 text, err := w.in.ReadString('\n') 313 if err != nil { 314 log.Crit("Failed to read user input", "err", err) 315 } 316 if text = strings.TrimSpace(text); text == "" { 317 return "" 318 } 319 // Make sure it looks ok and return it if so 320 if ip := net.ParseIP(text); ip == nil { 321 log.Error("Invalid IP address, please retry") 322 continue 323 } 324 return text 325 } 326 }