github.com/SmartMeshFoundation/Spectrum@v0.0.0-20220621030607-452a266fee1e/cmd/puppeth/module.go (about) 1 // Copyright 2017 The Spectrum Authors 2 // This file is part of Spectrum. 3 // 4 // Spectrum 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 // Spectrum 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 Spectrum. 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/SmartMeshFoundation/Spectrum/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 }