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  }