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