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 }