github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/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-runner/common" 10 "gitlab.com/gitlab-org/gitlab-runner/executors" 11 "gitlab.com/gitlab-org/gitlab-runner/helpers/ssh" 12 13 prl "gitlab.com/gitlab-org/gitlab-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.Trace, 67 Stderr: s.Trace, 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(s.Context, 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 // TODO: integration tests do fail on this due 140 // Unable to open new session in this virtual machine. 141 // Make sure the latest version of Parallels Tools is installed in this virtual machine and it has finished bootingg 142 s.Debugln("Waiting for VM to start...") 143 err = prl.TryExec(s.vmName, 120, "exit", "0") 144 if err != nil { 145 return err 146 } 147 148 s.Debugln("Waiting for VM to become responsive...") 149 err = s.verifyMachine(s.vmName) 150 if err != nil { 151 return err 152 } 153 return nil 154 } 155 156 func (s *executor) Prepare(options common.ExecutorPrepareOptions) error { 157 err := s.AbstractExecutor.Prepare(options) 158 if err != nil { 159 return err 160 } 161 162 if s.BuildShell.PassFile { 163 return errors.New("Parallels doesn't support shells that require script file") 164 } 165 166 if s.Config.SSH == nil { 167 return errors.New("Missing SSH configuration") 168 } 169 170 if s.Config.Parallels == nil { 171 return errors.New("Missing Parallels configuration") 172 } 173 174 if s.Config.Parallels.BaseName == "" { 175 return errors.New("Missing BaseName setting from Parallels config") 176 } 177 178 version, err := prl.Version() 179 if err != nil { 180 return err 181 } 182 183 s.Println("Using Parallels", version, "executor...") 184 185 // remove invalid VM (removed?) 186 vmStatus, _ := prl.Status(s.vmName) 187 if vmStatus == prl.Invalid { 188 prl.Unregister(s.vmName) 189 } 190 191 if s.Config.Parallels.DisableSnapshots { 192 s.vmName = s.Config.Parallels.BaseName + "-" + s.Build.ProjectUniqueName() 193 if prl.Exist(s.vmName) { 194 s.Debugln("Deleting old VM...") 195 prl.Kill(s.vmName) 196 prl.Delete(s.vmName) 197 prl.Unregister(s.vmName) 198 } 199 } else { 200 s.vmName = fmt.Sprintf("%s-runner-%s-concurrent-%d", 201 s.Config.Parallels.BaseName, 202 s.Build.Runner.ShortDescription(), 203 s.Build.RunnerID) 204 } 205 206 if prl.Exist(s.vmName) { 207 s.Println("Restoring VM from snapshot...") 208 err := s.restoreFromSnapshot() 209 if err != nil { 210 s.Println("Previous VM failed. Deleting, because", err) 211 prl.Kill(s.vmName) 212 prl.Delete(s.vmName) 213 prl.Unregister(s.vmName) 214 } 215 } 216 217 if !prl.Exist(s.vmName) { 218 s.Println("Creating new VM...") 219 err := s.createVM() 220 if err != nil { 221 return err 222 } 223 224 if !s.Config.Parallels.DisableSnapshots { 225 s.Println("Creating default snapshot...") 226 err = prl.CreateSnapshot(s.vmName, "Started") 227 if err != nil { 228 return err 229 } 230 } 231 } 232 233 s.Debugln("Checking VM status...") 234 status, err := prl.Status(s.vmName) 235 if err != nil { 236 return err 237 } 238 239 // Start VM if stopped 240 if status == prl.Stopped || status == prl.Suspended { 241 s.Println("Starting VM...") 242 err := prl.Start(s.vmName) 243 if err != nil { 244 return err 245 } 246 } 247 248 if status != prl.Running { 249 s.Debugln("Waiting for VM to run...") 250 err = prl.WaitForStatus(s.vmName, prl.Running, 60) 251 if err != nil { 252 return err 253 } 254 } 255 256 s.Println("Waiting VM to become responsive...") 257 err = s.verifyMachine(s.vmName) 258 if err != nil { 259 return err 260 } 261 262 s.provisioned = true 263 264 // TODO: integration tests do fail on this due 265 // Unable to open new session in this virtual machine. 266 // Make sure the latest version of Parallels Tools is installed in this virtual machine and it has finished booting 267 s.Debugln("Updating VM date...") 268 err = prl.TryExec(s.vmName, 20, "sudo", "ntpdate", "-u", "time.apple.com") 269 if err != nil { 270 return err 271 } 272 273 ipAddr, err := s.waitForIPAddress(s.vmName, 60) 274 if err != nil { 275 return err 276 } 277 278 s.Debugln("Starting SSH command...") 279 s.sshCommand = ssh.Client{ 280 Config: *s.Config.SSH, 281 Stdout: s.Trace, 282 Stderr: s.Trace, 283 } 284 s.sshCommand.Host = ipAddr 285 286 s.Debugln("Connecting to SSH server...") 287 err = s.sshCommand.Connect() 288 if err != nil { 289 return err 290 } 291 return nil 292 } 293 294 func (s *executor) Run(cmd common.ExecutorCommand) error { 295 err := s.sshCommand.Run(cmd.Context, ssh.Command{ 296 Environment: s.BuildShell.Environment, 297 Command: s.BuildShell.GetCommandWithArguments(), 298 Stdin: cmd.Script, 299 }) 300 if _, ok := err.(*ssh.ExitError); ok { 301 err = &common.BuildError{Inner: err} 302 } 303 return err 304 } 305 306 func (s *executor) Cleanup() { 307 s.sshCommand.Cleanup() 308 309 if s.vmName != "" { 310 prl.Kill(s.vmName) 311 312 if s.Config.Parallels.DisableSnapshots || !s.provisioned { 313 prl.Delete(s.vmName) 314 } 315 } 316 317 s.AbstractExecutor.Cleanup() 318 } 319 320 func init() { 321 options := executors.ExecutorOptions{ 322 DefaultBuildsDir: "builds", 323 SharedBuildsDir: false, 324 Shell: common.ShellScriptInfo{ 325 Shell: "bash", 326 Type: common.LoginShell, 327 RunnerCommand: "gitlab-runner", 328 }, 329 ShowHostname: true, 330 } 331 332 creator := func() common.Executor { 333 return &executor{ 334 AbstractExecutor: executors.AbstractExecutor{ 335 ExecutorOptions: options, 336 }, 337 } 338 } 339 340 featuresUpdater := func(features *common.FeaturesInfo) { 341 features.Variables = true 342 } 343 344 common.RegisterExecutor("parallels", executors.DefaultExecutorProvider{ 345 Creator: creator, 346 FeaturesUpdater: featuresUpdater, 347 }) 348 }