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