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