aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason Song <i@wolfogre.com>2025-02-11 09:39:10 +0800
committerGitHub <noreply@github.com>2025-02-11 01:39:10 +0000
commite9b98aef447686f918ce4922ccadfed54c3f1634 (patch)
tree2807ac7d539eb5212c3b62a003e0c3f51531c4ee
parent217ffe5492ff2236dd94ce76904b07aad8d4c149 (diff)
downloadgitea-e9b98aef447686f918ce4922ccadfed54c3f1634.tar.gz
gitea-e9b98aef447686f918ce4922ccadfed54c3f1634.zip
Enhance routers for the Actions runner operations (#33549)
- Find the runner before deleting - Move the main logic from `routers/web/repo/setting/runners.go` to `routers/web/shared/actions/runners.go`. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
-rw-r--r--models/actions/runner.go9
-rw-r--r--routers/web/repo/setting/runners.go187
-rw-r--r--routers/web/shared/actions/runners.go235
-rw-r--r--routers/web/web.go12
-rw-r--r--tests/integration/actions_runner_modify_test.go151
5 files changed, 387 insertions, 207 deletions
diff --git a/models/actions/runner.go b/models/actions/runner.go
index 0d5464a5be..798a647180 100644
--- a/models/actions/runner.go
+++ b/models/actions/runner.go
@@ -167,6 +167,7 @@ func init() {
type FindRunnerOptions struct {
db.ListOptions
+ IDs []int64
RepoID int64
OwnerID int64 // it will be ignored if RepoID is set
Sort string
@@ -178,6 +179,14 @@ type FindRunnerOptions struct {
func (opts FindRunnerOptions) ToConds() builder.Cond {
cond := builder.NewCond()
+ if len(opts.IDs) > 0 {
+ if len(opts.IDs) == 1 {
+ cond = cond.And(builder.Eq{"id": opts.IDs[0]})
+ } else {
+ cond = cond.And(builder.In("id", opts.IDs))
+ }
+ }
+
if opts.RepoID > 0 {
c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID})
if opts.WithAvailable {
diff --git a/routers/web/repo/setting/runners.go b/routers/web/repo/setting/runners.go
deleted file mode 100644
index 94f2ae7a0c..0000000000
--- a/routers/web/repo/setting/runners.go
+++ /dev/null
@@ -1,187 +0,0 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package setting
-
-import (
- "errors"
- "net/http"
- "net/url"
-
- actions_model "code.gitea.io/gitea/models/actions"
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/templates"
- actions_shared "code.gitea.io/gitea/routers/web/shared/actions"
- shared_user "code.gitea.io/gitea/routers/web/shared/user"
- "code.gitea.io/gitea/services/context"
-)
-
-const (
- // TODO: Separate secrets from runners when layout is ready
- tplRepoRunners templates.TplName = "repo/settings/actions"
- tplOrgRunners templates.TplName = "org/settings/actions"
- tplAdminRunners templates.TplName = "admin/actions"
- tplUserRunners templates.TplName = "user/settings/actions"
- tplRepoRunnerEdit templates.TplName = "repo/settings/runner_edit"
- tplOrgRunnerEdit templates.TplName = "org/settings/runners_edit"
- tplAdminRunnerEdit templates.TplName = "admin/runners/edit"
- tplUserRunnerEdit templates.TplName = "user/settings/runner_edit"
-)
-
-type runnersCtx struct {
- OwnerID int64
- RepoID int64
- IsRepo bool
- IsOrg bool
- IsAdmin bool
- IsUser bool
- RunnersTemplate templates.TplName
- RunnerEditTemplate templates.TplName
- RedirectLink string
-}
-
-func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) {
- if ctx.Data["PageIsRepoSettings"] == true {
- return &runnersCtx{
- RepoID: ctx.Repo.Repository.ID,
- OwnerID: 0,
- IsRepo: true,
- RunnersTemplate: tplRepoRunners,
- RunnerEditTemplate: tplRepoRunnerEdit,
- RedirectLink: ctx.Repo.RepoLink + "/settings/actions/runners/",
- }, nil
- }
-
- if ctx.Data["PageIsOrgSettings"] == true {
- err := shared_user.LoadHeaderCount(ctx)
- if err != nil {
- ctx.ServerError("LoadHeaderCount", err)
- return nil, nil
- }
- return &runnersCtx{
- RepoID: 0,
- OwnerID: ctx.Org.Organization.ID,
- IsOrg: true,
- RunnersTemplate: tplOrgRunners,
- RunnerEditTemplate: tplOrgRunnerEdit,
- RedirectLink: ctx.Org.OrgLink + "/settings/actions/runners/",
- }, nil
- }
-
- if ctx.Data["PageIsAdmin"] == true {
- return &runnersCtx{
- RepoID: 0,
- OwnerID: 0,
- IsAdmin: true,
- RunnersTemplate: tplAdminRunners,
- RunnerEditTemplate: tplAdminRunnerEdit,
- RedirectLink: setting.AppSubURL + "/-/admin/actions/runners/",
- }, nil
- }
-
- if ctx.Data["PageIsUserSettings"] == true {
- return &runnersCtx{
- OwnerID: ctx.Doer.ID,
- RepoID: 0,
- IsUser: true,
- RunnersTemplate: tplUserRunners,
- RunnerEditTemplate: tplUserRunnerEdit,
- RedirectLink: setting.AppSubURL + "/user/settings/actions/runners/",
- }, nil
- }
-
- return nil, errors.New("unable to set Runners context")
-}
-
-// Runners render settings/actions/runners page for repo level
-func Runners(ctx *context.Context) {
- ctx.Data["PageIsSharedSettingsRunners"] = true
- ctx.Data["Title"] = ctx.Tr("actions.actions")
- ctx.Data["PageType"] = "runners"
-
- rCtx, err := getRunnersCtx(ctx)
- if err != nil {
- ctx.ServerError("getRunnersCtx", err)
- return
- }
-
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
-
- opts := actions_model.FindRunnerOptions{
- ListOptions: db.ListOptions{
- Page: page,
- PageSize: 100,
- },
- Sort: ctx.Req.URL.Query().Get("sort"),
- Filter: ctx.Req.URL.Query().Get("q"),
- }
- if rCtx.IsRepo {
- opts.RepoID = rCtx.RepoID
- opts.WithAvailable = true
- } else if rCtx.IsOrg || rCtx.IsUser {
- opts.OwnerID = rCtx.OwnerID
- opts.WithAvailable = true
- }
- actions_shared.RunnersList(ctx, opts)
-
- ctx.HTML(http.StatusOK, rCtx.RunnersTemplate)
-}
-
-// RunnersEdit renders runner edit page for repository level
-func RunnersEdit(ctx *context.Context) {
- ctx.Data["PageIsSharedSettingsRunners"] = true
- ctx.Data["Title"] = ctx.Tr("actions.runners.edit_runner")
- rCtx, err := getRunnersCtx(ctx)
- if err != nil {
- ctx.ServerError("getRunnersCtx", err)
- return
- }
-
- page := ctx.FormInt("page")
- if page <= 1 {
- page = 1
- }
-
- actions_shared.RunnerDetails(ctx, page,
- ctx.PathParamInt64("runnerid"), rCtx.OwnerID, rCtx.RepoID,
- )
- ctx.HTML(http.StatusOK, rCtx.RunnerEditTemplate)
-}
-
-func RunnersEditPost(ctx *context.Context) {
- rCtx, err := getRunnersCtx(ctx)
- if err != nil {
- ctx.ServerError("getRunnersCtx", err)
- return
- }
- actions_shared.RunnerDetailsEditPost(ctx, ctx.PathParamInt64("runnerid"),
- rCtx.OwnerID, rCtx.RepoID,
- rCtx.RedirectLink+url.PathEscape(ctx.PathParam("runnerid")))
-}
-
-func ResetRunnerRegistrationToken(ctx *context.Context) {
- rCtx, err := getRunnersCtx(ctx)
- if err != nil {
- ctx.ServerError("getRunnersCtx", err)
- return
- }
- actions_shared.RunnerResetRegistrationToken(ctx, rCtx.OwnerID, rCtx.RepoID, rCtx.RedirectLink)
-}
-
-// RunnerDeletePost response for deleting runner
-func RunnerDeletePost(ctx *context.Context) {
- rCtx, err := getRunnersCtx(ctx)
- if err != nil {
- ctx.ServerError("getRunnersCtx", err)
- return
- }
- actions_shared.RunnerDeletePost(ctx, ctx.PathParamInt64("runnerid"), rCtx.RedirectLink, rCtx.RedirectLink+url.PathEscape(ctx.PathParam("runnerid")))
-}
-
-func RedirectToDefaultSetting(ctx *context.Context) {
- ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/runners")
-}
diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go
index 6d77bdd2fa..41aac4976b 100644
--- a/routers/web/shared/actions/runners.go
+++ b/routers/web/shared/actions/runners.go
@@ -5,18 +5,131 @@ package actions
import (
"errors"
+ "net/http"
+ "net/url"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
+ shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms"
)
-// RunnersList prepares data for runners list
-func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) {
+const (
+ // TODO: Separate secrets from runners when layout is ready
+ tplRepoRunners templates.TplName = "repo/settings/actions"
+ tplOrgRunners templates.TplName = "org/settings/actions"
+ tplAdminRunners templates.TplName = "admin/actions"
+ tplUserRunners templates.TplName = "user/settings/actions"
+ tplRepoRunnerEdit templates.TplName = "repo/settings/runner_edit"
+ tplOrgRunnerEdit templates.TplName = "org/settings/runners_edit"
+ tplAdminRunnerEdit templates.TplName = "admin/runners/edit"
+ tplUserRunnerEdit templates.TplName = "user/settings/runner_edit"
+)
+
+type runnersCtx struct {
+ OwnerID int64
+ RepoID int64
+ IsRepo bool
+ IsOrg bool
+ IsAdmin bool
+ IsUser bool
+ RunnersTemplate templates.TplName
+ RunnerEditTemplate templates.TplName
+ RedirectLink string
+}
+
+func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) {
+ if ctx.Data["PageIsRepoSettings"] == true {
+ return &runnersCtx{
+ RepoID: ctx.Repo.Repository.ID,
+ OwnerID: 0,
+ IsRepo: true,
+ RunnersTemplate: tplRepoRunners,
+ RunnerEditTemplate: tplRepoRunnerEdit,
+ RedirectLink: ctx.Repo.RepoLink + "/settings/actions/runners/",
+ }, nil
+ }
+
+ if ctx.Data["PageIsOrgSettings"] == true {
+ err := shared_user.LoadHeaderCount(ctx)
+ if err != nil {
+ ctx.ServerError("LoadHeaderCount", err)
+ return nil, nil
+ }
+ return &runnersCtx{
+ RepoID: 0,
+ OwnerID: ctx.Org.Organization.ID,
+ IsOrg: true,
+ RunnersTemplate: tplOrgRunners,
+ RunnerEditTemplate: tplOrgRunnerEdit,
+ RedirectLink: ctx.Org.OrgLink + "/settings/actions/runners/",
+ }, nil
+ }
+
+ if ctx.Data["PageIsAdmin"] == true {
+ return &runnersCtx{
+ RepoID: 0,
+ OwnerID: 0,
+ IsAdmin: true,
+ RunnersTemplate: tplAdminRunners,
+ RunnerEditTemplate: tplAdminRunnerEdit,
+ RedirectLink: setting.AppSubURL + "/-/admin/actions/runners/",
+ }, nil
+ }
+
+ if ctx.Data["PageIsUserSettings"] == true {
+ return &runnersCtx{
+ OwnerID: ctx.Doer.ID,
+ RepoID: 0,
+ IsUser: true,
+ RunnersTemplate: tplUserRunners,
+ RunnerEditTemplate: tplUserRunnerEdit,
+ RedirectLink: setting.AppSubURL + "/user/settings/actions/runners/",
+ }, nil
+ }
+
+ return nil, errors.New("unable to set Runners context")
+}
+
+// Runners render settings/actions/runners page for repo level
+func Runners(ctx *context.Context) {
+ ctx.Data["PageIsSharedSettingsRunners"] = true
+ ctx.Data["Title"] = ctx.Tr("actions.actions")
+ ctx.Data["PageType"] = "runners"
+
+ rCtx, err := getRunnersCtx(ctx)
+ if err != nil {
+ ctx.ServerError("getRunnersCtx", err)
+ return
+ }
+
+ page := ctx.FormInt("page")
+ if page <= 1 {
+ page = 1
+ }
+
+ opts := actions_model.FindRunnerOptions{
+ ListOptions: db.ListOptions{
+ Page: page,
+ PageSize: 100,
+ },
+ Sort: ctx.Req.URL.Query().Get("sort"),
+ Filter: ctx.Req.URL.Query().Get("q"),
+ }
+ if rCtx.IsRepo {
+ opts.RepoID = rCtx.RepoID
+ opts.WithAvailable = true
+ } else if rCtx.IsOrg || rCtx.IsUser {
+ opts.OwnerID = rCtx.OwnerID
+ opts.WithAvailable = true
+ }
+
runners, count, err := db.FindAndCount[actions_model.ActionRunner](ctx, opts)
if err != nil {
ctx.ServerError("CountRunners", err)
@@ -53,10 +166,29 @@ func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) {
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
ctx.Data["Page"] = pager
+
+ ctx.HTML(http.StatusOK, rCtx.RunnersTemplate)
}
-// RunnerDetails prepares data for runners edit page
-func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int64) {
+// RunnersEdit renders runner edit page for repository level
+func RunnersEdit(ctx *context.Context) {
+ ctx.Data["PageIsSharedSettingsRunners"] = true
+ ctx.Data["Title"] = ctx.Tr("actions.runners.edit_runner")
+ rCtx, err := getRunnersCtx(ctx)
+ if err != nil {
+ ctx.ServerError("getRunnersCtx", err)
+ return
+ }
+
+ page := ctx.FormInt("page")
+ if page <= 1 {
+ page = 1
+ }
+
+ runnerID := ctx.PathParamInt64("runnerid")
+ ownerID := rCtx.OwnerID
+ repoID := rCtx.RepoID
+
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
if err != nil {
ctx.ServerError("GetRunnerByID", err)
@@ -97,10 +229,22 @@ func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int
ctx.Data["Tasks"] = tasks
pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
ctx.Data["Page"] = pager
+
+ ctx.HTML(http.StatusOK, rCtx.RunnerEditTemplate)
}
-// RunnerDetailsEditPost response for edit runner details
-func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64, redirectTo string) {
+func RunnersEditPost(ctx *context.Context) {
+ rCtx, err := getRunnersCtx(ctx)
+ if err != nil {
+ ctx.ServerError("getRunnersCtx", err)
+ return
+ }
+
+ runnerID := ctx.PathParamInt64("runnerid")
+ ownerID := rCtx.OwnerID
+ repoID := rCtx.RepoID
+ redirectTo := rCtx.RedirectLink
+
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
if err != nil {
log.Warn("RunnerDetailsEditPost.GetRunnerByID failed: %v, url: %s", err, ctx.Req.URL)
@@ -129,10 +273,18 @@ func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64
ctx.Redirect(redirectTo)
}
-// RunnerResetRegistrationToken reset registration token
-func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, redirectTo string) {
- _, err := actions_model.NewRunnerToken(ctx, ownerID, repoID)
+func ResetRunnerRegistrationToken(ctx *context.Context) {
+ rCtx, err := getRunnersCtx(ctx)
if err != nil {
+ ctx.ServerError("getRunnersCtx", err)
+ return
+ }
+
+ ownerID := rCtx.OwnerID
+ repoID := rCtx.RepoID
+ redirectTo := rCtx.RedirectLink
+
+ if _, err := actions_model.NewRunnerToken(ctx, ownerID, repoID); err != nil {
ctx.ServerError("ResetRunnerRegistrationToken", err)
return
}
@@ -140,11 +292,28 @@ func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, r
ctx.JSONRedirect(redirectTo)
}
-// RunnerDeletePost response for deleting a runner
-func RunnerDeletePost(ctx *context.Context, runnerID int64,
- successRedirectTo, failedRedirectTo string,
-) {
- if err := actions_model.DeleteRunner(ctx, runnerID); err != nil {
+// RunnerDeletePost response for deleting runner
+func RunnerDeletePost(ctx *context.Context) {
+ rCtx, err := getRunnersCtx(ctx)
+ if err != nil {
+ ctx.ServerError("getRunnersCtx", err)
+ return
+ }
+
+ runner := findActionsRunner(ctx, rCtx)
+ if ctx.Written() {
+ return
+ }
+
+ if !runner.Editable(rCtx.OwnerID, rCtx.RepoID) {
+ ctx.NotFound("RunnerDeletePost", util.NewPermissionDeniedErrorf("no permission to delete this runner"))
+ return
+ }
+
+ successRedirectTo := rCtx.RedirectLink
+ failedRedirectTo := rCtx.RedirectLink + url.PathEscape(ctx.PathParam("runnerid"))
+
+ if err := actions_model.DeleteRunner(ctx, runner.ID); err != nil {
log.Warn("DeleteRunnerPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL)
ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed"))
@@ -158,3 +327,41 @@ func RunnerDeletePost(ctx *context.Context, runnerID int64,
ctx.JSONRedirect(successRedirectTo)
}
+
+func RedirectToDefaultSetting(ctx *context.Context) {
+ ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/runners")
+}
+
+func findActionsRunner(ctx *context.Context, rCtx *runnersCtx) *actions_model.ActionRunner {
+ runnerID := ctx.PathParamInt64("runnerid")
+ opts := &actions_model.FindRunnerOptions{
+ IDs: []int64{runnerID},
+ }
+ switch {
+ case rCtx.IsRepo:
+ opts.RepoID = rCtx.RepoID
+ if opts.RepoID == 0 {
+ panic("repoID is 0")
+ }
+ case rCtx.IsOrg, rCtx.IsUser:
+ opts.OwnerID = rCtx.OwnerID
+ if opts.OwnerID == 0 {
+ panic("ownerID is 0")
+ }
+ case rCtx.IsAdmin:
+ // do nothing
+ default:
+ panic("invalid actions runner context")
+ }
+
+ got, err := db.Find[actions_model.ActionRunner](ctx, opts)
+ if err != nil {
+ ctx.ServerError("FindRunner", err)
+ return nil
+ } else if len(got) == 0 {
+ ctx.NotFound("FindRunner", errors.New("runner not found"))
+ return nil
+ }
+
+ return got[0]
+}
diff --git a/routers/web/web.go b/routers/web/web.go
index 2745f7df41..65548073d2 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -467,11 +467,11 @@ func registerRoutes(m *web.Router) {
addSettingsRunnersRoutes := func() {
m.Group("/runners", func() {
- m.Get("", repo_setting.Runners)
- m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit).
- Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost)
- m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost)
- m.Post("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken)
+ m.Get("", shared_actions.Runners)
+ m.Combo("/{runnerid}").Get(shared_actions.RunnersEdit).
+ Post(web.Bind(forms.EditRunnerForm{}), shared_actions.RunnersEditPost)
+ m.Post("/{runnerid}/delete", shared_actions.RunnerDeletePost)
+ m.Post("/reset_registration_token", shared_actions.ResetRunnerRegistrationToken)
})
}
@@ -1147,7 +1147,7 @@ func registerRoutes(m *web.Router) {
})
})
m.Group("/actions", func() {
- m.Get("", repo_setting.RedirectToDefaultSetting)
+ m.Get("", shared_actions.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsSecretsRoutes()
addSettingsVariablesRoutes()
diff --git a/tests/integration/actions_runner_modify_test.go b/tests/integration/actions_runner_modify_test.go
new file mode 100644
index 0000000000..feb3bc0893
--- /dev/null
+++ b/tests/integration/actions_runner_modify_test.go
@@ -0,0 +1,151 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package integration
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "testing"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestActionsRunnerModify(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+
+ ctx := context.Background()
+
+ require.NoError(t, db.DeleteAllRecords("action_runner"))
+
+ user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
+ _ = actions_model.CreateRunner(ctx, &actions_model.ActionRunner{OwnerID: user2.ID, Name: "user2-runner", TokenHash: "a", UUID: "a"})
+ user2Runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{OwnerID: user2.ID, Name: "user2-runner"})
+ userWebURL := "/user/settings/actions/runners"
+
+ org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization})
+ require.NoError(t, actions_model.CreateRunner(ctx, &actions_model.ActionRunner{OwnerID: org3.ID, Name: "org3-runner", TokenHash: "b", UUID: "b"}))
+ org3Runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{OwnerID: org3.ID, Name: "org3-runner"})
+ orgWebURL := "/org/org3/settings/actions/runners"
+
+ repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
+ _ = actions_model.CreateRunner(ctx, &actions_model.ActionRunner{RepoID: repo1.ID, Name: "repo1-runner", TokenHash: "c", UUID: "c"})
+ repo1Runner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{RepoID: repo1.ID, Name: "repo1-runner"})
+ repoWebURL := "/user2/repo1/settings/actions/runners"
+
+ _ = actions_model.CreateRunner(ctx, &actions_model.ActionRunner{Name: "global-runner", TokenHash: "d", UUID: "d"})
+ globalRunner := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{Name: "global-runner"})
+ adminWebURL := "/-/admin/actions/runners"
+
+ sessionAdmin := loginUser(t, "user1")
+ sessionUser2 := loginUser(t, user2.Name)
+
+ doUpdate := func(t *testing.T, sess *TestSession, baseURL string, id int64, description string, expectedStatus int) {
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d", baseURL, id), map[string]string{
+ "_csrf": GetUserCSRFToken(t, sess),
+ "description": description,
+ })
+ sess.MakeRequest(t, req, expectedStatus)
+ }
+
+ doDelete := func(t *testing.T, sess *TestSession, baseURL string, id int64, expectedStatus int) {
+ req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/%d/delete", baseURL, id), map[string]string{
+ "_csrf": GetUserCSRFToken(t, sess),
+ })
+ sess.MakeRequest(t, req, expectedStatus)
+ }
+
+ assertDenied := func(t *testing.T, sess *TestSession, baseURL string, id int64) {
+ doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusNotFound)
+ doDelete(t, sess, baseURL, id, http.StatusNotFound)
+ v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
+ assert.Empty(t, v.Description)
+ }
+
+ assertSuccess := func(t *testing.T, sess *TestSession, baseURL string, id int64) {
+ doUpdate(t, sess, baseURL, id, "ChangedDescription", http.StatusSeeOther)
+ v := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunner{ID: id})
+ assert.Equal(t, "ChangedDescription", v.Description)
+ doDelete(t, sess, baseURL, id, http.StatusOK)
+ unittest.AssertNotExistsBean(t, &actions_model.ActionRunner{ID: id})
+ }
+
+ t.Run("UpdateUserRunner", func(t *testing.T) {
+ theRunner := user2Runner
+ t.Run("FromOrg", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, orgWebURL, theRunner.ID)
+ })
+ t.Run("FromRepo", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, repoWebURL, theRunner.ID)
+ })
+ t.Run("FromAdmin", func(t *testing.T) {
+ t.Skip("Admin can update any runner (not right but not too bad)")
+ assertDenied(t, sessionAdmin, adminWebURL, theRunner.ID)
+ })
+ })
+
+ t.Run("UpdateOrgRunner", func(t *testing.T) {
+ theRunner := org3Runner
+ t.Run("FromRepo", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, repoWebURL, theRunner.ID)
+ })
+ t.Run("FromUser", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, userWebURL, theRunner.ID)
+ })
+ t.Run("FromAdmin", func(t *testing.T) {
+ t.Skip("Admin can update any runner (not right but not too bad)")
+ assertDenied(t, sessionAdmin, adminWebURL, theRunner.ID)
+ })
+ })
+
+ t.Run("UpdateRepoRunner", func(t *testing.T) {
+ theRunner := repo1Runner
+ t.Run("FromOrg", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, orgWebURL, theRunner.ID)
+ })
+ t.Run("FromUser", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, userWebURL, theRunner.ID)
+ })
+ t.Run("FromAdmin", func(t *testing.T) {
+ t.Skip("Admin can update any runner (not right but not too bad)")
+ assertDenied(t, sessionAdmin, adminWebURL, theRunner.ID)
+ })
+ })
+
+ t.Run("UpdateGlobalRunner", func(t *testing.T) {
+ theRunner := globalRunner
+ t.Run("FromOrg", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, orgWebURL, theRunner.ID)
+ })
+ t.Run("FromUser", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, userWebURL, theRunner.ID)
+ })
+ t.Run("FromRepo", func(t *testing.T) {
+ assertDenied(t, sessionAdmin, repoWebURL, theRunner.ID)
+ })
+ })
+
+ t.Run("UpdateSuccess", func(t *testing.T) {
+ t.Run("User", func(t *testing.T) {
+ assertSuccess(t, sessionUser2, userWebURL, user2Runner.ID)
+ })
+ t.Run("Org", func(t *testing.T) {
+ assertSuccess(t, sessionAdmin, orgWebURL, org3Runner.ID)
+ })
+ t.Run("Repo", func(t *testing.T) {
+ assertSuccess(t, sessionUser2, repoWebURL, repo1Runner.ID)
+ })
+ t.Run("Admin", func(t *testing.T) {
+ assertSuccess(t, sessionAdmin, adminWebURL, globalRunner.ID)
+ })
+ })
+}