2022-11-15 13:46:29 +01:00
package cmd
import (
2022-11-15 14:28:07 +01:00
"bufio"
2022-11-15 13:46:29 +01:00
"context"
2022-11-17 12:43:26 +01:00
"fmt"
2022-11-15 14:28:07 +01:00
"os"
"os/signal"
"runtime"
"strings"
2022-11-15 15:42:41 +01:00
"time"
2022-11-22 05:22:19 +01:00
pingv1 "code.gitea.io/bots-proto-go/ping/v1"
2022-11-15 15:42:41 +01:00
"gitea.com/gitea/act_runner/client"
"gitea.com/gitea/act_runner/config"
"gitea.com/gitea/act_runner/register"
2022-11-24 04:55:52 +01:00
2022-11-15 15:42:41 +01:00
"github.com/bufbuild/connect-go"
"github.com/joho/godotenv"
2022-11-15 14:28:07 +01:00
"github.com/mattn/go-isatty"
2022-11-15 13:46:29 +01:00
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// runRegister registers a runner to the server
2022-11-15 15:42:41 +01:00
func runRegister ( ctx context . Context , regArgs * registerArgs , envFile string ) func ( * cobra . Command , [ ] string ) error {
2022-11-15 13:46:29 +01:00
return func ( cmd * cobra . Command , args [ ] string ) error {
2022-11-15 14:28:07 +01:00
log . SetReportCaller ( false )
isTerm := isatty . IsTerminal ( os . Stdout . Fd ( ) )
log . SetFormatter ( & log . TextFormatter {
DisableColors : ! isTerm ,
DisableTimestamp : true ,
} )
2022-11-15 15:42:41 +01:00
log . SetLevel ( log . DebugLevel )
2022-11-15 14:28:07 +01:00
log . Infof ( "Registering runner, arch=%s, os=%s, version=%s." ,
runtime . GOARCH , runtime . GOOS , version )
// runner always needs root permission
if os . Getuid ( ) != 0 {
// TODO: use a better way to check root permission
log . Warnf ( "Runner in user-mode." )
}
2022-11-17 12:43:26 +01:00
if regArgs . NoInteractive {
if err := registerNoInteractive ( envFile , regArgs ) ; err != nil {
return err
2022-11-15 14:28:07 +01:00
}
2022-11-17 12:43:26 +01:00
} else {
go func ( ) {
if err := registerInteractive ( envFile ) ; err != nil {
// log.Errorln(err)
os . Exit ( 2 )
return
}
os . Exit ( 0 )
} ( )
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt )
<- c
}
2022-11-15 14:28:07 +01:00
2022-11-15 13:46:29 +01:00
return nil
}
}
// registerArgs represents the arguments for register command
type registerArgs struct {
NoInteractive bool
InstanceAddr string
Token string
2022-11-17 12:43:26 +01:00
RunnerName string
Labels string
2022-11-15 13:46:29 +01:00
}
2022-11-15 14:28:07 +01:00
type registerStage int8
const (
2022-11-15 15:42:41 +01:00
StageUnknown registerStage = - 1
StageOverwriteLocalConfig registerStage = iota + 1
StageInputInstance
2022-11-15 14:28:07 +01:00
StageInputToken
StageInputRunnerName
StageInputCustomLabels
StageWaitingForRegistration
2022-11-15 15:42:41 +01:00
StageExit
2022-11-15 14:28:07 +01:00
)
2022-12-02 05:01:50 +01:00
var (
defaultLabels = [ ] string {
"ubuntu-latest:docker://node:16-bullseye" ,
"ubuntu-22.04:docker://node:16-bullseye" , // There's no node:16-bookworm yet
"ubuntu-20.04:docker://node:16-bullseye" ,
"ubuntu-18.04:docker://node:16-buster" ,
}
)
2022-11-15 14:28:07 +01:00
type registerInputs struct {
InstanceAddr string
Token string
RunnerName string
CustomLabels [ ] string
}
2022-11-17 12:43:26 +01:00
func ( r * registerInputs ) validate ( ) error {
if r . InstanceAddr == "" {
return fmt . Errorf ( "instance address is empty" )
}
if r . Token == "" {
return fmt . Errorf ( "token is empty" )
}
2022-11-21 13:12:25 +01:00
if len ( r . CustomLabels ) > 0 {
return validateLabels ( r . CustomLabels )
}
return nil
}
func validateLabels ( labels [ ] string ) error {
for _ , label := range labels {
values := strings . SplitN ( label , ":" , 2 )
if len ( values ) != 2 {
return fmt . Errorf ( "Invalid label: %s" , label )
}
// TODO: validate value format, like docker://node:16-buster
}
2022-11-17 12:43:26 +01:00
return nil
}
2022-11-15 14:28:07 +01:00
func ( r * registerInputs ) assignToNext ( stage registerStage , value string ) registerStage {
// must set instance address and token.
// if empty, keep current stage.
if stage == StageInputInstance || stage == StageInputToken {
if value == "" {
return stage
}
}
// set hostname for runner name if empty
if stage == StageInputRunnerName && value == "" {
value , _ = os . Hostname ( )
}
switch stage {
2022-11-15 15:42:41 +01:00
case StageOverwriteLocalConfig :
if value == "Y" || value == "y" {
return StageInputInstance
}
return StageExit
2022-11-15 14:28:07 +01:00
case StageInputInstance :
r . InstanceAddr = value
return StageInputToken
case StageInputToken :
r . Token = value
return StageInputRunnerName
case StageInputRunnerName :
r . RunnerName = value
return StageInputCustomLabels
case StageInputCustomLabels :
2022-12-02 05:01:50 +01:00
r . CustomLabels = defaultLabels
2022-11-24 04:55:52 +01:00
if value != "" {
r . CustomLabels = strings . Split ( value , "," )
2022-11-21 13:12:25 +01:00
}
2022-11-24 04:55:52 +01:00
2022-11-21 13:12:25 +01:00
if validateLabels ( r . CustomLabels ) != nil {
2022-11-24 04:55:52 +01:00
log . Infoln ( "Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster)" )
2022-11-21 13:12:25 +01:00
return StageInputCustomLabels
}
2022-11-15 14:28:07 +01:00
return StageWaitingForRegistration
}
return StageUnknown
}
2022-11-15 15:42:41 +01:00
func registerInteractive ( envFile string ) error {
2022-11-15 14:28:07 +01:00
var (
reader = bufio . NewReader ( os . Stdin )
stage = StageInputInstance
inputs = new ( registerInputs )
)
2022-11-15 15:42:41 +01:00
// check if overwrite local config
_ = godotenv . Load ( envFile )
cfg , _ := config . FromEnviron ( )
2022-11-24 04:55:52 +01:00
if f , err := os . Stat ( cfg . Runner . File ) ; err == nil && ! f . IsDir ( ) {
2022-11-15 15:42:41 +01:00
stage = StageOverwriteLocalConfig
}
2022-11-15 14:28:07 +01:00
for {
printStageHelp ( stage )
2022-11-15 15:42:41 +01:00
2022-11-15 14:28:07 +01:00
cmdString , err := reader . ReadString ( '\n' )
if err != nil {
return err
}
stage = inputs . assignToNext ( stage , strings . TrimSpace ( cmdString ) )
if stage == StageWaitingForRegistration {
log . Infof ( "Registering runner, name=%s, instance=%s, labels=%v." , inputs . RunnerName , inputs . InstanceAddr , inputs . CustomLabels )
2022-11-15 15:42:41 +01:00
if err := doRegister ( & cfg , inputs ) ; err != nil {
log . Errorf ( "Failed to register runner: %v" , err )
} else {
log . Infof ( "Runner registered successfully." )
}
return nil
}
if stage == StageExit {
2022-11-15 14:28:07 +01:00
return nil
}
if stage <= StageUnknown {
log . Errorf ( "Invalid input, please re-run act command." )
return nil
}
}
}
func printStageHelp ( stage registerStage ) {
switch stage {
2022-11-15 15:42:41 +01:00
case StageOverwriteLocalConfig :
log . Infoln ( "Runner is already registered, overwrite local config? [Y/n]" )
2022-11-15 14:28:07 +01:00
case StageInputInstance :
log . Infoln ( "Enter the Gitea instance URL (for example, https://gitea.com/):" )
case StageInputToken :
log . Infoln ( "Enter the runner token:" )
case StageInputRunnerName :
hostname , _ := os . Hostname ( )
log . Infof ( "Enter the runner name (if set empty, use hostname:%s ):\n" , hostname )
case StageInputCustomLabels :
2022-11-24 04:55:52 +01:00
log . Infoln ( "Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-20.04:docker://node:16-bullseye,ubuntu-18.04:docker://node:16-buster):" )
2022-11-15 14:28:07 +01:00
case StageWaitingForRegistration :
log . Infoln ( "Waiting for registration..." )
}
}
2022-11-15 15:42:41 +01:00
2022-11-17 12:43:26 +01:00
func registerNoInteractive ( envFile string , regArgs * registerArgs ) error {
_ = godotenv . Load ( envFile )
cfg , _ := config . FromEnviron ( )
inputs := & registerInputs {
InstanceAddr : regArgs . InstanceAddr ,
Token : regArgs . Token ,
RunnerName : regArgs . RunnerName ,
2022-12-02 05:01:50 +01:00
CustomLabels : defaultLabels ,
2022-11-21 13:12:25 +01:00
}
regArgs . Labels = strings . TrimSpace ( regArgs . Labels )
if regArgs . Labels != "" {
inputs . CustomLabels = strings . Split ( regArgs . Labels , "," )
2022-11-17 12:43:26 +01:00
}
if inputs . RunnerName == "" {
inputs . RunnerName , _ = os . Hostname ( )
log . Infof ( "Runner name is empty, use hostname '%s'." , inputs . RunnerName )
}
if err := inputs . validate ( ) ; err != nil {
log . WithError ( err ) . Errorf ( "Invalid input, please re-run act command." )
return nil
}
if err := doRegister ( & cfg , inputs ) ; err != nil {
log . Errorf ( "Failed to register runner: %v" , err )
2022-11-24 17:54:32 +01:00
return nil
2022-11-17 12:43:26 +01:00
}
log . Infof ( "Runner registered successfully." )
return nil
}
2022-11-15 15:42:41 +01:00
func doRegister ( cfg * config . Config , inputs * registerInputs ) error {
ctx := context . Background ( )
// initial http client
cli := client . New (
inputs . InstanceAddr ,
2022-11-29 03:35:59 +01:00
"" , "" ,
2022-11-15 15:42:41 +01:00
)
for {
_ , err := cli . Ping ( ctx , connect . NewRequest ( & pingv1 . PingRequest {
Data : inputs . RunnerName ,
} ) )
select {
case <- ctx . Done ( ) :
return nil
default :
}
if ctx . Err ( ) != nil {
break
}
if err != nil {
log . WithError ( err ) .
Errorln ( "Cannot ping the Gitea instance server" )
// TODO: if ping failed, retry or exit
time . Sleep ( time . Second )
} else {
log . Debugln ( "Successfully pinged the Gitea instance server" )
break
}
}
cfg . Runner . Name = inputs . RunnerName
cfg . Runner . Token = inputs . Token
2022-11-22 13:56:14 +01:00
cfg . Runner . Labels = inputs . CustomLabels
2022-11-24 04:55:52 +01:00
_ , err := register . New ( cli ) . Register ( ctx , cfg . Runner )
2022-11-24 17:54:32 +01:00
return err
2022-11-15 15:42:41 +01:00
}