2023-04-04 13:32:04 +00:00
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"context"
"fmt"
"os"
2023-06-05 13:11:23 +00:00
"path"
2023-07-01 01:27:54 +00:00
"path/filepath"
2023-06-05 13:11:23 +00:00
"runtime"
"strconv"
"strings"
2023-04-04 13:32:04 +00:00
2024-07-31 04:02:21 +00:00
"connectrpc.com/connect"
2023-04-04 13:32:04 +00:00
"github.com/mattn/go-isatty"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gitea.com/gitea/act_runner/internal/app/poll"
"gitea.com/gitea/act_runner/internal/app/run"
"gitea.com/gitea/act_runner/internal/pkg/client"
"gitea.com/gitea/act_runner/internal/pkg/config"
"gitea.com/gitea/act_runner/internal/pkg/envcheck"
"gitea.com/gitea/act_runner/internal/pkg/labels"
"gitea.com/gitea/act_runner/internal/pkg/ver"
)
func runDaemon ( ctx context . Context , configFile * string ) func ( cmd * cobra . Command , args [ ] string ) error {
return func ( cmd * cobra . Command , args [ ] string ) error {
cfg , err := config . LoadDefault ( * configFile )
if err != nil {
return fmt . Errorf ( "invalid configuration: %w" , err )
}
initLogging ( cfg )
2023-06-05 13:11:23 +00:00
log . Infoln ( "Starting runner daemon" )
2023-04-04 13:32:04 +00:00
reg , err := config . LoadRegistration ( cfg . Runner . File )
if os . IsNotExist ( err ) {
log . Error ( "registration file not found, please register the runner first" )
return err
} else if err != nil {
return fmt . Errorf ( "failed to load registration file: %w" , err )
}
2024-01-05 15:07:15 +00:00
cfg . Tune ( reg . Address )
2023-06-15 03:59:15 +00:00
lbls := reg . Labels
if len ( cfg . Runner . Labels ) > 0 {
lbls = cfg . Runner . Labels
}
2023-04-04 13:32:04 +00:00
ls := labels . Labels { }
2023-06-15 03:59:15 +00:00
for _ , l := range lbls {
2023-04-04 13:32:04 +00:00
label , err := labels . Parse ( l )
if err != nil {
log . WithError ( err ) . Warnf ( "ignored invalid label %q" , l )
continue
}
ls = append ( ls , label )
}
if len ( ls ) == 0 {
log . Warn ( "no labels configured, runner may not be able to pick up jobs" )
}
if ls . RequireDocker ( ) {
2023-06-30 04:00:04 +00:00
dockerSocketPath , err := getDockerSocketPath ( cfg . Container . DockerHost )
if err != nil {
return err
}
if err := envcheck . CheckIfDockerRunning ( ctx , dockerSocketPath ) ; err != nil {
2023-04-04 13:32:04 +00:00
return err
}
2023-06-30 04:00:04 +00:00
// if dockerSocketPath passes the check, override DOCKER_HOST with dockerSocketPath
os . Setenv ( "DOCKER_HOST" , dockerSocketPath )
// empty cfg.Container.DockerHost means act_runner need to find an available docker host automatically
// and assign the path to cfg.Container.DockerHost
if cfg . Container . DockerHost == "" {
cfg . Container . DockerHost = dockerSocketPath
}
// check the scheme, if the scheme is not npipe or unix
2023-07-13 01:10:54 +00:00
// set cfg.Container.DockerHost to "-" because it can't be mounted to the job container
2023-06-30 04:00:04 +00:00
if protoIndex := strings . Index ( cfg . Container . DockerHost , "://" ) ; protoIndex != - 1 {
scheme := cfg . Container . DockerHost [ : protoIndex ]
if ! strings . EqualFold ( scheme , "npipe" ) && ! strings . EqualFold ( scheme , "unix" ) {
cfg . Container . DockerHost = "-"
}
}
2023-04-04 13:32:04 +00:00
}
cli := client . New (
reg . Address ,
cfg . Runner . Insecure ,
reg . UUID ,
reg . Token ,
ver . Version ( ) ,
)
runner := run . NewRunner ( cfg , reg , cli )
2023-06-15 03:59:15 +00:00
// declare the labels of the runner before fetching tasks
resp , err := runner . Declare ( ctx , ls . Names ( ) )
if err != nil && connect . CodeOf ( err ) == connect . CodeUnimplemented {
// Gitea instance is older version. skip declare step.
2024-04-09 22:09:50 +00:00
log . Warn ( "Because the Forgejo instance is an old version, skipping declaring the labels and version." )
2023-06-15 03:59:15 +00:00
} else if err != nil {
log . WithError ( err ) . Error ( "fail to invoke Declare" )
return err
} else {
2024-04-09 22:09:50 +00:00
log . Infof ( "runner: %s, with version: %s, with labels: %v, declared successfully" ,
2023-06-15 03:59:15 +00:00
resp . Msg . Runner . Name , resp . Msg . Runner . Version , resp . Msg . Runner . Labels )
2024-04-09 22:09:50 +00:00
// if declared successfully, override the labels in the.runner file with valid labels in the config file (if specified)
2024-04-09 18:53:12 +00:00
runner . Update ( ctx , ls )
2023-06-15 03:59:15 +00:00
reg . Labels = ls . ToStrings ( )
if err := config . SaveRegistration ( cfg . Runner . File , reg ) ; err != nil {
return fmt . Errorf ( "failed to save runner config: %w" , err )
}
}
2023-04-04 13:32:04 +00:00
poller := poll . New ( cfg , cli , runner )
2024-06-06 09:40:31 +00:00
go poller . Poll ( )
2023-04-04 13:32:04 +00:00
2024-06-06 09:40:31 +00:00
<- ctx . Done ( )
log . Infof ( "runner: %s shutdown initiated, waiting [runner].shutdown_timeout=%s for running jobs to complete before shutting down" , resp . Msg . Runner . Name , cfg . Runner . ShutdownTimeout )
ctx , cancel := context . WithTimeout ( context . Background ( ) , cfg . Runner . ShutdownTimeout )
defer cancel ( )
err = poller . Shutdown ( ctx )
if err != nil {
log . Warnf ( "runner: %s cancelled in progress jobs during shutdown" , resp . Msg . Runner . Name )
}
2023-04-04 13:32:04 +00:00
return nil
}
}
// initLogging setup the global logrus logger.
func initLogging ( cfg * config . Config ) {
isTerm := isatty . IsTerminal ( os . Stdout . Fd ( ) )
2023-06-05 13:11:23 +00:00
format := & log . TextFormatter {
2023-04-04 13:32:04 +00:00
DisableColors : ! isTerm ,
FullTimestamp : true ,
2023-06-05 13:11:23 +00:00
}
log . SetFormatter ( format )
2023-04-04 13:32:04 +00:00
if l := cfg . Log . Level ; l != "" {
level , err := log . ParseLevel ( l )
if err != nil {
log . WithError ( err ) .
Errorf ( "invalid log level: %q" , l )
}
2023-06-05 13:11:23 +00:00
// debug level
if level == log . DebugLevel {
log . SetReportCaller ( true )
format . CallerPrettyfier = func ( f * runtime . Frame ) ( string , string ) {
// get function name
s := strings . Split ( f . Function , "." )
funcname := "[" + s [ len ( s ) - 1 ] + "]"
// get file name and line number
_ , filename := path . Split ( f . File )
filename = "[" + filename + ":" + strconv . Itoa ( f . Line ) + "]"
return funcname , filename
}
log . SetFormatter ( format )
}
2023-04-04 13:32:04 +00:00
if log . GetLevel ( ) != level {
log . Infof ( "log level changed to %v" , level )
log . SetLevel ( level )
}
}
}
2023-06-30 04:00:04 +00:00
2023-07-01 01:27:54 +00:00
var commonSocketPaths = [ ] string {
"/var/run/docker.sock" ,
2023-08-21 04:01:12 +00:00
"/run/podman/podman.sock" ,
2023-07-01 01:27:54 +00:00
"$HOME/.colima/docker.sock" ,
"$XDG_RUNTIME_DIR/docker.sock" ,
2023-08-21 04:01:12 +00:00
"$XDG_RUNTIME_DIR/podman/podman.sock" ,
2023-07-01 01:27:54 +00:00
` \\.\pipe\docker_engine ` ,
"$HOME/.docker/run/docker.sock" ,
}
2023-06-30 04:00:04 +00:00
func getDockerSocketPath ( configDockerHost string ) ( string , error ) {
// a `-` means don't mount the docker socket to job containers
if configDockerHost != "" && configDockerHost != "-" {
return configDockerHost , nil
}
socket , found := os . LookupEnv ( "DOCKER_HOST" )
if found {
return socket , nil
}
2023-07-01 01:27:54 +00:00
for _ , p := range commonSocketPaths {
if _ , err := os . Lstat ( os . ExpandEnv ( p ) ) ; err == nil {
if strings . HasPrefix ( p , ` \\.\ ` ) {
return "npipe://" + filepath . ToSlash ( os . ExpandEnv ( p ) ) , nil
}
return "unix://" + filepath . ToSlash ( os . ExpandEnv ( p ) ) , nil
}
}
2023-06-30 04:00:04 +00:00
return "" , fmt . Errorf ( "daemon Docker Engine socket not found and docker_host config was invalid" )
}