enhance: add --token-file flag to register command (#1076)

Continuation of #362.

In addition to fixing merge conflicts and the `fmt.Errorf` issue from the previous version, I've also added a set of tests to cover some basic usage of `initInputs`

---------

Co-authored-by: Félix Baylac Jacqué <felix@alternativebit.fr>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1076
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: h7x4 <h7x4@nani.wtf>
This commit is contained in:
h7x4
2026-07-11 17:02:11 +00:00
committed by Lunny Xiao
parent 1d74ae636a
commit be9b4502d6
3 changed files with 130 additions and 18 deletions

View File

@@ -35,7 +35,8 @@ func Execute(ctx context.Context) {
}
registerCmd.Flags().BoolVar(&regArgs.NoInteractive, "no-interactive", false, "Disable interactive mode")
registerCmd.Flags().StringVar(&regArgs.InstanceAddr, "instance", "", "Gitea instance address")
registerCmd.Flags().StringVar(&regArgs.Token, "token", "", "Runner token")
registerCmd.Flags().StringVar(&regArgs.Token, "token", "", "Runner token (or set the GITEA_RUNNER_REGISTRATION_TOKEN envvar)")
registerCmd.Flags().StringVar(&regArgs.TokenFile, "token-file", "", "Path to a file containing the runner token")
registerCmd.Flags().StringVar(&regArgs.RunnerName, "name", "", "Runner name")
registerCmd.Flags().StringVar(&regArgs.Labels, "labels", "", "Runner tags, comma separated")
registerCmd.Flags().BoolVar(&regArgs.Ephemeral, "ephemeral", false, "Configure the runner to be ephemeral and only ever be able to pick a single job (stricter than --once)")

View File

@@ -75,6 +75,7 @@ type registerArgs struct {
NoInteractive bool
InstanceAddr string
Token string
TokenFile string
RunnerName string
Labels string
Ephemeral bool
@@ -93,6 +94,8 @@ const (
StageExit
)
const registerTokenEnvVar = "GITEA_RUNNER_REGISTRATION_TOKEN"
var defaultLabels = []string{
"ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest",
"ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04",
@@ -207,10 +210,27 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co
return StageUnknown
}
func initInputs(regArgs *registerArgs) *registerInputs {
func initInputs(regArgs *registerArgs) (*registerInputs, error) {
var token string
switch {
case regArgs.TokenFile != "":
tokenBytes, err := os.ReadFile(regArgs.TokenFile)
if err != nil {
return nil, fmt.Errorf("cannot read the token file: %s, %v", regArgs.TokenFile, err)
}
token = string(tokenBytes)
case regArgs.Token != "":
token = regArgs.Token
default:
envToken, ok := os.LookupEnv(registerTokenEnvVar)
if !ok || envToken == "" {
return nil, fmt.Errorf("missing token, token-file argument, or %s environment variable", registerTokenEnvVar)
}
token = envToken
}
inputs := &registerInputs{
InstanceAddr: regArgs.InstanceAddr,
Token: regArgs.Token,
Token: token,
RunnerName: regArgs.RunnerName,
Ephemeral: regArgs.Ephemeral,
}
@@ -219,7 +239,7 @@ func initInputs(regArgs *registerArgs) *registerInputs {
if regArgs.Labels != "" {
inputs.Labels = strings.Split(regArgs.Labels, ",")
}
return inputs
return inputs, nil
}
func registerInteractive(ctx context.Context, configFile string, regArgs *registerArgs) error {
@@ -235,7 +255,10 @@ func registerInteractive(ctx context.Context, configFile string, regArgs *regist
if f, err := os.Stat(cfg.Runner.File); err == nil && !f.IsDir() {
stage = StageOverwriteLocalConfig
}
inputs := initInputs(regArgs)
inputs, err := initInputs(regArgs)
if err != nil {
return err
}
for {
cmdString := inputs.stageValue(stage)
@@ -292,7 +315,10 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
if err != nil {
return err
}
inputs := initInputs(regArgs)
inputs, err := initInputs(regArgs)
if err != nil {
return err
}
// specify labels in config file.
if len(cfg.Runner.Labels) > 0 {
if regArgs.Labels != "" {

View File

@@ -139,18 +139,103 @@ func TestRegisterInputsAssignToNext(t *testing.T) {
}
func TestInitInputs(t *testing.T) {
inputs := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
Token: "token",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
t.Run("missing token", func(t *testing.T) {
_, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.EqualError(t, err, "missing token, token-file argument, or GITEA_RUNNER_REGISTRATION_TOKEN environment variable")
})
require.Equal(t, "http://localhost:3000", inputs.InstanceAddr)
require.Equal(t, "token", inputs.Token)
require.Equal(t, "runner", inputs.RunnerName)
require.True(t, inputs.Ephemeral)
require.Equal(t, []string{"ubuntu:host ", " ubuntu:docker://node:18"}, inputs.Labels)
require.Nil(t, initInputs(&registerArgs{Labels: " "}).Labels)
t.Run("empty token", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "")
_, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
Token: "",
TokenFile: "",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.EqualError(t, err, "missing token, token-file argument, or GITEA_RUNNER_REGISTRATION_TOKEN environment variable")
})
t.Run("invalid token file", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
_, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
TokenFile: "/tmp/nonexistent",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.EqualError(t, err, "cannot read the token file: /tmp/nonexistent, open /tmp/nonexistent: no such file or directory")
})
t.Run("valid token", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
inputs, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
Token: "from-plain-arg",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.NoError(t, err)
require.Equal(t, "http://localhost:3000", inputs.InstanceAddr)
require.Equal(t, "from-plain-arg", inputs.Token)
require.Equal(t, "runner", inputs.RunnerName)
require.True(t, inputs.Ephemeral)
require.Equal(t, []string{"ubuntu:host ", " ubuntu:docker://node:18"}, inputs.Labels)
})
t.Run("valid token file", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
tokenFile, createErr := os.CreateTemp(t.TempDir(), "from-file")
require.NoError(t, createErr)
defer tokenFile.Close()
_, writeErr := tokenFile.WriteString("from-file")
require.NoError(t, writeErr)
_ = tokenFile.Sync()
inputs, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
TokenFile: tokenFile.Name(),
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.NoError(t, err)
require.Equal(t, "http://localhost:3000", inputs.InstanceAddr)
require.Equal(t, "from-file", inputs.Token)
require.Equal(t, "runner", inputs.RunnerName)
require.True(t, inputs.Ephemeral)
require.Equal(t, []string{"ubuntu:host ", " ubuntu:docker://node:18"}, inputs.Labels)
})
t.Run("token from environment variable", func(t *testing.T) {
t.Setenv(registerTokenEnvVar, "from-env")
inputs, err := initInputs(&registerArgs{
InstanceAddr: "http://localhost:3000",
RunnerName: "runner",
Ephemeral: true,
Labels: " ubuntu:host , ubuntu:docker://node:18 ",
})
require.NoError(t, err)
require.Equal(t, "http://localhost:3000", inputs.InstanceAddr)
require.Equal(t, "from-env", inputs.Token)
require.Equal(t, "runner", inputs.RunnerName)
require.True(t, inputs.Ephemeral)
require.Equal(t, []string{"ubuntu:host ", " ubuntu:docker://node:18"}, inputs.Labels)
})
t.Run("empty labels", func(t *testing.T) {
inputs, _ := initInputs(&registerArgs{
Token: "from-plain-arg",
Labels: " ",
})
require.Nil(t, inputs.Labels)
})
}