func (mr *MultiRunner) requestBuild(runner *common.RunnerConfig) *common.Build { if runner == nil { return nil } if !mr.isHealthy(runner) { return nil } count := mr.buildsForRunner(runner) limit := helpers.NonZeroOrDefault(runner.Limit, math.MaxInt32) if count >= limit { return nil } buildData, healthy := common.GetBuild(*runner) if healthy { mr.makeHealthy(runner) } else { mr.makeUnhealthy(runner) } if buildData == nil { return nil } mr.debugln("Received new build for", runner.ShortDescription(), "build", buildData.ID) newBuild := &common.Build{ GetBuildResponse: *buildData, Runner: runner, BuildAbort: mr.abortBuilds, } return newBuild }
func (mr *MultiRunner) makeUnhealthy(runner *common.RunnerConfig) { health := mr.getHealth(runner) health.failures++ if health.failures >= common.HealthyChecks { mr.errorln("Runner", runner.ShortDescription(), "is not healthy and will be disabled!") } }
func (mr *MultiRunner) isHealthy(runner *common.RunnerConfig) bool { health := mr.getHealth(runner) if health.failures < common.HealthyChecks { return true } if time.Since(health.lastCheck) > common.HealthCheckInterval*time.Second { mr.errorln("Runner", runner.ShortDescription(), "is not healthy, but will be checked!") health.failures = 0 health.lastCheck = time.Now() return true } return false }
func (mr *MultiRunner) getHealth(runner *common.RunnerConfig) *RunnerHealth { mr.healthyLock.Lock() defer mr.healthyLock.Unlock() if mr.healthy == nil { mr.healthy = map[string]*RunnerHealth{} } health := mr.healthy[runner.UniqueID()] if health == nil { health = &RunnerHealth{ lastCheck: time.Now(), } mr.healthy[runner.UniqueID()] = health } return health }
func (s *RegistrationContext) askDocker(runnerConfig *common.RunnerConfig) { dockerConfig := &common.DockerConfig{} dockerConfig.Image = s.ask("docker-image", "Please enter the Docker image (eg. ruby:2.1):") dockerConfig.Privileged = s.Bool("docker-privileged") if s.askForDockerService("mysql", dockerConfig) { runnerConfig.Environment = append(runnerConfig.Environment, "MYSQL_ALLOW_EMPTY_PASSWORD=1") } s.askForDockerService("postgres", dockerConfig) s.askForDockerService("redis", dockerConfig) s.askForDockerService("mongo", dockerConfig) dockerConfig.Volumes = append(dockerConfig.Volumes, "/cache") runnerConfig.Docker = dockerConfig }
func (s *RegistrationContext) askSSH(runnerConfig *common.RunnerConfig, serverless bool) { runnerConfig.SSH = &ssh.Config{} if !serverless { if host := s.ask("ssh-host", "Please enter the SSH server address (eg. my.server.com):"); host != "" { runnerConfig.SSH.Host = &host } if port := s.ask("ssh-port", "Please enter the SSH server port (eg. 22):", true); port != "" { runnerConfig.SSH.Port = &port } } if user := s.ask("ssh-user", "Please enter the SSH user (eg. root):"); user != "" { runnerConfig.SSH.User = &user } if password := s.ask("ssh-password", "Please enter the SSH password (eg. docker.io):"); password != "" { runnerConfig.SSH.Password = &password } }
func (s *RegistrationContext) askSSH(runnerConfig *common.RunnerConfig, serverless bool) { runnerConfig.SSH = &ssh.Config{} if !serverless { if host := s.ask("ssh-host", "Please enter the SSH server address (eg. my.server.com):"); host != "" { runnerConfig.SSH.Host = &host } if port := s.ask("ssh-port", "Please enter the SSH server port (eg. 22):", true); port != "" { runnerConfig.SSH.Port = &port } } if user := s.ask("ssh-user", "Please enter the SSH user (eg. root):"); user != "" { runnerConfig.SSH.User = &user } if password := s.ask("ssh-password", "Please enter the SSH password (eg. docker.io):", true); password != "" { runnerConfig.SSH.Password = &password } if identityFile := s.ask("ssh-identity-file", "Please enter path to SSH identity file (eg. /home/user/.ssh/id_rsa):", true); identityFile != "" { runnerConfig.SSH.IdentityFile = &identityFile } }
func (s *RegistrationContext) askParallels(runnerConfig *common.RunnerConfig) { parallelsConfig := &common.ParallelsConfig{} parallelsConfig.BaseName = s.ask("parallels-vm", "Please enter the Parallels VM (eg. my-vm):") runnerConfig.Parallels = parallelsConfig }
func runSingle(c *cli.Context) { buildsDir := c.String("builds-dir") shell := c.String("shell") config := common.NewConfig() runner := common.RunnerConfig{ URL: c.String("url"), Token: c.String("token"), Executor: c.String("executor"), BuildsDir: &buildsDir, Shell: &shell, } if len(runner.URL) == 0 { log.Fatalln("Missing URL") } if len(runner.Token) == 0 { log.Fatalln("Missing Token") } if len(runner.Executor) == 0 { log.Fatalln("Missing Executor") } go runServer(c.String("addr")) go runHerokuURL(c.String("heroku-url")) signals := make(chan os.Signal) signal.Notify(signals, os.Interrupt, syscall.SIGTERM) log.Println("Starting runner for", runner.URL, "with token", runner.ShortDescription(), "...") finished := false abortSignal := make(chan os.Signal) doneSignal := make(chan int, 1) go func() { interrupt := <-signals log.Warningln("Requested exit:", interrupt) finished = true go func() { for { abortSignal <- interrupt } }() select { case newSignal := <-signals: log.Fatalln("forced exit:", newSignal) case <-time.After(common.ShutdownTimeout * time.Second): log.Fatalln("shutdown timedout") case <-doneSignal: } }() for !finished { buildData, healthy := common.GetBuild(runner) if !healthy { log.Println("Runner is not healthy!") select { case <-time.After(common.NotHealthyCheckInterval * time.Second): case <-abortSignal: } continue } if buildData == nil { select { case <-time.After(common.CheckInterval * time.Second): case <-abortSignal: } continue } newBuild := common.Build{ GetBuildResponse: *buildData, Runner: &runner, BuildAbort: abortSignal, } newBuild.AssignID() newBuild.Run(config) } doneSignal <- 0 }