2023-02-28 18:44:46 +08:00
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
2022-11-15 20:46:29 +08:00
package cmd
import (
2022-11-15 21:28:07 +08:00
"bufio"
2022-11-15 20:46:29 +08:00
"context"
2022-11-17 19:43:26 +08:00
"fmt"
2022-11-15 21:28:07 +08:00
"os"
"os/signal"
2023-03-23 20:48:33 +08:00
goruntime "runtime"
2022-11-15 21:28:07 +08:00
"strings"
2022-11-15 22:42:41 +08:00
"time"
2022-12-06 16:37:38 +08:00
pingv1 "code.gitea.io/actions-proto-go/ping/v1"
2023-04-02 22:41:48 +08:00
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
2022-11-15 22:42:41 +08:00
"github.com/bufbuild/connect-go"
2022-11-15 21:28:07 +08:00
"github.com/mattn/go-isatty"
2022-11-15 20:46:29 +08:00
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
2023-03-23 20:48:33 +08:00
2023-04-04 21:32:04 +08:00
"gitea.com/gitea/act_runner/internal/pkg/client"
"gitea.com/gitea/act_runner/internal/pkg/config"
"gitea.com/gitea/act_runner/internal/pkg/labels"
"gitea.com/gitea/act_runner/internal/pkg/ver"
2022-11-15 20:46:29 +08:00
)
// runRegister registers a runner to the server
2023-04-02 22:41:48 +08:00
func runRegister ( ctx context . Context , regArgs * registerArgs , configFile * string ) func ( * cobra . Command , [ ] string ) error {
2022-11-15 20:46:29 +08:00
return func ( cmd * cobra . Command , args [ ] string ) error {
2022-11-15 21:28:07 +08:00
log . SetReportCaller ( false )
isTerm := isatty . IsTerminal ( os . Stdout . Fd ( ) )
log . SetFormatter ( & log . TextFormatter {
DisableColors : ! isTerm ,
DisableTimestamp : true ,
} )
2022-11-15 22:42:41 +08:00
log . SetLevel ( log . DebugLevel )
2022-11-15 21:28:07 +08:00
log . Infof ( "Registering runner, arch=%s, os=%s, version=%s." ,
2023-04-04 21:32:04 +08:00
goruntime . GOARCH , goruntime . GOOS , ver . Version ( ) )
2022-11-15 21:28:07 +08:00
// 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 19:43:26 +08:00
if regArgs . NoInteractive {
2023-04-02 22:41:48 +08:00
if err := registerNoInteractive ( * configFile , regArgs ) ; err != nil {
2022-11-17 19:43:26 +08:00
return err
2022-11-15 21:28:07 +08:00
}
2022-11-17 19:43:26 +08:00
} else {
go func ( ) {
2023-04-02 22:41:48 +08:00
if err := registerInteractive ( * configFile ) ; err != nil {
log . Fatal ( err )
2022-11-17 19:43:26 +08:00
return
}
os . Exit ( 0 )
} ( )
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt )
<- c
}
2022-11-15 21:28:07 +08:00
2022-11-15 20:46:29 +08:00
return nil
}
}
// registerArgs represents the arguments for register command
type registerArgs struct {
NoInteractive bool
InstanceAddr string
Token string
2022-11-17 19:43:26 +08:00
RunnerName string
Labels string
2022-11-15 20:46:29 +08:00
}
2022-11-15 21:28:07 +08:00
type registerStage int8
const (
2022-11-15 22:42:41 +08:00
StageUnknown registerStage = - 1
StageOverwriteLocalConfig registerStage = iota + 1
StageInputInstance
2022-11-15 21:28:07 +08:00
StageInputToken
StageInputRunnerName
StageInputCustomLabels
StageWaitingForRegistration
2022-11-15 22:42:41 +08:00
StageExit
2022-11-15 21:28:07 +08:00
)
2023-02-15 16:51:14 +08: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-12-02 12:01:50 +08:00
2022-11-15 21:28:07 +08:00
type registerInputs struct {
InstanceAddr string
Token string
RunnerName string
CustomLabels [ ] string
}
2022-11-17 19:43:26 +08: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 20:12:25 +08:00
if len ( r . CustomLabels ) > 0 {
return validateLabels ( r . CustomLabels )
}
return nil
}
2023-04-04 21:32:04 +08:00
func validateLabels ( ls [ ] string ) error {
for _ , label := range ls {
if _ , err := labels . Parse ( label ) ; err != nil {
2023-03-23 20:48:33 +08:00
return err
2022-11-21 20:12:25 +08:00
}
}
2022-11-17 19:43:26 +08:00
return nil
}
2022-11-15 21:28:07 +08: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 22:42:41 +08:00
case StageOverwriteLocalConfig :
if value == "Y" || value == "y" {
return StageInputInstance
}
return StageExit
2022-11-15 21:28:07 +08: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 12:01:50 +08:00
r . CustomLabels = defaultLabels
2022-11-24 11:55:52 +08:00
if value != "" {
r . CustomLabels = strings . Split ( value , "," )
2022-11-21 20:12:25 +08:00
}
2022-11-24 11:55:52 +08:00
2022-11-21 20:12:25 +08:00
if validateLabels ( r . CustomLabels ) != nil {
2023-03-23 20:48:33 +08: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,linux_arm:host)" )
2022-11-21 20:12:25 +08:00
return StageInputCustomLabels
}
2022-11-15 21:28:07 +08:00
return StageWaitingForRegistration
}
return StageUnknown
}
2023-04-02 22:41:48 +08:00
func registerInteractive ( configFile string ) error {
2022-11-15 21:28:07 +08:00
var (
reader = bufio . NewReader ( os . Stdin )
stage = StageInputInstance
inputs = new ( registerInputs )
)
2023-04-02 22:41:48 +08:00
cfg , err := config . LoadDefault ( configFile )
if err != nil {
return fmt . Errorf ( "failed to load config: %v" , err )
}
2022-11-24 11:55:52 +08:00
if f , err := os . Stat ( cfg . Runner . File ) ; err == nil && ! f . IsDir ( ) {
2022-11-15 22:42:41 +08:00
stage = StageOverwriteLocalConfig
}
2022-11-15 21:28:07 +08:00
for {
printStageHelp ( stage )
2022-11-15 22:42:41 +08:00
2022-11-15 21:28:07 +08: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 )
2023-04-02 22:41:48 +08:00
if err := doRegister ( cfg , inputs ) ; err != nil {
2022-11-15 22:42:41 +08:00
log . Errorf ( "Failed to register runner: %v" , err )
} else {
log . Infof ( "Runner registered successfully." )
}
return nil
}
if stage == StageExit {
2022-11-15 21:28:07 +08: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 22:42:41 +08:00
case StageOverwriteLocalConfig :
2022-12-19 09:06:24 +08:00
log . Infoln ( "Runner is already registered, overwrite local config? [y/N]" )
2022-11-15 21:28:07 +08: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 ( )
2023-03-22 14:48:35 +08:00
log . Infof ( "Enter the runner name (if set empty, use hostname: %s):\n" , hostname )
2022-11-15 21:28:07 +08:00
case StageInputCustomLabels :
2023-03-23 20:48:33 +08: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,linux_arm:host):" )
2022-11-15 21:28:07 +08:00
case StageWaitingForRegistration :
log . Infoln ( "Waiting for registration..." )
}
}
2022-11-15 22:42:41 +08:00
2023-04-02 22:41:48 +08:00
func registerNoInteractive ( configFile string , regArgs * registerArgs ) error {
cfg , err := config . LoadDefault ( configFile )
if err != nil {
return err
}
2022-11-17 19:43:26 +08:00
inputs := & registerInputs {
InstanceAddr : regArgs . InstanceAddr ,
Token : regArgs . Token ,
RunnerName : regArgs . RunnerName ,
2022-12-02 12:01:50 +08:00
CustomLabels : defaultLabels ,
2022-11-21 20:12:25 +08:00
}
regArgs . Labels = strings . TrimSpace ( regArgs . Labels )
if regArgs . Labels != "" {
inputs . CustomLabels = strings . Split ( regArgs . Labels , "," )
2022-11-17 19:43:26 +08: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
}
2023-04-02 22:41:48 +08:00
if err := doRegister ( cfg , inputs ) ; err != nil {
2022-11-17 19:43:26 +08:00
log . Errorf ( "Failed to register runner: %v" , err )
2022-11-25 00:54:32 +08:00
return nil
2022-11-17 19:43:26 +08:00
}
log . Infof ( "Runner registered successfully." )
return nil
}
2022-11-15 22:42:41 +08:00
func doRegister ( cfg * config . Config , inputs * registerInputs ) error {
ctx := context . Background ( )
// initial http client
cli := client . New (
inputs . InstanceAddr ,
2023-04-02 22:41:48 +08:00
cfg . Runner . Insecure ,
2023-03-13 18:57:35 +08:00
"" ,
"" ,
2023-04-04 21:32:04 +08:00
ver . Version ( ) ,
2022-11-15 22:42:41 +08: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
}
}
2023-04-02 22:41:48 +08:00
reg := & config . Registration {
Name : inputs . RunnerName ,
Token : inputs . Token ,
Address : inputs . InstanceAddr ,
Labels : inputs . CustomLabels ,
}
2023-04-04 21:32:04 +08:00
ls := make ( [ ] string , len ( reg . Labels ) )
2023-04-02 22:41:48 +08:00
for i , v := range reg . Labels {
2023-04-04 21:32:04 +08:00
l , _ := labels . Parse ( v )
ls [ i ] = l . Name
2023-04-02 22:41:48 +08:00
}
// register new runner.
resp , err := cli . Register ( ctx , connect . NewRequest ( & runnerv1 . RegisterRequest {
Name : reg . Name ,
Token : reg . Token ,
2023-04-04 21:32:04 +08:00
AgentLabels : ls ,
2023-04-02 22:41:48 +08:00
} ) )
if err != nil {
log . WithError ( err ) . Error ( "poller: cannot register new runner" )
return err
}
reg . ID = resp . Msg . Runner . Id
reg . UUID = resp . Msg . Runner . Uuid
reg . Name = resp . Msg . Runner . Name
reg . Token = resp . Msg . Runner . Token
if err := config . SaveRegistration ( cfg . Runner . File , reg ) ; err != nil {
return fmt . Errorf ( "failed to save runner config: %w" , err )
}
return nil
2022-11-15 22:42:41 +08:00
}