single.go 3.92 KB
Newer Older
Kamil Trzciński's avatar
Kamil Trzciński committed
1 2 3 4
package commands

import (
	"os"
5 6
	"os/signal"
	"syscall"
Kamil Trzciński's avatar
Kamil Trzciński committed
7 8
	"time"

9
	log "github.com/sirupsen/logrus"
10
	"github.com/tevino/abool"
11 12
	"github.com/urfave/cli"

13 14
	"gitlab.com/gitlab-org/gitlab-runner/common"
	"gitlab.com/gitlab-org/gitlab-runner/network"
Kamil Trzciński's avatar
Kamil Trzciński committed
15 16
)

17 18
type RunSingleCommand struct {
	common.RunnerConfig
19 20 21 22 23 24 25
	network          common.Network
	WaitTimeout      int `long:"wait-timeout" description:"How long to wait in seconds before receiving the first job"`
	lastBuild        time.Time
	runForever       bool
	MaxBuilds        int `long:"max-builds" description:"How many builds to process before exiting"`
	finished         *abool.AtomicBool
	interruptSignals chan os.Signal
26 27
}

28 29 30 31 32
func waitForInterrupts(finished *abool.AtomicBool, abortSignal chan os.Signal, doneSignal chan int, interruptSignals chan os.Signal) {
	if interruptSignals == nil {
		interruptSignals = make(chan os.Signal)
	}
	signal.Notify(interruptSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
33

34
	interrupt := <-interruptSignals
35
	if finished != nil {
36
		finished.Set()
37 38 39 40 41
	}

	// request stop, but wait for force exit
	for interrupt == syscall.SIGQUIT {
		log.Warningln("Requested quit, waiting for builds to finish")
42
		interrupt = <-interruptSignals
43 44 45 46 47 48 49 50 51 52 53
	}

	log.Warningln("Requested exit:", interrupt)

	go func() {
		for {
			abortSignal <- interrupt
		}
	}()

	select {
54
	case newSignal := <-interruptSignals:
55 56 57 58 59 60 61
		log.Fatalln("forced exit:", newSignal)
	case <-time.After(common.ShutdownTimeout * time.Second):
		log.Fatalln("shutdown timedout")
	case <-doneSignal:
	}
}

62 63 64 65 66 67 68 69
// Things to do after a build
func (r *RunSingleCommand) postBuild() {
	if r.MaxBuilds > 0 {
		r.MaxBuilds--
	}
	r.lastBuild = time.Now()
}

70
func (r *RunSingleCommand) processBuild(data common.ExecutorData, abortSignal chan os.Signal) (err error) {
71
	jobData, healthy := r.network.RequestJob(r.RunnerConfig, nil)
72 73 74 75 76 77 78 79 80
	if !healthy {
		log.Println("Runner is not healthy!")
		select {
		case <-time.After(common.NotHealthyCheckInterval * time.Second):
		case <-abortSignal:
		}
		return
	}

81
	if jobData == nil {
82
		select {
83
		case <-time.After(common.CheckInterval):
84 85 86 87 88 89
		case <-abortSignal:
		}
		return
	}

	config := common.NewConfig()
90
	newBuild := common.NewBuild(*jobData, &r.RunnerConfig, abortSignal, data)
91

92 93 94
	jobCredentials := &common.JobCredentials{
		ID:    jobData.ID,
		Token: jobData.Token,
95
	}
96
	trace := r.network.ProcessJob(r.RunnerConfig, jobCredentials)
Tomasz Maczukin's avatar
Tomasz Maczukin committed
97
	defer trace.Fail(err, common.NoneFailure)
98 99

	err = newBuild.Run(config, trace)
100 101 102 103 104 105 106 107 108

	r.postBuild()

	return
}

func (r *RunSingleCommand) checkFinishedConditions() {
	if r.MaxBuilds < 1 && !r.runForever {
		log.Println("This runner has processed its build limit, so now exiting")
109
		r.finished.Set()
110 111 112
	}
	if r.WaitTimeout > 0 && int(time.Since(r.lastBuild).Seconds()) > r.WaitTimeout {
		log.Println("This runner has not received a job in", r.WaitTimeout, "seconds, so now exiting")
113
		r.finished.Set()
114
	}
115
	return
116 117
}

118 119
func (r *RunSingleCommand) Execute(c *cli.Context) {
	if len(r.URL) == 0 {
120 121
		log.Fatalln("Missing URL")
	}
122
	if len(r.Token) == 0 {
123 124
		log.Fatalln("Missing Token")
	}
125
	if len(r.Executor) == 0 {
126 127 128
		log.Fatalln("Missing Executor")
	}

129 130
	executorProvider := common.GetExecutor(r.Executor)
	if executorProvider == nil {
131
		log.Fatalln("Unknown executor:", r.Executor)
132 133
	}

134
	log.Println("Starting runner for", r.URL, "with token", r.ShortDescription(), "...")
Kamil Trzciński's avatar
Kamil Trzciński committed
135

136
	r.finished = abool.New()
137 138
	abortSignal := make(chan os.Signal)
	doneSignal := make(chan int, 1)
139 140
	r.runForever = r.MaxBuilds == 0

141
	go waitForInterrupts(r.finished, abortSignal, doneSignal, r.interruptSignals)
142

143
	r.lastBuild = time.Now()
144

145
	for !r.finished.IsSet() {
146 147 148 149 150 151
		data, err := executorProvider.Acquire(&r.RunnerConfig)
		if err != nil {
			log.Warningln("Executor update:", err)
		}

		r.processBuild(data, abortSignal)
152
		r.checkFinishedConditions()
153
		executorProvider.Release(&r.RunnerConfig, data)
Kamil Trzciński's avatar
Kamil Trzciński committed
154
	}
155 156

	doneSignal <- 0
Kamil Trzciński's avatar
Kamil Trzciński committed
157 158
}

159
func init() {
160
	common.RegisterCommand2("run-single", "start single runner", &RunSingleCommand{
161
		network: network.NewGitLabClient(),
162
	})
163
}