github.com/ssube/gitlab-ci-multi-runner@v1.2.1-0.20160607142738-b8d1285632e6/executors/parallels/executor_parallels.go (about) 1 package parallels 2 3 import ( 4 "errors" 5 "fmt" 6 "os/exec" 7 "time" 8 9 "gitlab.com/gitlab-org/gitlab-ci-multi-runner/common" 10 "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors" 11 "gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers/ssh" 12 13 prl "gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers/parallels" 14 ) 15 16 type executor struct { 17 executors.AbstractExecutor 18 cmd *exec.Cmd 19 vmName string 20 sshCommand ssh.Client 21 provisioned bool 22 ipAddress string 23 machineVerified bool 24 } 25 26 func (s *executor) waitForIPAddress(vmName string, seconds int) (string, error) { 27 var lastError error 28 29 if s.ipAddress != "" { 30 return s.ipAddress, nil 31 } 32 33 s.Debugln("Looking for MAC address...") 34 macAddr, err := prl.Mac(vmName) 35 if err != nil { 36 return "", err 37 } 38 39 s.Debugln("Requesting IP address...") 40 for i := 0; i < seconds; i++ { 41 ipAddr, err := prl.IPAddress(macAddr) 42 if err == nil { 43 s.Debugln("IP address found", ipAddr, "...") 44 s.ipAddress = ipAddr 45 return ipAddr, nil 46 } 47 lastError = err 48 time.Sleep(time.Second) 49 } 50 return "", lastError 51 } 52 53 func (s *executor) verifyMachine(vmName string) error { 54 if s.machineVerified { 55 return nil 56 } 57 58 ipAddr, err := s.waitForIPAddress(vmName, 120) 59 if err != nil { 60 return err 61 } 62 63 // Create SSH command 64 sshCommand := ssh.Client{ 65 Config: *s.Config.SSH, 66 Stdout: s.BuildLog, 67 Stderr: s.BuildLog, 68 ConnectRetries: 30, 69 } 70 sshCommand.Host = ipAddr 71 72 s.Debugln("Connecting to SSH...") 73 err = sshCommand.Connect() 74 if err != nil { 75 return err 76 } 77 defer sshCommand.Cleanup() 78 err = sshCommand.Run(ssh.Command{Command: []string{"exit"}}) 79 if err != nil { 80 return err 81 } 82 s.machineVerified = true 83 return nil 84 } 85 86 func (s *executor) restoreFromSnapshot() error { 87 s.Debugln("Requesting default snapshot for VM...") 88 snapshot, err := prl.GetDefaultSnapshot(s.vmName) 89 if err != nil { 90 return err 91 } 92 93 s.Debugln("Reverting VM to snapshot", snapshot, "...") 94 err = prl.RevertToSnapshot(s.vmName, snapshot) 95 if err != nil { 96 return err 97 } 98 99 return nil 100 } 101 102 func (s *executor) createVM() error { 103 baseImage := s.Config.Parallels.BaseName 104 if baseImage == "" { 105 return errors.New("Missing Image setting from Parallels config") 106 } 107 108 templateName := s.Config.Parallels.TemplateName 109 if templateName == "" { 110 templateName = baseImage + "-template" 111 } 112 113 // remove invalid template (removed?) 114 templateStatus, _ := prl.Status(templateName) 115 if templateStatus == prl.Invalid { 116 prl.Unregister(templateName) 117 } 118 119 if !prl.Exist(templateName) { 120 s.Debugln("Creating template from VM", baseImage, "...") 121 err := prl.CreateTemplate(baseImage, templateName) 122 if err != nil { 123 return err 124 } 125 } 126 127 s.Debugln("Creating runner from VM template...") 128 err := prl.CreateOsVM(s.vmName, templateName) 129 if err != nil { 130 return err 131 } 132 133 s.Debugln("Bootstraping VM...") 134 err = prl.Start(s.vmName) 135 if err != nil { 136 return err 137 } 138 139 s.Debugln("Waiting for VM to start...") 140 err = prl.TryExec(s.vmName, 120, "exit", "0") 141 if err != nil { 142 return err 143 } 144 145 s.Debugln("Waiting for VM to become responsive...") 146 err = s.verifyMachine(s.vmName) 147 if err != nil { 148 return err 149 } 150 return nil 151 } 152 153 func (s *executor) Prepare(globalConfig *common.Config, config *common.RunnerConfig, build *common.Build) error { 154 err := s.AbstractExecutor.Prepare(globalConfig, config, build) 155 if err != nil { 156 return err 157 } 158 159 if s.BuildScript.PassFile { 160 return errors.New("Parallels doesn't support shells that require script file") 161 } 162 163 if s.Config.SSH == nil { 164 return errors.New("Missing SSH configuration") 165 } 166 167 if s.Config.Parallels == nil { 168 return errors.New("Missing Parallels configuration") 169 } 170 171 if s.Config.Parallels.BaseName == "" { 172 return errors.New("Missing BaseName setting from Parallels config") 173 } 174 175 version, err := prl.Version() 176 if err != nil { 177 return err 178 } 179 180 s.Println("Using Parallels", version, "executor...") 181 182 // remove invalid VM (removed?) 183 vmStatus, _ := prl.Status(s.vmName) 184 if vmStatus == prl.Invalid { 185 prl.Unregister(s.vmName) 186 } 187 188 if s.Config.Parallels.DisableSnapshots { 189 s.vmName = s.Config.Parallels.BaseName + "-" + s.Build.ProjectUniqueName() 190 if prl.Exist(s.vmName) { 191 s.Debugln("Deleting old VM...") 192 prl.Stop(s.vmName) 193 prl.Delete(s.vmName) 194 prl.Unregister(s.vmName) 195 } 196 } else { 197 s.vmName = fmt.Sprintf("%s-runner-%s-concurrent-%d", 198 s.Config.Parallels.BaseName, 199 s.Build.Runner.ShortDescription(), 200 s.Build.RunnerID) 201 } 202 203 if prl.Exist(s.vmName) { 204 s.Println("Restoring VM from snapshot...") 205 err := s.restoreFromSnapshot() 206 if err != nil { 207 s.Println("Previous VM failed. Deleting, because", err) 208 prl.Stop(s.vmName) 209 prl.Delete(s.vmName) 210 prl.Unregister(s.vmName) 211 } 212 } 213 214 if !prl.Exist(s.vmName) { 215 s.Println("Creating new VM...") 216 err := s.createVM() 217 if err != nil { 218 return err 219 } 220 221 if !s.Config.Parallels.DisableSnapshots { 222 s.Println("Creating default snapshot...") 223 err = prl.CreateSnapshot(s.vmName, "Started") 224 if err != nil { 225 return err 226 } 227 } 228 } 229 230 s.Debugln("Checking VM status...") 231 status, err := prl.Status(s.vmName) 232 if err != nil { 233 return err 234 } 235 236 // Start VM if stopped 237 if status == prl.Stopped || status == prl.Suspended { 238 s.Println("Starting VM...") 239 err := prl.Start(s.vmName) 240 if err != nil { 241 return err 242 } 243 } 244 245 if status != prl.Running { 246 s.Debugln("Waiting for VM to run...") 247 err = prl.WaitForStatus(s.vmName, prl.Running, 60) 248 if err != nil { 249 return err 250 } 251 } 252 253 s.Println("Waiting VM to become responsive...") 254 err = s.verifyMachine(s.vmName) 255 if err != nil { 256 return err 257 } 258 259 s.provisioned = true 260 261 s.Debugln("Updating VM date...") 262 err = prl.TryExec(s.vmName, 20, "sudo", "ntpdate", "-u", "time.apple.com") 263 if err != nil { 264 return err 265 } 266 267 ipAddr, err := s.waitForIPAddress(s.vmName, 60) 268 if err != nil { 269 return err 270 } 271 272 s.Debugln("Starting SSH command...") 273 s.sshCommand = ssh.Client{ 274 Config: *s.Config.SSH, 275 Stdout: s.BuildLog, 276 Stderr: s.BuildLog, 277 } 278 s.sshCommand.Host = ipAddr 279 280 s.Debugln("Connecting to SSH server...") 281 err = s.sshCommand.Connect() 282 if err != nil { 283 return err 284 } 285 return nil 286 } 287 288 func (s *executor) Run(cmd common.ExecutorCommand) error { 289 return s.sshCommand.Run(ssh.Command{ 290 Environment: s.BuildScript.Environment, 291 Command: s.BuildScript.GetCommandWithArguments(), 292 Stdin: cmd.Script, 293 Abort: cmd.Abort, 294 }) 295 } 296 297 func (s *executor) Cleanup() { 298 s.sshCommand.Cleanup() 299 300 if s.vmName != "" { 301 prl.Kill(s.vmName) 302 303 if s.Config.Parallels.DisableSnapshots || !s.provisioned { 304 prl.Delete(s.vmName) 305 } 306 } 307 308 s.AbstractExecutor.Cleanup() 309 } 310 311 func init() { 312 options := executors.ExecutorOptions{ 313 DefaultBuildsDir: "builds", 314 SharedBuildsDir: false, 315 Shell: common.ShellScriptInfo{ 316 Shell: "bash", 317 Type: common.LoginShell, 318 }, 319 ShowHostname: true, 320 } 321 322 creator := func() common.Executor { 323 return &executor{ 324 AbstractExecutor: executors.AbstractExecutor{ 325 ExecutorOptions: options, 326 }, 327 } 328 } 329 330 featuresUpdater := func(features *common.FeaturesInfo) { 331 features.Variables = true 332 } 333 334 common.RegisterExecutor("parallels", executors.DefaultExecutorProvider{ 335 Creator: creator, 336 FeaturesUpdater: featuresUpdater, 337 }) 338 }