From 706a1e552c96bf75c60844c1dc28fc83778795fc Mon Sep 17 00:00:00 2001 From: Péter Szilágyi Date: Tue, 11 Apr 2017 02:25:53 +0300 Subject: cmd/puppeth: your Ethereum private network manager (#13854) --- cmd/puppeth/module.go | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 cmd/puppeth/module.go (limited to 'cmd/puppeth/module.go') diff --git a/cmd/puppeth/module.go b/cmd/puppeth/module.go new file mode 100644 index 000000000..b6a029a01 --- /dev/null +++ b/cmd/puppeth/module.go @@ -0,0 +1,152 @@ +// 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 . + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/ethereum/go-ethereum/log" +) + +var ( + // ErrServiceUnknown is returned when a service container doesn't exist. + ErrServiceUnknown = errors.New("service unknown") + + // ErrServiceOffline is returned when a service container exists, but it is not + // running. + ErrServiceOffline = errors.New("service offline") + + // ErrServiceUnreachable is returned when a service container is running, but + // seems to not respond to communication attempts. + ErrServiceUnreachable = errors.New("service unreachable") + + // ErrNotExposed is returned if a web-service doesn't have an exposed port, nor + // a reverse-proxy in front of it to forward requests. + ErrNotExposed = errors.New("service not exposed, nor proxied") +) + +// containerInfos is a heavily reduced version of the huge inspection dataset +// returned from docker inspect, parsed into a form easily usable by puppeth. +type containerInfos struct { + running bool // Flag whether the container is running currently + envvars map[string]string // Collection of environmental variables set on the container + portmap map[string]int // Port mapping from internal port/proto combos to host binds + volumes map[string]string // Volume mount points from container to host directories +} + +// inspectContainer runs docker inspect against a running container +func inspectContainer(client *sshClient, container string) (*containerInfos, error) { + // Check whether there's a container running for the service + out, err := client.Run(fmt.Sprintf("docker inspect %s", container)) + if err != nil { + return nil, ErrServiceUnknown + } + // If yes, extract various configuration options + type inspection struct { + State struct { + Running bool + } + Mounts []struct { + Source string + Destination string + } + Config struct { + Env []string + } + HostConfig struct { + PortBindings map[string][]map[string]string + } + } + var inspects []inspection + if err = json.Unmarshal(out, &inspects); err != nil { + return nil, err + } + inspect := inspects[0] + + // Infos retrieved, parse the above into something meaningful + infos := &containerInfos{ + running: inspect.State.Running, + envvars: make(map[string]string), + portmap: make(map[string]int), + volumes: make(map[string]string), + } + for _, envvar := range inspect.Config.Env { + if parts := strings.Split(envvar, "="); len(parts) == 2 { + infos.envvars[parts[0]] = parts[1] + } + } + for portname, details := range inspect.HostConfig.PortBindings { + if len(details) > 0 { + port, _ := strconv.Atoi(details[0]["HostPort"]) + infos.portmap[portname] = port + } + } + for _, mount := range inspect.Mounts { + infos.volumes[mount.Destination] = mount.Source + } + return infos, err +} + +// tearDown connects to a remote machine via SSH and terminates docker containers +// running with the specified name in the specified network. +func tearDown(client *sshClient, network string, service string, purge bool) ([]byte, error) { + // Tear down the running (or paused) container + out, err := client.Run(fmt.Sprintf("docker rm -f %s_%s_1", network, service)) + if err != nil { + return out, err + } + // If requested, purge the associated docker image too + if purge { + return client.Run(fmt.Sprintf("docker rmi %s/%s", network, service)) + } + return nil, nil +} + +// resolve retrieves the hostname a service is running on either by returning the +// actual server name and port, or preferably an nginx virtual host if available. +func resolve(client *sshClient, network string, service string, port int) (string, error) { + // Inspect the service to get various configurations from it + infos, err := inspectContainer(client, fmt.Sprintf("%s_%s_1", network, service)) + if err != nil { + return "", err + } + if !infos.running { + return "", ErrServiceOffline + } + // Container online, extract any environmental variables + if vhost := infos.envvars["VIRTUAL_HOST"]; vhost != "" { + return vhost, nil + } + return fmt.Sprintf("%s:%d", client.server, port), nil +} + +// checkPort tries to connect to a remote host on a given +func checkPort(host string, port int) error { + log.Trace("Verifying remote TCP connectivity", "server", host, "port", port) + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Second) + if err != nil { + return err + } + conn.Close() + return nil +} -- cgit