github.com/zhiqiangxu/go-ethereum@v1.9.16-0.20210824055606-be91cfdebc48/cmd/puppeth/module.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  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"net"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/zhiqiangxu/go-ethereum/log"
    29  )
    30  
    31  var (
    32  	// ErrServiceUnknown is returned when a service container doesn't exist.
    33  	ErrServiceUnknown = errors.New("service unknown")
    34  
    35  	// ErrServiceOffline is returned when a service container exists, but it is not
    36  	// running.
    37  	ErrServiceOffline = errors.New("service offline")
    38  
    39  	// ErrServiceUnreachable is returned when a service container is running, but
    40  	// seems to not respond to communication attempts.
    41  	ErrServiceUnreachable = errors.New("service unreachable")
    42  
    43  	// ErrNotExposed is returned if a web-service doesn't have an exposed port, nor
    44  	// a reverse-proxy in front of it to forward requests.
    45  	ErrNotExposed = errors.New("service not exposed, nor proxied")
    46  )
    47  
    48  // containerInfos is a heavily reduced version of the huge inspection dataset
    49  // returned from docker inspect, parsed into a form easily usable by puppeth.
    50  type containerInfos struct {
    51  	running bool              // Flag whether the container is running currently
    52  	envvars map[string]string // Collection of environmental variables set on the container
    53  	portmap map[string]int    // Port mapping from internal port/proto combos to host binds
    54  	volumes map[string]string // Volume mount points from container to host directories
    55  }
    56  
    57  // inspectContainer runs docker inspect against a running container
    58  func inspectContainer(client *sshClient, container string) (*containerInfos, error) {
    59  	// Check whether there's a container running for the service
    60  	out, err := client.Run(fmt.Sprintf("docker inspect %s", container))
    61  	if err != nil {
    62  		return nil, ErrServiceUnknown
    63  	}
    64  	// If yes, extract various configuration options
    65  	type inspection struct {
    66  		State struct {
    67  			Running bool
    68  		}
    69  		Mounts []struct {
    70  			Source      string
    71  			Destination string
    72  		}
    73  		Config struct {
    74  			Env []string
    75  		}
    76  		HostConfig struct {
    77  			PortBindings map[string][]map[string]string
    78  		}
    79  	}
    80  	var inspects []inspection
    81  	if err = json.Unmarshal(out, &inspects); err != nil {
    82  		return nil, err
    83  	}
    84  	inspect := inspects[0]
    85  
    86  	// Infos retrieved, parse the above into something meaningful
    87  	infos := &containerInfos{
    88  		running: inspect.State.Running,
    89  		envvars: make(map[string]string),
    90  		portmap: make(map[string]int),
    91  		volumes: make(map[string]string),
    92  	}
    93  	for _, envvar := range inspect.Config.Env {
    94  		if parts := strings.Split(envvar, "="); len(parts) == 2 {
    95  			infos.envvars[parts[0]] = parts[1]
    96  		}
    97  	}
    98  	for portname, details := range inspect.HostConfig.PortBindings {
    99  		if len(details) > 0 {
   100  			port, _ := strconv.Atoi(details[0]["HostPort"])
   101  			infos.portmap[portname] = port
   102  		}
   103  	}
   104  	for _, mount := range inspect.Mounts {
   105  		infos.volumes[mount.Destination] = mount.Source
   106  	}
   107  	return infos, err
   108  }
   109  
   110  // tearDown connects to a remote machine via SSH and terminates docker containers
   111  // running with the specified name in the specified network.
   112  func tearDown(client *sshClient, network string, service string, purge bool) ([]byte, error) {
   113  	// Tear down the running (or paused) container
   114  	out, err := client.Run(fmt.Sprintf("docker rm -f %s_%s_1", network, service))
   115  	if err != nil {
   116  		return out, err
   117  	}
   118  	// If requested, purge the associated docker image too
   119  	if purge {
   120  		return client.Run(fmt.Sprintf("docker rmi %s/%s", network, service))
   121  	}
   122  	return nil, nil
   123  }
   124  
   125  // resolve retrieves the hostname a service is running on either by returning the
   126  // actual server name and port, or preferably an nginx virtual host if available.
   127  func resolve(client *sshClient, network string, service string, port int) (string, error) {
   128  	// Inspect the service to get various configurations from it
   129  	infos, err := inspectContainer(client, fmt.Sprintf("%s_%s_1", network, service))
   130  	if err != nil {
   131  		return "", err
   132  	}
   133  	if !infos.running {
   134  		return "", ErrServiceOffline
   135  	}
   136  	// Container online, extract any environmental variables
   137  	if vhost := infos.envvars["VIRTUAL_HOST"]; vhost != "" {
   138  		return vhost, nil
   139  	}
   140  	return fmt.Sprintf("%s:%d", client.server, port), nil
   141  }
   142  
   143  // checkPort tries to connect to a remote host on a given
   144  func checkPort(host string, port int) error {
   145  	log.Trace("Verifying remote TCP connectivity", "server", host, "port", port)
   146  	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Second)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	conn.Close()
   151  	return nil
   152  }