// Copyright 2019 Drone.IO Inc. All rights reserved. // Use of this source code is governed by the Polyform License // that can be found in the LICENSE file. package daemon import ( "context" "time" "github.com/drone-runners/drone-runner-docker/engine" "github.com/drone-runners/drone-runner-docker/engine/compiler" "github.com/drone-runners/drone-runner-docker/engine/linter" "github.com/drone-runners/drone-runner-docker/engine/resource" "github.com/drone-runners/drone-runner-docker/internal/match" "github.com/drone-runners/drone-runner-docker/runtime" "github.com/drone/runner-go/client" "github.com/drone/runner-go/handler/router" "github.com/drone/runner-go/logger" loghistory "github.com/drone/runner-go/logger/history" "github.com/drone/runner-go/pipeline/history" "github.com/drone/runner-go/pipeline/remote" "github.com/drone/runner-go/secret" "github.com/drone/runner-go/server" "github.com/drone/signal" "github.com/joho/godotenv" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" "gopkg.in/alecthomas/kingpin.v2" ) // empty context. var nocontext = context.Background() type daemonCommand struct { envfile string } func (c *daemonCommand) run(*kingpin.ParseContext) error { // load environment variables from file. godotenv.Load(c.envfile) // load the configuration from the environment config, err := fromEnviron() if err != nil { return err } // setup the global logrus logger. setupLogger(config) cli := client.New( config.Client.Address, config.Client.Secret, config.Client.SkipVerify, ) if config.Client.Dump { cli.Dumper = logger.StandardDumper( config.Client.DumpBody, ) } cli.Logger = logger.Logrus( logrus.NewEntry( logrus.StandardLogger(), ), ) engine, err := engine.New(config.Keypair.Public, config.Keypair.Private) if err != nil { return err } remote := remote.New(cli) tracer := history.New(remote) hook := loghistory.New() logrus.AddHook(hook) poller := &runtime.Poller{ Client: cli, Runner: &runtime.Runner{ Client: cli, Machine: config.Runner.Name, Reporter: tracer, Linter: linter.New(), Match: match.Func( config.Limit.Repos, config.Limit.Events, config.Limit.Trusted, ), Compiler: &compiler.Compiler{ Environ: nil, Labels: nil, Privileged: nil, Networks: nil, Volumes: nil, // Resources: nil, Registry: nil, Secret: secret.External( config.Secret.Endpoint, config.Secret.Token, config.Secret.SkipVerify, ), }, Execer: runtime.NewExecer( tracer, remote, engine, config.Runner.Procs, ), }, Filter: &client.Filter{ Kind: resource.Kind, Type: resource.Type, Labels: config.Runner.Labels, }, } ctx, cancel := context.WithCancel(nocontext) defer cancel() // listen for termination signals to gracefully shutdown // the runner daemon. ctx = signal.WithContextFunc(ctx, func() { println("received signal, terminating process") cancel() }) var g errgroup.Group server := server.Server{ Addr: config.Server.Port, Handler: router.New(tracer, hook, router.Config{ Username: config.Dashboard.Username, Password: config.Dashboard.Password, Realm: config.Dashboard.Realm, }), } logrus.WithField("addr", config.Server.Port). Infoln("starting the server") g.Go(func() error { return server.ListenAndServe(ctx) }) // Ping the server and block until a successful connection // to the server has been established. for { err := cli.Ping(ctx, config.Runner.Name) select { case <-ctx.Done(): return nil default: } if ctx.Err() != nil { break } if err != nil { logrus.WithError(err). Errorln("cannot ping the remote server") time.Sleep(time.Second) } else { logrus.Infoln("successfully pinged the remote server") break } } g.Go(func() error { logrus.WithField("capacity", config.Runner.Capacity). WithField("endpoint", config.Client.Address). WithField("kind", resource.Kind). WithField("type", resource.Type). Infoln("polling the remote server") poller.Poll(ctx, config.Runner.Capacity) return nil }) err = g.Wait() if err != nil { logrus.WithError(err). Errorln("shutting down the server") } return err } // helper function configures the global logger from // the loaded configuration. func setupLogger(config Config) { logger.Default = logger.Logrus( logrus.NewEntry( logrus.StandardLogger(), ), ) if config.Debug { logrus.SetLevel(logrus.DebugLevel) } if config.Trace { logrus.SetLevel(logrus.TraceLevel) } } // Register the daemon command. func Register(app *kingpin.Application) { c := new(daemonCommand) cmd := app.Command("daemon", "starts the runner daemon"). Default(). Action(c.run) cmd.Arg("envfile", "load the environment variable file"). Default(""). StringVar(&c.envfile) }