From be9b4502d61c55e7ac8ba60292d96333b4d61a54 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Sat, 11 Jul 2026 17:02:11 +0000 Subject: [PATCH] enhance: add `--token-file` flag to `register` command (#1076) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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é Reviewed-on: https://gitea.com/gitea/runner/pulls/1076 Reviewed-by: Lunny Xiao Co-authored-by: h7x4 --- internal/app/cmd/cmd.go | 3 +- internal/app/cmd/register.go | 36 ++++++++-- internal/app/cmd/register_test.go | 109 ++++++++++++++++++++++++++---- 3 files changed, 130 insertions(+), 18 deletions(-) diff --git a/internal/app/cmd/cmd.go b/internal/app/cmd/cmd.go index a964f614..95074001 100644 --- a/internal/app/cmd/cmd.go +++ b/internal/app/cmd/cmd.go @@ -35,7 +35,8 @@ func Execute(ctx context.Context) { } registerCmd.Flags().BoolVar(®Args.NoInteractive, "no-interactive", false, "Disable interactive mode") registerCmd.Flags().StringVar(®Args.InstanceAddr, "instance", "", "Gitea instance address") - registerCmd.Flags().StringVar(®Args.Token, "token", "", "Runner token") + registerCmd.Flags().StringVar(®Args.Token, "token", "", "Runner token (or set the GITEA_RUNNER_REGISTRATION_TOKEN envvar)") + registerCmd.Flags().StringVar(®Args.TokenFile, "token-file", "", "Path to a file containing the runner token") registerCmd.Flags().StringVar(®Args.RunnerName, "name", "", "Runner name") registerCmd.Flags().StringVar(®Args.Labels, "labels", "", "Runner tags, comma separated") registerCmd.Flags().BoolVar(®Args.Ephemeral, "ephemeral", false, "Configure the runner to be ephemeral and only ever be able to pick a single job (stricter than --once)") diff --git a/internal/app/cmd/register.go b/internal/app/cmd/register.go index 2063c366..bc2f3f63 100644 --- a/internal/app/cmd/register.go +++ b/internal/app/cmd/register.go @@ -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 := ®isterInputs{ 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 != "" { diff --git a/internal/app/cmd/register_test.go b/internal/app/cmd/register_test.go index 031672c5..0c3234b5 100644 --- a/internal/app/cmd/register_test.go +++ b/internal/app/cmd/register_test.go @@ -139,18 +139,103 @@ func TestRegisterInputsAssignToNext(t *testing.T) { } func TestInitInputs(t *testing.T) { - inputs := initInputs(®isterArgs{ - 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(®isterArgs{ + 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(®isterArgs{Labels: " "}).Labels) + t.Run("empty token", func(t *testing.T) { + t.Setenv(registerTokenEnvVar, "") + _, err := initInputs(®isterArgs{ + 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(®isterArgs{ + 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(®isterArgs{ + 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(®isterArgs{ + 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(®isterArgs{ + 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(®isterArgs{ + Token: "from-plain-arg", + Labels: " ", + }) + require.Nil(t, inputs.Labels) + }) }