123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- // 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"
- "net"
- "os"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "sync"
- "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 {
- path string // File containing the configuration values
- bootnodes []string // Bootnodes to always connect to by all nodes
- ethstats string // Ethstats settings to cache for node deploys
- Genesis *core.Genesis `json:"genesis,omitempty"` // Genesis block to cache for node deploys
- 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
- }
- // 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
- in *bufio.Reader // Wrapper around stdin to allow reading user input
- lock sync.Mutex // Lock to protect configs during concurrent service discovery
- }
- // 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
- }
- }
- }
- // 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
- }
- // 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
- }
- }
- // 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
- }
- }
- /*
- // 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
- }
- }
- */
- // 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
- }
- }
- // 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 {
- fmt.Printf("> ")
- text, err := terminal.ReadPassword(int(os.Stdin.Fd()))
- if err != nil {
- log.Crit("Failed to read password", "err", err)
- }
- fmt.Println()
- return string(text)
- }
- // 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)
- }
- }
- // readIPAddress reads a single line from stdin, trimming if from spaces and
- // 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 {
- 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 == "" {
- return ""
- }
- // Make sure it looks ok and return it if so
- if ip := net.ParseIP(text); ip == nil {
- log.Error("Invalid IP address, please retry")
- continue
- }
- return text
- }
- }
|