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 }