aboutsummaryrefslogblamecommitdiffstats
path: root/cmd/puppeth/wizard.go
blob: 92d7962a04ee33ad35968b877d26d5cd67cf8516 (plain) (tree)




































































































































































































































                                                                                          
// 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"
    "os"
    "path/filepath"
    "sort"
    "strconv"
    "strings"
    "syscall"

    "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
    genesis   *core.Genesis // Genesis block to cache for node deploys
    bootFull  []string      // Bootnodes to always connect to by full nodes
    bootLight []string      // Bootnodes to always connect to by light nodes
    ethstats  string        // Ethstats settings to cache for node deploys

    Servers []string `json:"servers,omitempty"`
}

// flush dumps the contents of config to disk.
func (c config) flush() {
    os.MkdirAll(filepath.Dir(c.path), 0755)

    sort.Strings(c.Servers)
    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
}

// 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 {
    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
        }
        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
    }
}

// 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 {
    for {
        fmt.Printf("> ")
        text, err := terminal.ReadPassword(int(syscall.Stdin))
        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)
    }
}