Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use env GITEA_RUNNER_REGISTRATION_TOKEN as global runner token #32946

Merged
merged 5 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions models/actions/runner_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
)
Expand Down Expand Up @@ -51,7 +52,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
if err != nil {
return nil, err
} else if !has {
return nil, fmt.Errorf("runner token %q: %w", token, util.ErrNotExist)
return nil, fmt.Errorf(`runner token "%s...": %w`, base.TruncateString(token, 3), util.ErrNotExist)
}
return &runnerToken, nil
}
Expand All @@ -68,19 +69,15 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
return err
}

// NewRunnerToken creates a new active runner token and invalidate all old tokens
// NewRunnerTokenWithValue creates a new active runner token and invalidate all old tokens
// ownerID will be ignored and treated as 0 if repoID is non-zero.
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
func NewRunnerTokenWithValue(ctx context.Context, ownerID, repoID int64, token string) (*ActionRunnerToken, error) {
if ownerID != 0 && repoID != 0 {
// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
ownerID = 0
}

token, err := util.CryptoRandomString(40)
if err != nil {
return nil, err
}
runnerToken := &ActionRunnerToken{
OwnerID: ownerID,
RepoID: repoID,
Expand All @@ -95,11 +92,19 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo
return err
}

_, err = db.GetEngine(ctx).Insert(runnerToken)
_, err := db.GetEngine(ctx).Insert(runnerToken)
return err
})
}

func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
token, err := util.CryptoRandomString(40)
if err != nil {
return nil, err
}
return NewRunnerTokenWithValue(ctx, ownerID, repoID, token)
}

// GetLatestRunnerToken returns the latest runner token
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
if ownerID != 0 && repoID != 0 {
Expand Down
2 changes: 1 addition & 1 deletion routers/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func InitWebInstalled(ctx context.Context) {
auth.Init()
mustInit(svg.Init)

actions_service.Init()
mustInitCtx(ctx, actions_service.Init)

mustInit(repo_service.InitLicenseClassifier)

Expand Down
49 changes: 46 additions & 3 deletions services/actions/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,66 @@
package actions

import (
"context"
"errors"
"fmt"
"os"
"strings"

actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"
)

func Init() {
func initGlobalRunnerToken(ctx context.Context) error {
// use the same env name as the runner, for consistency
token := os.Getenv("GITEA_RUNNER_REGISTRATION_TOKEN")
tokenFile := os.Getenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE")
if token != "" && tokenFile != "" {
return errors.New("both GITEA_RUNNER_REGISTRATION_TOKEN and GITEA_RUNNER_REGISTRATION_TOKEN_FILE are set, only one can be used")
}
if tokenFile != "" {
file, err := os.ReadFile(tokenFile)
if err != nil {
return fmt.Errorf("unable to read GITEA_RUNNER_REGISTRATION_TOKEN_FILE: %w", err)
}
token = strings.TrimSpace(string(file))
}
if token == "" {
return nil
}

if len(token) < 32 {
return errors.New("GITEA_RUNNER_REGISTRATION_TOKEN must be at least 32 random characters")
}

existing, err := actions_model.GetRunnerToken(ctx, token)
if err != nil && !errors.Is(err, util.ErrNotExist) {
return fmt.Errorf("unable to check existing token: %w", err)
}
if existing != nil && !existing.IsActive {
log.Warn("The token defined by GITEA_RUNNER_REGISTRATION_TOKEN is already invalidated, please use the latest one from web UI")
return nil
}
_, err = actions_model.NewRunnerTokenWithValue(ctx, 0, 0, token)
return err
}

func Init(ctx context.Context) error {
if !setting.Actions.Enabled {
return
return nil
}

jobEmitterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "actions_ready_job", jobEmitterQueueHandler)
if jobEmitterQueue == nil {
log.Fatal("Unable to create actions_ready_job queue")
return errors.New("unable to create actions_ready_job queue")
}
go graceful.GetManager().RunWithCancel(jobEmitterQueue)

notify_service.RegisterNotifier(NewNotifier())
return initGlobalRunnerToken(ctx)
}
73 changes: 73 additions & 0 deletions services/actions/init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
"os"
"testing"

actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/util"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
FixtureFiles: []string{"action_runner_token.yml"},
})
os.Exit(m.Run())
}

func TestInitToken(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())

t.Run("NoToken", func(t *testing.T) {
_, _ = db.Exec(db.DefaultContext, "DELETE FROM action_runner_token")
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "")
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", "")
err := initGlobalRunnerToken(db.DefaultContext)
require.NoError(t, err)
notEmpty, err := db.IsTableNotEmpty(&actions_model.ActionRunnerToken{})
require.NoError(t, err)
assert.False(t, notEmpty)
})

t.Run("EnvToken", func(t *testing.T) {
tokenValue, _ := util.CryptoRandomString(32)
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", tokenValue)
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", "")
err := initGlobalRunnerToken(db.DefaultContext)
require.NoError(t, err)
token := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
assert.True(t, token.IsActive)
})

t.Run("EnvFileToken", func(t *testing.T) {
tokenValue, _ := util.CryptoRandomString(32)
f := t.TempDir() + "/token"
_ = os.WriteFile(f, []byte(tokenValue), 0o644)
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "")
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", f)
err := initGlobalRunnerToken(db.DefaultContext)
require.NoError(t, err)
token := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
assert.True(t, token.IsActive)

// if the env token is invalidated by another new token, then it shouldn't be active anymore
_, err = actions_model.NewRunnerToken(db.DefaultContext, 0, 0)
require.NoError(t, err)
token = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
assert.False(t, token.IsActive)
})

t.Run("InvalidToken", func(t *testing.T) {
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "abc")
err := initGlobalRunnerToken(db.DefaultContext)
assert.ErrorContains(t, err, "must be at least")
})
}
Loading