github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/cmd/puppeth/module.go (about)

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