gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+incompatible/helpers/docker/machine_command.go (about) 1 package docker_helpers 2 3 import ( 4 "bufio" 5 "context" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/docker/machine/commands/mcndirs" 18 "github.com/sirupsen/logrus" 19 ) 20 21 type logWriter struct { 22 log func(args ...interface{}) 23 reader *bufio.Reader 24 } 25 26 func (l *logWriter) write(line string) { 27 line = strings.TrimRight(line, "\n") 28 29 if len(line) <= 0 { 30 return 31 } 32 33 l.log(line) 34 } 35 36 func (l *logWriter) watch() { 37 for { 38 line, err := l.reader.ReadString('\n') 39 if err == nil || err == io.EOF { 40 l.write(line) 41 if err == io.EOF { 42 return 43 } 44 } else { 45 if !strings.Contains(err.Error(), "bad file descriptor") { 46 logrus.WithError(err).Errorln("Problem while reading command output") 47 } 48 return 49 } 50 } 51 } 52 53 func newLogWriter(logFunction func(args ...interface{}), reader io.Reader) { 54 writer := &logWriter{ 55 log: logFunction, 56 reader: bufio.NewReader(reader), 57 } 58 59 go writer.watch() 60 } 61 62 func stdoutLogWriter(cmd *exec.Cmd, fields logrus.Fields) { 63 log := logrus.WithFields(fields) 64 reader, err := cmd.StdoutPipe() 65 66 if err == nil { 67 newLogWriter(log.Infoln, reader) 68 } 69 } 70 71 func stderrLogWriter(cmd *exec.Cmd, fields logrus.Fields) { 72 log := logrus.WithFields(fields) 73 reader, err := cmd.StderrPipe() 74 75 if err == nil { 76 newLogWriter(log.Errorln, reader) 77 } 78 } 79 80 type machineCommand struct { 81 cache map[string]machineInfo 82 cacheLock sync.RWMutex 83 } 84 85 type machineInfo struct { 86 expires time.Time 87 88 canConnect bool 89 } 90 91 func (m *machineCommand) Create(driver, name string, opts ...string) error { 92 args := []string{ 93 "create", 94 "--driver", driver, 95 } 96 for _, opt := range opts { 97 args = append(args, "--"+opt) 98 } 99 args = append(args, name) 100 101 cmd := exec.Command("docker-machine", args...) 102 cmd.Env = os.Environ() 103 104 fields := logrus.Fields{ 105 "operation": "create", 106 "driver": driver, 107 "name": name, 108 } 109 stdoutLogWriter(cmd, fields) 110 stderrLogWriter(cmd, fields) 111 112 logrus.Debugln("Executing", cmd.Path, cmd.Args) 113 return cmd.Run() 114 } 115 116 func (m *machineCommand) Provision(name string) error { 117 cmd := exec.Command("docker-machine", "provision", name) 118 cmd.Env = os.Environ() 119 120 fields := logrus.Fields{ 121 "operation": "provision", 122 "name": name, 123 } 124 stdoutLogWriter(cmd, fields) 125 stderrLogWriter(cmd, fields) 126 127 return cmd.Run() 128 } 129 130 func (m *machineCommand) Stop(name string, timeout time.Duration) error { 131 ctx, ctxCancelFn := context.WithTimeout(context.Background(), timeout) 132 defer ctxCancelFn() 133 134 cmd := exec.CommandContext(ctx, "docker-machine", "stop", name) 135 cmd.Env = os.Environ() 136 137 fields := logrus.Fields{ 138 "operation": "stop", 139 "name": name, 140 } 141 stdoutLogWriter(cmd, fields) 142 stderrLogWriter(cmd, fields) 143 144 return cmd.Run() 145 } 146 147 func (m *machineCommand) Remove(name string) error { 148 cmd := exec.Command("docker-machine", "rm", "-y", name) 149 cmd.Env = os.Environ() 150 151 fields := logrus.Fields{ 152 "operation": "remove", 153 "name": name, 154 } 155 stdoutLogWriter(cmd, fields) 156 stderrLogWriter(cmd, fields) 157 158 if err := cmd.Run(); err != nil { 159 return err 160 } 161 162 m.cacheLock.Lock() 163 delete(m.cache, name) 164 m.cacheLock.Unlock() 165 return nil 166 } 167 168 func (m *machineCommand) List() (hostNames []string, err error) { 169 dir, err := ioutil.ReadDir(mcndirs.GetMachineDir()) 170 if err != nil { 171 if os.IsNotExist(err) { 172 return nil, nil 173 } 174 return nil, err 175 } 176 177 for _, file := range dir { 178 if file.IsDir() && !strings.HasPrefix(file.Name(), ".") { 179 hostNames = append(hostNames, file.Name()) 180 } 181 } 182 183 return 184 } 185 186 func (m *machineCommand) get(args ...string) (out string, err error) { 187 // Execute docker-machine to fetch IP 188 cmd := exec.Command("docker-machine", args...) 189 cmd.Env = os.Environ() 190 data, err := cmd.Output() 191 if err != nil { 192 return 193 } 194 195 // Save the IP 196 out = strings.TrimSpace(string(data)) 197 if out == "" { 198 err = fmt.Errorf("failed to get %v", args) 199 } 200 return 201 } 202 203 func (m *machineCommand) IP(name string) (string, error) { 204 return m.get("ip", name) 205 } 206 207 func (m *machineCommand) URL(name string) (string, error) { 208 return m.get("url", name) 209 } 210 211 func (m *machineCommand) CertPath(name string) (string, error) { 212 return m.get("inspect", name, "-f", "{{.HostOptions.AuthOptions.StorePath}}") 213 } 214 215 func (m *machineCommand) Status(name string) (string, error) { 216 return m.get("status", name) 217 } 218 219 func (m *machineCommand) Exist(name string) bool { 220 configPath := filepath.Join(mcndirs.GetMachineDir(), name, "config.json") 221 _, err := os.Stat(configPath) 222 if err != nil { 223 return false 224 } 225 226 cmd := exec.Command("docker-machine", "inspect", name) 227 cmd.Env = os.Environ() 228 229 fields := logrus.Fields{ 230 "operation": "exists", 231 "name": name, 232 } 233 stderrLogWriter(cmd, fields) 234 235 return cmd.Run() == nil 236 } 237 238 func (m *machineCommand) CanConnect(name string, skipCache bool) bool { 239 m.cacheLock.RLock() 240 cachedInfo, ok := m.cache[name] 241 m.cacheLock.RUnlock() 242 243 if ok && !skipCache && time.Now().Before(cachedInfo.expires) { 244 return cachedInfo.canConnect 245 } 246 247 canConnect := m.canConnect(name) 248 if !canConnect { 249 return false // we only cache positive hits. Machines usually do not disconnect. 250 } 251 252 m.cacheLock.Lock() 253 m.cache[name] = machineInfo{ 254 expires: time.Now().Add(5 * time.Minute), 255 canConnect: true, 256 } 257 m.cacheLock.Unlock() 258 return true 259 } 260 261 func (m *machineCommand) canConnect(name string) bool { 262 // Execute docker-machine config which actively ask the machine if it is up and online 263 cmd := exec.Command("docker-machine", "config", name) 264 cmd.Env = os.Environ() 265 err := cmd.Run() 266 if err == nil { 267 return true 268 } 269 return false 270 } 271 272 func (m *machineCommand) Credentials(name string) (dc DockerCredentials, err error) { 273 if !m.CanConnect(name, true) { 274 err = errors.New("Can't connect") 275 return 276 } 277 278 dc.TLSVerify = true 279 dc.Host, err = m.URL(name) 280 if err == nil { 281 dc.CertPath, err = m.CertPath(name) 282 } 283 return 284 } 285 286 func NewMachineCommand() Machine { 287 return &machineCommand{ 288 cache: map[string]machineInfo{}, 289 } 290 }