diff --git a/cmd/actions.go b/cmd/actions.go new file mode 100644 index 00000000000..66ad336da50 --- /dev/null +++ b/cmd/actions.go @@ -0,0 +1,56 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "fmt" + + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/setting" + + "github.com/urfave/cli" +) + +var ( + // CmdActions represents the available actions sub-commands. + CmdActions = cli.Command{ + Name: "actions", + Usage: "", + Description: "Commands for managing Gitea Actions", + Subcommands: []cli.Command{ + subcmdActionsGenRunnerToken, + }, + } + + subcmdActionsGenRunnerToken = cli.Command{ + Name: "generate-runner-token", + Usage: "Generate a new token for a runner to use to register with the server", + Action: runGenerateActionsRunnerToken, + Aliases: []string{"grt"}, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "scope, s", + Value: "", + Usage: "{owner}[/{repo}] - leave empty for a global runner", + }, + }, + } +) + +func runGenerateActionsRunnerToken(c *cli.Context) error { + ctx, cancel := installSignals() + defer cancel() + + setting.InitProviderFromExistingFile() + setting.LoadCommonSettings() + + scope := c.String("scope") + + respText, extra := private.GenerateActionsRunnerToken(ctx, scope) + if extra.HasError() { + return handleCliResponseExtra(extra) + } + _, _ = fmt.Printf("%s\n", respText) + return nil +} diff --git a/docs/content/doc/administration/command-line.en-us.md b/docs/content/doc/administration/command-line.en-us.md index d3362e57313..4d01d6e640e 100644 --- a/docs/content/doc/administration/command-line.en-us.md +++ b/docs/content/doc/administration/command-line.en-us.md @@ -551,3 +551,28 @@ Restore-repo restore repository data from disk dir: - `--owner_name lunny`: Restore destination owner name - `--repo_name tango`: Restore destination repository name - `--units `: Which items will be restored, one or more units should be separated as comma. wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units. + +### actions generate-runner-token + +Generate a new token for a runner to use to register with the server + +- Options: + - `--scope {owner}[/{repo}]`, `-s {owner}[/{repo}]`: To limit the scope of the runner, no scope means the runner can be used for all repos, but you can also limit it to a specific repo or owner + +To register a global runner: + +``` +gitea actions generate-runner-token +``` + +To register a runner for a specific organization, in this case `org`: + +``` +gitea actions generate-runner-token -s org +``` + +To register a runner for a specific repo, in this case `username/test-repo`: + +``` +gitea actions generate-runner-token -s username/test-repo +``` diff --git a/main.go b/main.go index eeedf54c274..1589fa97db4 100644 --- a/main.go +++ b/main.go @@ -75,6 +75,7 @@ arguments - which can alternatively be run by running the subcommand web.` cmd.CmdDocs, cmd.CmdDumpRepository, cmd.CmdRestoreRepository, + cmd.CmdActions, } // Now adjust these commands to add our global configuration options diff --git a/modules/private/actions.go b/modules/private/actions.go new file mode 100644 index 00000000000..be24e16d3ff --- /dev/null +++ b/modules/private/actions.go @@ -0,0 +1,27 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "context" + + "code.gitea.io/gitea/modules/setting" +) + +// Email structure holds a data for sending general emails +type GenerateTokenRequest struct { + Scope string +} + +// GenerateActionsRunnerToken calls the internal GenerateActionsRunnerToken function +func GenerateActionsRunnerToken(ctx context.Context, scope string) (string, ResponseExtra) { + reqURL := setting.LocalURL + "api/internal/actions/generate_actions_runner_token" + + req := newInternalRequest(ctx, reqURL, "POST", GenerateTokenRequest{ + Scope: scope, + }) + + resp, extra := requestJSONResp(req, &responseText{}) + return resp.Text, extra +} diff --git a/routers/private/actions.go b/routers/private/actions.go new file mode 100644 index 00000000000..b7e416f56a3 --- /dev/null +++ b/routers/private/actions.go @@ -0,0 +1,91 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package private + +import ( + "errors" + "fmt" + "net/http" + "strings" + + actions_model "code.gitea.io/gitea/models/actions" + repo_model "code.gitea.io/gitea/models/repo" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/util" +) + +// GenerateActionsRunnerToken generates a new runner token for a given scope +func GenerateActionsRunnerToken(ctx *context.PrivateContext) { + var genRequest private.GenerateTokenRequest + rd := ctx.Req.Body + defer rd.Close() + + if err := json.NewDecoder(rd).Decode(&genRequest); err != nil { + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + return + } + + owner, repo, err := parseScope(ctx, genRequest.Scope) + if err != nil { + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err.Error(), + }) + } + + token, err := actions_model.GetUnactivatedRunnerToken(ctx, owner, repo) + if errors.Is(err, util.ErrNotExist) { + token, err = actions_model.NewRunnerToken(ctx, owner, repo) + if err != nil { + err := fmt.Sprintf("error while creating runner token: %v", err) + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err, + }) + return + } + } else if err != nil { + err := fmt.Sprintf("could not get unactivated runner token: %v", err) + log.Error("%v", err) + ctx.JSON(http.StatusInternalServerError, private.Response{ + Err: err, + }) + return + } + + ctx.PlainText(http.StatusOK, token.Token) +} + +func parseScope(ctx *context.PrivateContext, scope string) (ownerID, repoID int64, err error) { + ownerID = 0 + repoID = 0 + if scope == "" { + return ownerID, repoID, nil + } + + ownerName, repoName, found := strings.Cut(scope, "/") + + u, err := user_model.GetUserByName(ctx, ownerName) + if err != nil { + return ownerID, repoID, err + } + + if !found { + return u.ID, repoID, nil + } + + r, err := repo_model.GetRepositoryByName(u.ID, repoName) + if err != nil { + return ownerID, repoID, err + } + repoID = r.ID + return ownerID, repoID, nil +} diff --git a/routers/private/internal.go b/routers/private/internal.go index 4acede33705..b4d32c37a64 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -77,6 +77,7 @@ func Routes() *web.Route { r.Get("/manager/processes", Processes) r.Post("/mail/send", SendEmail) r.Post("/restore_repo", RestoreRepo) + r.Post("/actions/generate_actions_runner_token", GenerateActionsRunnerToken) return r }