github.com/tyler-smith/go-ethereum@v1.9.7/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  	"net/url"
    27  	"os"
    28  	"path/filepath"
    29  	"sort"
    30  	"strconv"
    31  	"strings"
    32  	"sync"
    33  
    34  	"github.com/ethereum/go-ethereum/common"
    35  	"github.com/ethereum/go-ethereum/core"
    36  	"github.com/ethereum/go-ethereum/log"
    37  	"golang.org/x/crypto/ssh/terminal"
    38  )
    39  
    40  // config contains all the configurations needed by puppeth that should be saved
    41  // between sessions.
    42  type config struct {
    43  	path      string   // File containing the configuration values
    44  	bootnodes []string // Bootnodes to always connect to by all nodes
    45  	ethstats  string   // Ethstats settings to cache for node deploys
    46  
    47  	Genesis *core.Genesis     `json:"genesis,omitempty"` // Genesis block to cache for node deploys
    48  	Servers map[string][]byte `json:"servers,omitempty"`
    49  }
    50  
    51  // servers retrieves an alphabetically sorted list of servers.
    52  func (c config) servers() []string {
    53  	servers := make([]string, 0, len(c.Servers))
    54  	for server := range c.Servers {
    55  		servers = append(servers, server)
    56  	}
    57  	sort.Strings(servers)
    58  
    59  	return servers
    60  }
    61  
    62  // flush dumps the contents of config to disk.
    63  func (c config) flush() {
    64  	os.MkdirAll(filepath.Dir(c.path), 0755)
    65  
    66  	out, _ := json.MarshalIndent(c, "", "  ")
    67  	if err := ioutil.WriteFile(c.path, out, 0644); err != nil {
    68  		log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)
    69  	}
    70  }
    71  
    72  type wizard struct {
    73  	network string // Network name to manage
    74  	conf    config // Configurations from previous runs
    75  
    76  	servers  map[string]*sshClient // SSH connections to servers to administer
    77  	services map[string][]string   // Ethereum services known to be running on servers
    78  
    79  	in   *bufio.Reader // Wrapper around stdin to allow reading user input
    80  	lock sync.Mutex    // Lock to protect configs during concurrent service discovery
    81  }
    82  
    83  // read reads a single line from stdin, trimming if from spaces.
    84  func (w *wizard) read() string {
    85  	fmt.Printf("> ")
    86  	text, err := w.in.ReadString('\n')
    87  	if err != nil {
    88  		log.Crit("Failed to read user input", "err", err)
    89  	}
    90  	return strings.TrimSpace(text)
    91  }
    92  
    93  // readString reads a single line from stdin, trimming if from spaces, enforcing
    94  // non-emptyness.
    95  func (w *wizard) readString() string {
    96  	for {
    97  		fmt.Printf("> ")
    98  		text, err := w.in.ReadString('\n')
    99  		if err != nil {
   100  			log.Crit("Failed to read user input", "err", err)
   101  		}
   102  		if text = strings.TrimSpace(text); text != "" {
   103  			return text
   104  		}
   105  	}
   106  }
   107  
   108  // readDefaultString reads a single line from stdin, trimming if from spaces. If
   109  // an empty line is entered, the default value is returned.
   110  func (w *wizard) readDefaultString(def string) string {
   111  	fmt.Printf("> ")
   112  	text, err := w.in.ReadString('\n')
   113  	if err != nil {
   114  		log.Crit("Failed to read user input", "err", err)
   115  	}
   116  	if text = strings.TrimSpace(text); text != "" {
   117  		return text
   118  	}
   119  	return def
   120  }
   121  
   122  // readDefaultYesNo reads a single line from stdin, trimming if from spaces and
   123  // interpreting it as a 'yes' or a 'no'. If an empty line is entered, the default
   124  // value is returned.
   125  func (w *wizard) readDefaultYesNo(def bool) bool {
   126  	for {
   127  		fmt.Printf("> ")
   128  		text, err := w.in.ReadString('\n')
   129  		if err != nil {
   130  			log.Crit("Failed to read user input", "err", err)
   131  		}
   132  		if text = strings.ToLower(strings.TrimSpace(text)); text == "" {
   133  			return def
   134  		}
   135  		if text == "y" || text == "yes" {
   136  			return true
   137  		}
   138  		if text == "n" || text == "no" {
   139  			return false
   140  		}
   141  		log.Error("Invalid input, expected 'y', 'yes', 'n', 'no' or empty")
   142  	}
   143  }
   144  
   145  // readURL reads a single line from stdin, trimming if from spaces and trying to
   146  // interpret it as a URL (http, https or file).
   147  func (w *wizard) readURL() *url.URL {
   148  	for {
   149  		fmt.Printf("> ")
   150  		text, err := w.in.ReadString('\n')
   151  		if err != nil {
   152  			log.Crit("Failed to read user input", "err", err)
   153  		}
   154  		uri, err := url.Parse(strings.TrimSpace(text))
   155  		if err != nil {
   156  			log.Error("Invalid input, expected URL", "err", err)
   157  			continue
   158  		}
   159  		return uri
   160  	}
   161  }
   162  
   163  // readInt reads a single line from stdin, trimming if from spaces, enforcing it
   164  // to parse into an integer.
   165  func (w *wizard) readInt() int {
   166  	for {
   167  		fmt.Printf("> ")
   168  		text, err := w.in.ReadString('\n')
   169  		if err != nil {
   170  			log.Crit("Failed to read user input", "err", err)
   171  		}
   172  		if text = strings.TrimSpace(text); text == "" {
   173  			continue
   174  		}
   175  		val, err := strconv.Atoi(strings.TrimSpace(text))
   176  		if err != nil {
   177  			log.Error("Invalid input, expected integer", "err", err)
   178  			continue
   179  		}
   180  		return val
   181  	}
   182  }
   183  
   184  // readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing
   185  // it to parse into an integer. If an empty line is entered, the default value is
   186  // returned.
   187  func (w *wizard) readDefaultInt(def int) int {
   188  	for {
   189  		fmt.Printf("> ")
   190  		text, err := w.in.ReadString('\n')
   191  		if err != nil {
   192  			log.Crit("Failed to read user input", "err", err)
   193  		}
   194  		if text = strings.TrimSpace(text); text == "" {
   195  			return def
   196  		}
   197  		val, err := strconv.Atoi(strings.TrimSpace(text))
   198  		if err != nil {
   199  			log.Error("Invalid input, expected integer", "err", err)
   200  			continue
   201  		}
   202  		return val
   203  	}
   204  }
   205  
   206  // readDefaultBigInt reads a single line from stdin, trimming if from spaces,
   207  // enforcing it to parse into a big integer. If an empty line is entered, the
   208  // default value is returned.
   209  func (w *wizard) readDefaultBigInt(def *big.Int) *big.Int {
   210  	for {
   211  		fmt.Printf("> ")
   212  		text, err := w.in.ReadString('\n')
   213  		if err != nil {
   214  			log.Crit("Failed to read user input", "err", err)
   215  		}
   216  		if text = strings.TrimSpace(text); text == "" {
   217  			return def
   218  		}
   219  		val, ok := new(big.Int).SetString(text, 0)
   220  		if !ok {
   221  			log.Error("Invalid input, expected big integer")
   222  			continue
   223  		}
   224  		return val
   225  	}
   226  }
   227  
   228  /*
   229  // readFloat reads a single line from stdin, trimming if from spaces, enforcing it
   230  // to parse into a float.
   231  func (w *wizard) readFloat() float64 {
   232  	for {
   233  		fmt.Printf("> ")
   234  		text, err := w.in.ReadString('\n')
   235  		if err != nil {
   236  			log.Crit("Failed to read user input", "err", err)
   237  		}
   238  		if text = strings.TrimSpace(text); text == "" {
   239  			continue
   240  		}
   241  		val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
   242  		if err != nil {
   243  			log.Error("Invalid input, expected float", "err", err)
   244  			continue
   245  		}
   246  		return val
   247  	}
   248  }
   249  */
   250  
   251  // readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing
   252  // it to parse into a float. If an empty line is entered, the default value is returned.
   253  func (w *wizard) readDefaultFloat(def float64) float64 {
   254  	for {
   255  		fmt.Printf("> ")
   256  		text, err := w.in.ReadString('\n')
   257  		if err != nil {
   258  			log.Crit("Failed to read user input", "err", err)
   259  		}
   260  		if text = strings.TrimSpace(text); text == "" {
   261  			return def
   262  		}
   263  		val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
   264  		if err != nil {
   265  			log.Error("Invalid input, expected float", "err", err)
   266  			continue
   267  		}
   268  		return val
   269  	}
   270  }
   271  
   272  // readPassword reads a single line from stdin, trimming it from the trailing new
   273  // line and returns it. The input will not be echoed.
   274  func (w *wizard) readPassword() string {
   275  	fmt.Printf("> ")
   276  	text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
   277  	if err != nil {
   278  		log.Crit("Failed to read password", "err", err)
   279  	}
   280  	fmt.Println()
   281  	return string(text)
   282  }
   283  
   284  // readAddress reads a single line from stdin, trimming if from spaces and converts
   285  // it to an Ethereum address.
   286  func (w *wizard) readAddress() *common.Address {
   287  	for {
   288  		// Read the address from the user
   289  		fmt.Printf("> 0x")
   290  		text, err := w.in.ReadString('\n')
   291  		if err != nil {
   292  			log.Crit("Failed to read user input", "err", err)
   293  		}
   294  		if text = strings.TrimSpace(text); text == "" {
   295  			return nil
   296  		}
   297  		// Make sure it looks ok and return it if so
   298  		if len(text) != 40 {
   299  			log.Error("Invalid address length, please retry")
   300  			continue
   301  		}
   302  		bigaddr, _ := new(big.Int).SetString(text, 16)
   303  		address := common.BigToAddress(bigaddr)
   304  		return &address
   305  	}
   306  }
   307  
   308  // readDefaultAddress reads a single line from stdin, trimming if from spaces and
   309  // converts it to an Ethereum address. If an empty line is entered, the default
   310  // value is returned.
   311  func (w *wizard) readDefaultAddress(def common.Address) common.Address {
   312  	for {
   313  		// Read the address from the user
   314  		fmt.Printf("> 0x")
   315  		text, err := w.in.ReadString('\n')
   316  		if err != nil {
   317  			log.Crit("Failed to read user input", "err", err)
   318  		}
   319  		if text = strings.TrimSpace(text); text == "" {
   320  			return def
   321  		}
   322  		// Make sure it looks ok and return it if so
   323  		if len(text) != 40 {
   324  			log.Error("Invalid address length, please retry")
   325  			continue
   326  		}
   327  		bigaddr, _ := new(big.Int).SetString(text, 16)
   328  		return common.BigToAddress(bigaddr)
   329  	}
   330  }
   331  
   332  // readJSON reads a raw JSON message and returns it.
   333  func (w *wizard) readJSON() string {
   334  	var blob json.RawMessage
   335  
   336  	for {
   337  		fmt.Printf("> ")
   338  		if err := json.NewDecoder(w.in).Decode(&blob); err != nil {
   339  			log.Error("Invalid JSON, please try again", "err", err)
   340  			continue
   341  		}
   342  		return string(blob)
   343  	}
   344  }
   345  
   346  // readIPAddress reads a single line from stdin, trimming if from spaces and
   347  // returning it if it's convertible to an IP address. The reason for keeping
   348  // the user input format instead of returning a Go net.IP is to match with
   349  // weird formats used by ethstats, which compares IPs textually, not by value.
   350  func (w *wizard) readIPAddress() string {
   351  	for {
   352  		// Read the IP address from the user
   353  		fmt.Printf("> ")
   354  		text, err := w.in.ReadString('\n')
   355  		if err != nil {
   356  			log.Crit("Failed to read user input", "err", err)
   357  		}
   358  		if text = strings.TrimSpace(text); text == "" {
   359  			return ""
   360  		}
   361  		// Make sure it looks ok and return it if so
   362  		if ip := net.ParseIP(text); ip == nil {
   363  			log.Error("Invalid IP address, please retry")
   364  			continue
   365  		}
   366  		return text
   367  	}
   368  }