github.com/ssube/gitlab-ci-multi-runner@v1.2.1-0.20160607142738-b8d1285632e6/executors/virtualbox/executor_virtualbox.go (about)

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