github.com/luckypickle/go-ethereum-vet@v1.14.2/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/luckypickle/go-ethereum-vet/common"
    34  	"github.com/luckypickle/go-ethereum-vet/core"
    35  	"github.com/luckypickle/go-ethereum-vet/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  }