| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | // Copyright 2017 The go-ethereum Authors | 
					
						
							|  |  |  | // This file is part of go-ethereum. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // go-ethereum is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
					
						
							|  |  |  | // GNU General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU General Public License | 
					
						
							|  |  |  | // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package main | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bufio" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"math/big" | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 	"net" | 
					
						
							| 
									
										
										
										
											2018-12-03 16:50:59 +02:00
										 |  |  | 	"net/url" | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/common" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/core" | 
					
						
							|  |  |  | 	"github.com/ethereum/go-ethereum/log" | 
					
						
							|  |  |  | 	"golang.org/x/crypto/ssh/terminal" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // config contains all the configurations needed by puppeth that should be saved | 
					
						
							|  |  |  | // between sessions. | 
					
						
							|  |  |  | type config struct { | 
					
						
							| 
									
										
										
										
											2017-10-26 12:39:03 +03:00
										 |  |  | 	path      string   // File containing the configuration values | 
					
						
							| 
									
										
										
										
											2018-02-12 16:27:53 +02:00
										 |  |  | 	bootnodes []string // Bootnodes to always connect to by all nodes | 
					
						
							| 
									
										
										
										
											2017-10-26 12:39:03 +03:00
										 |  |  | 	ethstats  string   // Ethstats settings to cache for node deploys | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-26 12:39:03 +03:00
										 |  |  | 	Genesis *core.Genesis     `json:"genesis,omitempty"` // Genesis block to cache for node deploys | 
					
						
							| 
									
										
										
										
											2017-05-03 10:09:34 +03:00
										 |  |  | 	Servers map[string][]byte `json:"servers,omitempty"` | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // servers retrieves an alphabetically sorted list of servers. | 
					
						
							|  |  |  | func (c config) servers() []string { | 
					
						
							|  |  |  | 	servers := make([]string, 0, len(c.Servers)) | 
					
						
							|  |  |  | 	for server := range c.Servers { | 
					
						
							|  |  |  | 		servers = append(servers, server) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sort.Strings(servers) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return servers | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // flush dumps the contents of config to disk. | 
					
						
							|  |  |  | func (c config) flush() { | 
					
						
							|  |  |  | 	os.MkdirAll(filepath.Dir(c.path), 0755) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	out, _ := json.MarshalIndent(c, "", "  ") | 
					
						
							|  |  |  | 	if err := ioutil.WriteFile(c.path, out, 0644); err != nil { | 
					
						
							|  |  |  | 		log.Warn("Failed to save puppeth configs", "file", c.path, "err", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type wizard struct { | 
					
						
							|  |  |  | 	network string // Network name to manage | 
					
						
							|  |  |  | 	conf    config // Configurations from previous runs | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	servers  map[string]*sshClient // SSH connections to servers to administer | 
					
						
							|  |  |  | 	services map[string][]string   // Ethereum services known to be running on servers | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-19 14:40:43 +03:00
										 |  |  | 	in   *bufio.Reader // Wrapper around stdin to allow reading user input | 
					
						
							|  |  |  | 	lock sync.Mutex    // Lock to protect configs during concurrent service discovery | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // read reads a single line from stdin, trimming if from spaces. | 
					
						
							|  |  |  | func (w *wizard) read() string { | 
					
						
							|  |  |  | 	fmt.Printf("> ") | 
					
						
							|  |  |  | 	text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return strings.TrimSpace(text) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // readString reads a single line from stdin, trimming if from spaces, enforcing | 
					
						
							|  |  |  | // non-emptyness. | 
					
						
							|  |  |  | func (w *wizard) readString() string { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text != "" { | 
					
						
							|  |  |  | 			return text | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-03 16:50:59 +02:00
										 |  |  | // readDefaultString reads a single line from stdin, trimming if from spaces. If | 
					
						
							|  |  |  | // an empty line is entered, the default value is returned. | 
					
						
							|  |  |  | func (w *wizard) readDefaultString(def string) string { | 
					
						
							|  |  |  | 	fmt.Printf("> ") | 
					
						
							|  |  |  | 	text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if text = strings.TrimSpace(text); text != "" { | 
					
						
							|  |  |  | 		return text | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return def | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // readDefaultYesNo reads a single line from stdin, trimming if from spaces and | 
					
						
							|  |  |  | // interpreting it as a 'yes' or a 'no'. If an empty line is entered, the default | 
					
						
							|  |  |  | // value is returned. | 
					
						
							|  |  |  | func (w *wizard) readDefaultYesNo(def bool) bool { | 
					
						
							| 
									
										
										
										
											2018-11-24 23:22:25 +01:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-12-03 16:50:59 +02:00
										 |  |  | 		if text = strings.ToLower(strings.TrimSpace(text)); text == "" { | 
					
						
							|  |  |  | 			return def | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-11-24 23:22:25 +01:00
										 |  |  | 		if text == "y" || text == "yes" { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text == "n" || text == "no" { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-12-03 16:50:59 +02:00
										 |  |  | 		log.Error("Invalid input, expected 'y', 'yes', 'n', 'no' or empty") | 
					
						
							| 
									
										
										
										
											2018-11-24 23:22:25 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-03 16:50:59 +02:00
										 |  |  | // readURL reads a single line from stdin, trimming if from spaces and trying to | 
					
						
							|  |  |  | // interpret it as a URL (http, https or file). | 
					
						
							|  |  |  | func (w *wizard) readURL() *url.URL { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		uri, err := url.Parse(strings.TrimSpace(text)) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Error("Invalid input, expected URL", "err", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return uri | 
					
						
							| 
									
										
										
										
											2017-08-04 17:00:22 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // readInt reads a single line from stdin, trimming if from spaces, enforcing it | 
					
						
							|  |  |  | // to parse into an integer. | 
					
						
							|  |  |  | func (w *wizard) readInt() int { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text == "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		val, err := strconv.Atoi(strings.TrimSpace(text)) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Error("Invalid input, expected integer", "err", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return val | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing | 
					
						
							|  |  |  | // it to parse into an integer. If an empty line is entered, the default value is | 
					
						
							|  |  |  | // returned. | 
					
						
							|  |  |  | func (w *wizard) readDefaultInt(def int) int { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text == "" { | 
					
						
							|  |  |  | 			return def | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		val, err := strconv.Atoi(strings.TrimSpace(text)) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Error("Invalid input, expected integer", "err", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return val | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-10-04 12:15:58 +03:00
										 |  |  | // readDefaultBigInt reads a single line from stdin, trimming if from spaces, | 
					
						
							|  |  |  | // enforcing it to parse into a big integer. If an empty line is entered, the | 
					
						
							|  |  |  | // default value is returned. | 
					
						
							|  |  |  | func (w *wizard) readDefaultBigInt(def *big.Int) *big.Int { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text == "" { | 
					
						
							|  |  |  | 			return def | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		val, ok := new(big.Int).SetString(text, 0) | 
					
						
							|  |  |  | 		if !ok { | 
					
						
							|  |  |  | 			log.Error("Invalid input, expected big integer") | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return val | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-08-07 14:34:21 +03:00
										 |  |  | /* | 
					
						
							| 
									
										
										
										
											2017-05-13 03:03:56 +03:00
										 |  |  | // readFloat reads a single line from stdin, trimming if from spaces, enforcing it | 
					
						
							|  |  |  | // to parse into a float. | 
					
						
							|  |  |  | func (w *wizard) readFloat() float64 { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text == "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Error("Invalid input, expected float", "err", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return val | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-08-07 14:34:21 +03:00
										 |  |  | */ | 
					
						
							| 
									
										
										
										
											2017-05-13 03:03:56 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing | 
					
						
							|  |  |  | // it to parse into a float. If an empty line is entered, the default value is returned. | 
					
						
							|  |  |  | func (w *wizard) readDefaultFloat(def float64) float64 { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text == "" { | 
					
						
							|  |  |  | 			return def | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		val, err := strconv.ParseFloat(strings.TrimSpace(text), 64) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Error("Invalid input, expected float", "err", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return val | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | // readPassword reads a single line from stdin, trimming it from the trailing new | 
					
						
							|  |  |  | // line and returns it. The input will not be echoed. | 
					
						
							|  |  |  | func (w *wizard) readPassword() string { | 
					
						
							| 
									
										
										
										
											2017-08-04 17:00:22 +02:00
										 |  |  | 	fmt.Printf("> ") | 
					
						
							| 
									
										
										
										
											2017-11-10 18:06:45 +01:00
										 |  |  | 	text, err := terminal.ReadPassword(int(os.Stdin.Fd())) | 
					
						
							| 
									
										
										
										
											2017-08-04 17:00:22 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		log.Crit("Failed to read password", "err", err) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2017-08-04 17:00:22 +02:00
										 |  |  | 	fmt.Println() | 
					
						
							|  |  |  | 	return string(text) | 
					
						
							| 
									
										
										
										
											2017-04-11 02:25:53 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // readAddress reads a single line from stdin, trimming if from spaces and converts | 
					
						
							|  |  |  | // it to an Ethereum address. | 
					
						
							|  |  |  | func (w *wizard) readAddress() *common.Address { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		// Read the address from the user | 
					
						
							|  |  |  | 		fmt.Printf("> 0x") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text == "" { | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Make sure it looks ok and return it if so | 
					
						
							|  |  |  | 		if len(text) != 40 { | 
					
						
							|  |  |  | 			log.Error("Invalid address length, please retry") | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		bigaddr, _ := new(big.Int).SetString(text, 16) | 
					
						
							|  |  |  | 		address := common.BigToAddress(bigaddr) | 
					
						
							|  |  |  | 		return &address | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // readDefaultAddress reads a single line from stdin, trimming if from spaces and | 
					
						
							|  |  |  | // converts it to an Ethereum address. If an empty line is entered, the default | 
					
						
							|  |  |  | // value is returned. | 
					
						
							|  |  |  | func (w *wizard) readDefaultAddress(def common.Address) common.Address { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		// Read the address from the user | 
					
						
							|  |  |  | 		fmt.Printf("> 0x") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text == "" { | 
					
						
							|  |  |  | 			return def | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Make sure it looks ok and return it if so | 
					
						
							|  |  |  | 		if len(text) != 40 { | 
					
						
							|  |  |  | 			log.Error("Invalid address length, please retry") | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		bigaddr, _ := new(big.Int).SetString(text, 16) | 
					
						
							|  |  |  | 		return common.BigToAddress(bigaddr) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // readJSON reads a raw JSON message and returns it. | 
					
						
							|  |  |  | func (w *wizard) readJSON() string { | 
					
						
							|  |  |  | 	var blob json.RawMessage | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		if err := json.NewDecoder(w.in).Decode(&blob); err != nil { | 
					
						
							|  |  |  | 			log.Error("Invalid JSON, please try again", "err", err) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return string(blob) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | // readIPAddress reads a single line from stdin, trimming if from spaces and | 
					
						
							| 
									
										
										
										
											2017-10-10 12:35:09 +03:00
										 |  |  | // returning it if it's convertible to an IP address. The reason for keeping | 
					
						
							|  |  |  | // the user input format instead of returning a Go net.IP is to match with | 
					
						
							|  |  |  | // weird formats used by ethstats, which compares IPs textually, not by value. | 
					
						
							|  |  |  | func (w *wizard) readIPAddress() string { | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 	for { | 
					
						
							|  |  |  | 		// Read the IP address from the user | 
					
						
							|  |  |  | 		fmt.Printf("> ") | 
					
						
							|  |  |  | 		text, err := w.in.ReadString('\n') | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			log.Crit("Failed to read user input", "err", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if text = strings.TrimSpace(text); text == "" { | 
					
						
							| 
									
										
										
										
											2017-10-10 12:35:09 +03:00
										 |  |  | 			return "" | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		// Make sure it looks ok and return it if so | 
					
						
							| 
									
										
										
										
											2017-10-10 12:35:09 +03:00
										 |  |  | 		if ip := net.ParseIP(text); ip == nil { | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 			log.Error("Invalid IP address, please retry") | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2017-10-10 12:35:09 +03:00
										 |  |  | 		return text | 
					
						
							| 
									
										
										
										
											2017-08-18 11:23:56 +03:00
										 |  |  | 	} | 
					
						
							|  |  |  | } |