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