From 7794ff087419b8a304203f63432f4159806537fa Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 10 Feb 2025 23:25:56 -0800 Subject: [PATCH] Enhance routers for the Actions runner operations (#33549) (#33555) Backport #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: Jason Song Co-authored-by: wxiaoguang --- models/actions/runner.go | 9 + routers/web/repo/setting/runners.go | 187 -------------- routers/web/shared/actions/runners.go | 235 ++++++++++++++++-- routers/web/web.go | 12 +- .../integration/actions_runner_modify_test.go | 151 +++++++++++ 5 files changed, 387 insertions(+), 207 deletions(-) delete mode 100644 routers/web/repo/setting/runners.go create mode 100644 tests/integration/actions_runner_modify_test.go diff --git a/models/actions/runner.go b/models/actions/runner.go index b35a76680c..c4e5f9d121 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 3141d8f42a..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/base" - "code.gitea.io/gitea/modules/setting" - 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 base.TplName = "repo/settings/actions" - tplOrgRunners base.TplName = "org/settings/actions" - tplAdminRunners base.TplName = "admin/actions" - tplUserRunners base.TplName = "user/settings/actions" - tplRepoRunnerEdit base.TplName = "repo/settings/runner_edit" - tplOrgRunnerEdit base.TplName = "org/settings/runners_edit" - tplAdminRunnerEdit base.TplName = "admin/runners/edit" - tplUserRunnerEdit base.TplName = "user/settings/runner_edit" -) - -type runnersCtx struct { - OwnerID int64 - RepoID int64 - IsRepo bool - IsOrg bool - IsAdmin bool - IsUser bool - RunnersTemplate base.TplName - RunnerEditTemplate base.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..2266232ebe 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/base" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "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 base.TplName = "repo/settings/actions" + tplOrgRunners base.TplName = "org/settings/actions" + tplAdminRunners base.TplName = "admin/actions" + tplUserRunners base.TplName = "user/settings/actions" + tplRepoRunnerEdit base.TplName = "repo/settings/runner_edit" + tplOrgRunnerEdit base.TplName = "org/settings/runners_edit" + tplAdminRunnerEdit base.TplName = "admin/runners/edit" + tplUserRunnerEdit base.TplName = "user/settings/runner_edit" +) + +type runnersCtx struct { + OwnerID int64 + RepoID int64 + IsRepo bool + IsOrg bool + IsAdmin bool + IsUser bool + RunnersTemplate base.TplName + RunnerEditTemplate base.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 bf9f50139f..7035544897 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -460,11 +460,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) }) } @@ -1133,7 +1133,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) + }) + }) +}