github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/cmd/puppeth/module.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar library is free software: you can redistribute it and/or modify
     6  //  it under the terms of the GNU Lesser General Public License as published by
     7  //  the Free Software Foundation, either version 3 of the License, or
     8  //  (at your option) any later version.
     9  //
    10  //  The go-aigar library is distributed in the hope that it will be useful,
    11  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  //  GNU Lesser General Public License for more details.
    14  //
    15  //  You should have received a copy of the GNU Lesser General Public License
    16  //  along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package main
    19  
    20  import (
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"net"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/AigarNetwork/aigar/log"
    30  )
    31  
    32  var (
    33  	// ErrServiceUnknown is returned when a service container doesn't exist.
    34  	ErrServiceUnknown = errors.New("service unknown")
    35  
    36  	// ErrServiceOffline is returned when a service container exists, but it is not
    37  	// running.
    38  	ErrServiceOffline = errors.New("service offline")
    39  
    40  	// ErrServiceUnreachable is returned when a service container is running, but
    41  	// seems to not respond to communication attempts.
    42  	ErrServiceUnreachable = errors.New("service unreachable")
    43  
    44  	// ErrNotExposed is returned if a web-service doesn't have an exposed port, nor
    45  	// a reverse-proxy in front of it to forward requests.
    46  	ErrNotExposed = errors.New("service not exposed, nor proxied")
    47  )
    48  
    49  // containerInfos is a heavily reduced version of the huge inspection dataset
    50  // returned from docker inspect, parsed into a form easily usable by puppeth.
    51  type containerInfos struct {
    52  	running bool              // Flag whether the container is running currently
    53  	envvars map[string]string // Collection of environmental variables set on the container
    54  	portmap map[string]int    // Port mapping from internal port/proto combos to host binds
    55  	volumes map[string]string // Volume mount points from container to host directories
    56  }
    57  
    58  // inspectContainer runs docker inspect against a running container
    59  func inspectContainer(client *sshClient, container string) (*containerInfos, error) {
    60  	// Check whether there's a container running for the service
    61  	out, err := client.Run(fmt.Sprintf("docker inspect %s", container))
    62  	if err != nil {
    63  		return nil, ErrServiceUnknown
    64  	}
    65  	// If yes, extract various configuration options
    66  	type inspection struct {
    67  		State struct {
    68  			Running bool
    69  		}
    70  		Mounts []struct {
    71  			Source      string
    72  			Destination string
    73  		}
    74  		Config struct {
    75  			Env []string
    76  		}
    77  		HostConfig struct {
    78  			PortBindings map[string][]map[string]string
    79  		}
    80  	}
    81  	var inspects []inspection
    82  	if err = json.Unmarshal(out, &inspects); err != nil {
    83  		return nil, err
    84  	}
    85  	inspect := inspects[0]
    86  
    87  	// Infos retrieved, parse the above into something meaningful
    88  	infos := &containerInfos{
    89  		running: inspect.State.Running,
    90  		envvars: make(map[string]string),
    91  		portmap: make(map[string]int),
    92  		volumes: make(map[string]string),
    93  	}
    94  	for _, envvar := range inspect.Config.Env {
    95  		if parts := strings.Split(envvar, "="); len(parts) == 2 {
    96  			infos.envvars[parts[0]] = parts[1]
    97  		}
    98  	}
    99  	for portname, details := range inspect.HostConfig.PortBindings {
   100  		if len(details) > 0 {
   101  			port, _ := strconv.Atoi(details[0]["HostPort"])
   102  			infos.portmap[portname] = port
   103  		}
   104  	}
   105  	for _, mount := range inspect.Mounts {
   106  		infos.volumes[mount.Destination] = mount.Source
   107  	}
   108  	return infos, err
   109  }
   110  
   111  // tearDown connects to a remote machine via SSH and terminates docker containers
   112  // running with the specified name in the specified network.
   113  func tearDown(client *sshClient, network string, service string, purge bool) ([]byte, error) {
   114  	// Tear down the running (or paused) container
   115  	out, err := client.Run(fmt.Sprintf("docker rm -f %s_%s_1", network, service))
   116  	if err != nil {
   117  		return out, err
   118  	}
   119  	// If requested, purge the associated docker image too
   120  	if purge {
   121  		return client.Run(fmt.Sprintf("docker rmi %s/%s", network, service))
   122  	}
   123  	return nil, nil
   124  }
   125  
   126  // resolve retrieves the hostname a service is running on either by returning the
   127  // actual server name and port, or preferably an nginx virtual host if available.
   128  func resolve(client *sshClient, network string, service string, port int) (string, error) {
   129  	// Inspect the service to get various configurations from it
   130  	infos, err := inspectContainer(client, fmt.Sprintf("%s_%s_1", network, service))
   131  	if err != nil {
   132  		return "", err
   133  	}
   134  	if !infos.running {
   135  		return "", ErrServiceOffline
   136  	}
   137  	// Container online, extract any environmental variables
   138  	if vhost := infos.envvars["VIRTUAL_HOST"]; vhost != "" {
   139  		return vhost, nil
   140  	}
   141  	return fmt.Sprintf("%s:%d", client.server, port), nil
   142  }
   143  
   144  // checkPort tries to connect to a remote host on a given
   145  func checkPort(host string, port int) error {
   146  	log.Trace("Verifying remote TCP connectivity", "server", host, "port", port)
   147  	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Second)
   148  	if err != nil {
   149  		return err
   150  	}
   151  	conn.Close()
   152  	return nil
   153  }