github.com/secure-build/gitlab-runner@v12.5.0+incompatible/commands/single.go (about)

     1  package commands
     2  
     3  import (
     4  	"os"
     5  	"os/signal"
     6  	"syscall"
     7  	"time"
     8  
     9  	"github.com/sirupsen/logrus"
    10  	"github.com/tevino/abool"
    11  	"github.com/urfave/cli"
    12  
    13  	"gitlab.com/gitlab-org/gitlab-runner/common"
    14  	"gitlab.com/gitlab-org/gitlab-runner/network"
    15  )
    16  
    17  type RunSingleCommand struct {
    18  	common.RunnerConfig
    19  	network          common.Network
    20  	WaitTimeout      int `long:"wait-timeout" description:"How long to wait in seconds before receiving the first job"`
    21  	lastBuild        time.Time
    22  	runForever       bool
    23  	MaxBuilds        int `long:"max-builds" description:"How many builds to process before exiting"`
    24  	finished         *abool.AtomicBool
    25  	interruptSignals chan os.Signal
    26  }
    27  
    28  func waitForInterrupts(finished *abool.AtomicBool, abortSignal chan os.Signal, doneSignal chan int, interruptSignals chan os.Signal) {
    29  	if interruptSignals == nil {
    30  		interruptSignals = make(chan os.Signal)
    31  	}
    32  	signal.Notify(interruptSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT)
    33  
    34  	interrupt := <-interruptSignals
    35  	if finished != nil {
    36  		finished.Set()
    37  	}
    38  
    39  	// request stop, but wait for force exit
    40  	for interrupt == syscall.SIGQUIT {
    41  		logrus.Warningln("Requested quit, waiting for builds to finish")
    42  		interrupt = <-interruptSignals
    43  	}
    44  
    45  	logrus.Warningln("Requested exit:", interrupt)
    46  
    47  	go func() {
    48  		for {
    49  			abortSignal <- interrupt
    50  		}
    51  	}()
    52  
    53  	select {
    54  	case newSignal := <-interruptSignals:
    55  		logrus.Fatalln("forced exit:", newSignal)
    56  	case <-time.After(common.ShutdownTimeout * time.Second):
    57  		logrus.Fatalln("shutdown timed out")
    58  	case <-doneSignal:
    59  	}
    60  }
    61  
    62  // Things to do after a build
    63  func (r *RunSingleCommand) postBuild() {
    64  	if r.MaxBuilds > 0 {
    65  		r.MaxBuilds--
    66  	}
    67  	r.lastBuild = time.Now()
    68  }
    69  
    70  func (r *RunSingleCommand) processBuild(data common.ExecutorData, abortSignal chan os.Signal) (err error) {
    71  	jobData, healthy := r.network.RequestJob(r.RunnerConfig, nil)
    72  	if !healthy {
    73  		logrus.Println("Runner is not healthy!")
    74  		select {
    75  		case <-time.After(common.NotHealthyCheckInterval * time.Second):
    76  		case <-abortSignal:
    77  		}
    78  		return
    79  	}
    80  
    81  	if jobData == nil {
    82  		select {
    83  		case <-time.After(common.CheckInterval):
    84  		case <-abortSignal:
    85  		}
    86  		return
    87  	}
    88  
    89  	config := common.NewConfig()
    90  	newBuild, err := common.NewBuild(*jobData, &r.RunnerConfig, abortSignal, data)
    91  	if err != nil {
    92  		return
    93  	}
    94  
    95  	jobCredentials := &common.JobCredentials{
    96  		ID:    jobData.ID,
    97  		Token: jobData.Token,
    98  	}
    99  	trace, err := r.network.ProcessJob(r.RunnerConfig, jobCredentials)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	defer trace.Fail(err, common.NoneFailure)
   105  
   106  	err = newBuild.Run(config, trace)
   107  
   108  	r.postBuild()
   109  
   110  	return
   111  }
   112  
   113  func (r *RunSingleCommand) checkFinishedConditions() {
   114  	if r.MaxBuilds < 1 && !r.runForever {
   115  		logrus.Println("This runner has processed its build limit, so now exiting")
   116  		r.finished.Set()
   117  	}
   118  	if r.WaitTimeout > 0 && int(time.Since(r.lastBuild).Seconds()) > r.WaitTimeout {
   119  		logrus.Println("This runner has not received a job in", r.WaitTimeout, "seconds, so now exiting")
   120  		r.finished.Set()
   121  	}
   122  	return
   123  }
   124  
   125  func (r *RunSingleCommand) Execute(c *cli.Context) {
   126  	if len(r.URL) == 0 {
   127  		logrus.Fatalln("Missing URL")
   128  	}
   129  	if len(r.Token) == 0 {
   130  		logrus.Fatalln("Missing Token")
   131  	}
   132  	if len(r.Executor) == 0 {
   133  		logrus.Fatalln("Missing Executor")
   134  	}
   135  
   136  	executorProvider := common.GetExecutor(r.Executor)
   137  	if executorProvider == nil {
   138  		logrus.Fatalln("Unknown executor:", r.Executor)
   139  	}
   140  
   141  	logrus.Println("Starting runner for", r.URL, "with token", r.ShortDescription(), "...")
   142  
   143  	r.finished = abool.New()
   144  	abortSignal := make(chan os.Signal)
   145  	doneSignal := make(chan int, 1)
   146  	r.runForever = r.MaxBuilds == 0
   147  
   148  	go waitForInterrupts(r.finished, abortSignal, doneSignal, r.interruptSignals)
   149  
   150  	r.lastBuild = time.Now()
   151  
   152  	for !r.finished.IsSet() {
   153  		data, err := executorProvider.Acquire(&r.RunnerConfig)
   154  		if err != nil {
   155  			logrus.Warningln("Executor update:", err)
   156  		}
   157  
   158  		pErr := r.processBuild(data, abortSignal)
   159  		if pErr != nil {
   160  			logrus.WithError(pErr).Error("Failed to process build")
   161  		}
   162  
   163  		r.checkFinishedConditions()
   164  		executorProvider.Release(&r.RunnerConfig, data)
   165  	}
   166  
   167  	doneSignal <- 0
   168  }
   169  
   170  func init() {
   171  	common.RegisterCommand2("run-single", "start single runner", &RunSingleCommand{
   172  		network: network.NewGitLabClient(),
   173  	})
   174  }