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  }