feat(cli): make register interactive stages working

pull/2/head
fuxiaohei 2022-11-15 22:42:41 +08:00 committed by Jason Song
parent 8f9d7506dc
commit bfcc6f56f3
8 changed files with 132 additions and 84 deletions

View File

@ -15,4 +15,5 @@ type Filter struct {
type Client interface { type Client interface {
pingv1connect.PingServiceClient pingv1connect.PingServiceClient
runnerv1connect.RunnerServiceClient runnerv1connect.RunnerServiceClient
Address() string
} }

View File

@ -62,13 +62,19 @@ func New(endpoint string, opts ...Option) *HTTPClient {
endpoint, endpoint,
cfg.opts..., cfg.opts...,
), ),
endpoint: endpoint,
} }
} }
func (c *HTTPClient) Address() string {
return c.endpoint
}
var _ Client = (*HTTPClient)(nil) var _ Client = (*HTTPClient)(nil)
// An HTTPClient manages communication with the runner API. // An HTTPClient manages communication with the runner API.
type HTTPClient struct { type HTTPClient struct {
pingv1connect.PingServiceClient pingv1connect.PingServiceClient
runnerv1connect.RunnerServiceClient runnerv1connect.RunnerServiceClient
endpoint string
} }

View File

@ -14,8 +14,14 @@ import (
const version = "0.1.5" const version = "0.1.5"
type globalArgs struct {
EnvFile string
}
func Execute(ctx context.Context) { func Execute(ctx context.Context) {
task := runtime.NewTask("gitea", 0, nil, nil) // task := runtime.NewTask("gitea", 0, nil, nil)
var gArgs globalArgs
// ./act_runner // ./act_runner
rootCmd := &cobra.Command{ rootCmd := &cobra.Command{
@ -26,10 +32,10 @@ func Execute(ctx context.Context) {
Version: version, Version: version,
SilenceUsage: true, SilenceUsage: true,
} }
rootCmd.Flags().BoolP("run", "r", false, "run workflows") //rootCmd.Flags().BoolP("run", "r", false, "run workflows")
rootCmd.Flags().StringP("job", "j", "", "run job") //rootCmd.Flags().StringP("job", "j", "", "run job")
rootCmd.PersistentFlags().StringVarP(&task.Input.ForgeInstance, "forge-instance", "", "github.com", "Forge instance to use.") //rootCmd.PersistentFlags().StringVarP(&task.Input.ForgeInstance, "forge-instance", "", "github.com", "Forge instance to use.")
rootCmd.PersistentFlags().StringVarP(&task.Input.EnvFile, "env-file", "", ".env", "Read in a file of environment variables.") rootCmd.PersistentFlags().StringVarP(&gArgs.EnvFile, "env-file", "", ".env", "Read in a file of environment variables.")
// ./act_runner register // ./act_runner register
var regArgs registerArgs var regArgs registerArgs
@ -37,7 +43,7 @@ func Execute(ctx context.Context) {
Use: "register", Use: "register",
Short: "Register a runner to the server", Short: "Register a runner to the server",
Args: cobra.MaximumNArgs(0), Args: cobra.MaximumNArgs(0),
RunE: runRegister(ctx, &regArgs), // must use a pointer to regArgs RunE: runRegister(ctx, &regArgs, gArgs.EnvFile), // must use a pointer to regArgs
} }
registerCmd.Flags().BoolVarP(&regArgs.NoInteractive, "no-interactive", "", false, "Disable interactive mode") registerCmd.Flags().BoolVarP(&regArgs.NoInteractive, "no-interactive", "", false, "Disable interactive mode")
registerCmd.Flags().StringVarP(&regArgs.InstanceAddr, "instance", "", "", "Gitea instance address") registerCmd.Flags().StringVarP(&regArgs.InstanceAddr, "instance", "", "", "Gitea instance address")
@ -49,7 +55,7 @@ func Execute(ctx context.Context) {
Use: "daemon", Use: "daemon",
Short: "Run as a runner daemon", Short: "Run as a runner daemon",
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
RunE: runDaemon(ctx, task.Input.EnvFile), RunE: runDaemon(ctx, gArgs.EnvFile),
} }
// add all command // add all command
rootCmd.AddCommand(daemonCmd) rootCmd.AddCommand(daemonCmd)

View File

@ -3,15 +3,12 @@ package cmd
import ( import (
"context" "context"
"os" "os"
"time"
"gitea.com/gitea/act_runner/client" "gitea.com/gitea/act_runner/client"
"gitea.com/gitea/act_runner/config" "gitea.com/gitea/act_runner/config"
"gitea.com/gitea/act_runner/engine" "gitea.com/gitea/act_runner/engine"
"gitea.com/gitea/act_runner/poller" "gitea.com/gitea/act_runner/poller"
"gitea.com/gitea/act_runner/register"
"gitea.com/gitea/act_runner/runtime" "gitea.com/gitea/act_runner/runtime"
pingv1 "gitea.com/gitea/proto-go/ping/v1"
runnerv1 "gitea.com/gitea/proto-go/runner/v1" runnerv1 "gitea.com/gitea/proto-go/runner/v1"
"github.com/bufbuild/connect-go" "github.com/bufbuild/connect-go"
@ -35,60 +32,6 @@ func runDaemon(ctx context.Context, envFile string) func(cmd *cobra.Command, arg
initLogging(cfg) initLogging(cfg)
// initial http client
cli := client.New(
cfg.Client.Address,
client.WithSkipVerify(cfg.Client.SkipVerify),
client.WithGRPC(cfg.Client.GRPC),
client.WithGRPCWeb(cfg.Client.GRPCWeb),
)
for {
_, err := cli.Ping(ctx, connect.NewRequest(&pingv1.PingRequest{
Data: cfg.Runner.Name,
}))
select {
case <-ctx.Done():
return nil
default:
}
if ctx.Err() != nil {
break
}
if err != nil {
log.WithError(err).
Errorln("cannot ping the remote server")
// TODO: if ping failed, retry or exit
time.Sleep(time.Second)
} else {
log.Infoln("successfully pinged the remote server")
break
}
}
// register new runner
if cfg.Runner.UUID == "" {
register := register.New(
cli,
&client.Filter{
OS: cfg.Platform.OS,
Arch: cfg.Platform.Arch,
Labels: cfg.Runner.Labels,
},
)
data, err := register.Register(ctx, cfg.Runner)
if err != nil {
return err
}
if data.UUID != "" {
cfg.Runner.UUID = data.UUID
}
if data.Token != "" {
cfg.Runner.Token = data.Token
}
}
// try to connect to docker daemon // try to connect to docker daemon
// if failed, exit with error // if failed, exit with error
if err := engine.Start(ctx); err != nil { if err := engine.Start(ctx); err != nil {
@ -97,7 +40,7 @@ func runDaemon(ctx context.Context, envFile string) func(cmd *cobra.Command, arg
var g errgroup.Group var g errgroup.Group
cli = client.New( cli := client.New(
cfg.Client.Address, cfg.Client.Address,
client.WithSkipVerify(cfg.Client.SkipVerify), client.WithSkipVerify(cfg.Client.SkipVerify),
client.WithGRPC(cfg.Client.GRPC), client.WithGRPC(cfg.Client.GRPC),

View File

@ -7,14 +7,22 @@ import (
"os/signal" "os/signal"
"runtime" "runtime"
"strings" "strings"
"time"
"gitea.com/gitea/act_runner/client"
"gitea.com/gitea/act_runner/config"
"gitea.com/gitea/act_runner/register"
pingv1 "gitea.com/gitea/proto-go/ping/v1"
"github.com/appleboy/com/file"
"github.com/bufbuild/connect-go"
"github.com/joho/godotenv"
"github.com/mattn/go-isatty" "github.com/mattn/go-isatty"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// runRegister registers a runner to the server // runRegister registers a runner to the server
func runRegister(ctx context.Context, regArgs *registerArgs) func(*cobra.Command, []string) error { func runRegister(ctx context.Context, regArgs *registerArgs, envFile string) func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error {
log.SetReportCaller(false) log.SetReportCaller(false)
isTerm := isatty.IsTerminal(os.Stdout.Fd()) isTerm := isatty.IsTerminal(os.Stdout.Fd())
@ -22,6 +30,7 @@ func runRegister(ctx context.Context, regArgs *registerArgs) func(*cobra.Command
DisableColors: !isTerm, DisableColors: !isTerm,
DisableTimestamp: true, DisableTimestamp: true,
}) })
log.SetLevel(log.DebugLevel)
log.Infof("Registering runner, arch=%s, os=%s, version=%s.", log.Infof("Registering runner, arch=%s, os=%s, version=%s.",
runtime.GOARCH, runtime.GOOS, version) runtime.GOARCH, runtime.GOOS, version)
@ -33,7 +42,7 @@ func runRegister(ctx context.Context, regArgs *registerArgs) func(*cobra.Command
} }
go func() { go func() {
if err := registerInteractive(); err != nil { if err := registerInteractive(envFile); err != nil {
// log.Errorln(err) // log.Errorln(err)
os.Exit(2) os.Exit(2)
return return
@ -59,12 +68,14 @@ type registerArgs struct {
type registerStage int8 type registerStage int8
const ( const (
StageUnknown registerStage = -1 StageUnknown registerStage = -1
StageInputInstance registerStage = iota + 1 StageOverwriteLocalConfig registerStage = iota + 1
StageInputInstance
StageInputToken StageInputToken
StageInputRunnerName StageInputRunnerName
StageInputCustomLabels StageInputCustomLabels
StageWaitingForRegistration StageWaitingForRegistration
StageExit
) )
type registerInputs struct { type registerInputs struct {
@ -90,6 +101,11 @@ func (r *registerInputs) assignToNext(stage registerStage, value string) registe
} }
switch stage { switch stage {
case StageOverwriteLocalConfig:
if value == "Y" || value == "y" {
return StageInputInstance
}
return StageExit
case StageInputInstance: case StageInputInstance:
r.InstanceAddr = value r.InstanceAddr = value
return StageInputToken return StageInputToken
@ -106,15 +122,30 @@ func (r *registerInputs) assignToNext(stage registerStage, value string) registe
return StageUnknown return StageUnknown
} }
func registerInteractive() error { func getLocalConfigFile(envFile string) (string, bool) {
_ = godotenv.Load(envFile)
cfg, _ := config.FromEnviron()
return cfg.Runner.File, file.IsFile(cfg.Runner.File)
}
func registerInteractive(envFile string) error {
var ( var (
reader = bufio.NewReader(os.Stdin) reader = bufio.NewReader(os.Stdin)
stage = StageInputInstance stage = StageInputInstance
inputs = new(registerInputs) inputs = new(registerInputs)
) )
// check if overwrite local config
_ = godotenv.Load(envFile)
cfg, _ := config.FromEnviron()
if file.IsFile(cfg.Runner.File) {
stage = StageOverwriteLocalConfig
}
for { for {
printStageHelp(stage) printStageHelp(stage)
cmdString, err := reader.ReadString('\n') cmdString, err := reader.ReadString('\n')
if err != nil { if err != nil {
return err return err
@ -123,6 +154,15 @@ func registerInteractive() error {
if stage == StageWaitingForRegistration { if stage == StageWaitingForRegistration {
log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.CustomLabels) log.Infof("Registering runner, name=%s, instance=%s, labels=%v.", inputs.RunnerName, inputs.InstanceAddr, inputs.CustomLabels)
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 {
return nil return nil
} }
@ -135,6 +175,8 @@ func registerInteractive() error {
func printStageHelp(stage registerStage) { func printStageHelp(stage registerStage) {
switch stage { switch stage {
case StageOverwriteLocalConfig:
log.Infoln("Runner is already registered, overwrite local config? [Y/n]")
case StageInputInstance: case StageInputInstance:
log.Infoln("Enter the Gitea instance URL (for example, https://gitea.com/):") log.Infoln("Enter the Gitea instance URL (for example, https://gitea.com/):")
case StageInputToken: case StageInputToken:
@ -148,3 +190,57 @@ func printStageHelp(stage registerStage) {
log.Infoln("Waiting for registration...") log.Infoln("Waiting for registration...")
} }
} }
func doRegister(cfg *config.Config, inputs *registerInputs) error {
ctx := context.Background()
// initial http client
cli := client.New(
inputs.InstanceAddr,
client.WithSkipVerify(cfg.Client.SkipVerify),
client.WithGRPC(cfg.Client.GRPC),
client.WithGRPCWeb(cfg.Client.GRPCWeb),
)
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
}
}
register := register.New(
cli,
&client.Filter{
OS: cfg.Platform.OS,
Arch: cfg.Platform.Arch,
Labels: inputs.CustomLabels,
},
)
cfg.Runner.Name = inputs.RunnerName
cfg.Runner.Token = inputs.Token
_, err := register.Register(ctx, cfg.Runner)
if err != nil {
log.WithError(err).Errorln("Cannot register the runner")
return nil
}
return nil
}

View File

@ -2,7 +2,6 @@ package config
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"os" "os"
"runtime" "runtime"
@ -26,8 +25,6 @@ type (
Client struct { Client struct {
Address string `ignored:"true"` Address string `ignored:"true"`
Proto string `envconfig:"GITEA_RPC_PROTO" default:"http"`
Host string `envconfig:"GITEA_RPC_HOST"`
SkipVerify bool `envconfig:"GITEA_RPC_SKIP_VERIFY"` SkipVerify bool `envconfig:"GITEA_RPC_SKIP_VERIFY"`
GRPC bool `envconfig:"GITEA_RPC_GRPC" default:"true"` GRPC bool `envconfig:"GITEA_RPC_GRPC" default:"true"`
GRPCWeb bool `envconfig:"GITEA_RPC_GRPC_WEB"` GRPCWeb bool `envconfig:"GITEA_RPC_GRPC_WEB"`
@ -36,7 +33,7 @@ type (
Runner struct { Runner struct {
UUID string `ignored:"true"` UUID string `ignored:"true"`
Name string `envconfig:"GITEA_RUNNER_NAME"` Name string `envconfig:"GITEA_RUNNER_NAME"`
Token string `envconfig:"GITEA_RUNNER_TOKEN" required:"true"` Token string `ignored:"true"`
Capacity int `envconfig:"GITEA_RUNNER_CAPACITY" default:"1"` Capacity int `envconfig:"GITEA_RUNNER_CAPACITY" default:"1"`
File string `envconfig:"GITEA_RUNNER_FILE" default:".runner"` File string `envconfig:"GITEA_RUNNER_FILE" default:".runner"`
Environ map[string]string `envconfig:"GITEA_RUNNER_ENVIRON"` Environ map[string]string `envconfig:"GITEA_RUNNER_ENVIRON"`
@ -72,15 +69,12 @@ func FromEnviron() (Config, error) {
if runner.Token != "" { if runner.Token != "" {
cfg.Runner.Token = runner.Token cfg.Runner.Token = runner.Token
} }
if runner.Address != "" {
cfg.Client.Address = runner.Address
}
cfg.ForgeInstance = runner.ForgeInstance cfg.ForgeInstance = runner.ForgeInstance
} }
cfg.Client.Address = fmt.Sprintf(
"%s://%s",
cfg.Client.Proto,
cfg.Client.Host,
)
// runner config // runner config
if cfg.Runner.Environ == nil { if cfg.Runner.Environ == nil {
cfg.Runner.Environ = map[string]string{ cfg.Runner.Environ = map[string]string{

View File

@ -11,5 +11,6 @@ type Runner struct {
UUID string `json:"uuid"` UUID string `json:"uuid"`
Name string `json:"name"` Name string `json:"name"`
Token string `json:"token"` Token string `json:"token"`
Address string `json:"address"`
ForgeInstance string `json:"forge_instance"` ForgeInstance string `json:"forge_instance"`
} }

View File

@ -42,10 +42,11 @@ func (p *Register) Register(ctx context.Context, cfg config.Runner) (*core.Runne
} }
data := &core.Runner{ data := &core.Runner{
ID: resp.Msg.Runner.Id, ID: resp.Msg.Runner.Id,
UUID: resp.Msg.Runner.Uuid, UUID: resp.Msg.Runner.Uuid,
Name: resp.Msg.Runner.Name, Name: resp.Msg.Runner.Name,
Token: resp.Msg.Runner.Token, Token: resp.Msg.Runner.Token,
Address: p.Client.Address(),
// ForgeInstance: resp.Msg.Runner.ForgeInstance, TODO: add me // ForgeInstance: resp.Msg.Runner.ForgeInstance, TODO: add me
} }