mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	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>
This commit is contained in:
		| @@ -167,6 +167,7 @@ func init() { | |||||||
|  |  | ||||||
| type FindRunnerOptions struct { | type FindRunnerOptions struct { | ||||||
| 	db.ListOptions | 	db.ListOptions | ||||||
|  | 	IDs           []int64 | ||||||
| 	RepoID        int64 | 	RepoID        int64 | ||||||
| 	OwnerID       int64 // it will be ignored if RepoID is set | 	OwnerID       int64 // it will be ignored if RepoID is set | ||||||
| 	Sort          string | 	Sort          string | ||||||
| @@ -178,6 +179,14 @@ type FindRunnerOptions struct { | |||||||
| func (opts FindRunnerOptions) ToConds() builder.Cond { | func (opts FindRunnerOptions) ToConds() builder.Cond { | ||||||
| 	cond := builder.NewCond() | 	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 { | 	if opts.RepoID > 0 { | ||||||
| 		c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID}) | 		c := builder.NewCond().And(builder.Eq{"repo_id": opts.RepoID}) | ||||||
| 		if opts.WithAvailable { | 		if opts.WithAvailable { | ||||||
|   | |||||||
| @@ -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") |  | ||||||
| } |  | ||||||
| @@ -5,18 +5,131 @@ package actions | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  |  | ||||||
| 	actions_model "code.gitea.io/gitea/models/actions" | 	actions_model "code.gitea.io/gitea/models/actions" | ||||||
| 	"code.gitea.io/gitea/models/db" | 	"code.gitea.io/gitea/models/db" | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"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/util" | ||||||
| 	"code.gitea.io/gitea/modules/web" | 	"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/context" | ||||||
| 	"code.gitea.io/gitea/services/forms" | 	"code.gitea.io/gitea/services/forms" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // RunnersList prepares data for runners list | const ( | ||||||
| func RunnersList(ctx *context.Context, opts actions_model.FindRunnerOptions) { | 	// 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) | 	runners, count, err := db.FindAndCount[actions_model.ActionRunner](ctx, opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("CountRunners", err) | 		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) | 	pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) | ||||||
|  |  | ||||||
| 	ctx.Data["Page"] = pager | 	ctx.Data["Page"] = pager | ||||||
|  |  | ||||||
|  | 	ctx.HTML(http.StatusOK, rCtx.RunnersTemplate) | ||||||
| } | } | ||||||
|  |  | ||||||
| // RunnerDetails prepares data for runners edit page | // RunnersEdit renders runner edit page for repository level | ||||||
| func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int64) { | 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) | 	runner, err := actions_model.GetRunnerByID(ctx, runnerID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		ctx.ServerError("GetRunnerByID", err) | 		ctx.ServerError("GetRunnerByID", err) | ||||||
| @@ -97,10 +229,22 @@ func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int | |||||||
| 	ctx.Data["Tasks"] = tasks | 	ctx.Data["Tasks"] = tasks | ||||||
| 	pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) | 	pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5) | ||||||
| 	ctx.Data["Page"] = pager | 	ctx.Data["Page"] = pager | ||||||
|  |  | ||||||
|  | 	ctx.HTML(http.StatusOK, rCtx.RunnerEditTemplate) | ||||||
| } | } | ||||||
|  |  | ||||||
| // RunnerDetailsEditPost response for edit runner details | func RunnersEditPost(ctx *context.Context) { | ||||||
| func RunnerDetailsEditPost(ctx *context.Context, runnerID, ownerID, repoID int64, redirectTo string) { | 	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) | 	runner, err := actions_model.GetRunnerByID(ctx, runnerID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Warn("RunnerDetailsEditPost.GetRunnerByID failed: %v, url: %s", err, ctx.Req.URL) | 		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) | 	ctx.Redirect(redirectTo) | ||||||
| } | } | ||||||
|  |  | ||||||
| // RunnerResetRegistrationToken reset registration token | func ResetRunnerRegistrationToken(ctx *context.Context) { | ||||||
| func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, redirectTo string) { | 	rCtx, err := getRunnersCtx(ctx) | ||||||
| 	_, err := actions_model.NewRunnerToken(ctx, ownerID, repoID) |  | ||||||
| 	if err != nil { | 	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) | 		ctx.ServerError("ResetRunnerRegistrationToken", err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| @@ -140,11 +292,28 @@ func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, r | |||||||
| 	ctx.JSONRedirect(redirectTo) | 	ctx.JSONRedirect(redirectTo) | ||||||
| } | } | ||||||
|  |  | ||||||
| // RunnerDeletePost response for deleting a runner | // RunnerDeletePost response for deleting runner | ||||||
| func RunnerDeletePost(ctx *context.Context, runnerID int64, | func RunnerDeletePost(ctx *context.Context) { | ||||||
| 	successRedirectTo, failedRedirectTo string, | 	rCtx, err := getRunnersCtx(ctx) | ||||||
| ) { | 	if err != nil { | ||||||
| 	if err := actions_model.DeleteRunner(ctx, runnerID); 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) | 		log.Warn("DeleteRunnerPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL) | ||||||
| 		ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed")) | 		ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed")) | ||||||
|  |  | ||||||
| @@ -158,3 +327,41 @@ func RunnerDeletePost(ctx *context.Context, runnerID int64, | |||||||
|  |  | ||||||
| 	ctx.JSONRedirect(successRedirectTo) | 	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] | ||||||
|  | } | ||||||
|   | |||||||
| @@ -467,11 +467,11 @@ func registerRoutes(m *web.Router) { | |||||||
|  |  | ||||||
| 	addSettingsRunnersRoutes := func() { | 	addSettingsRunnersRoutes := func() { | ||||||
| 		m.Group("/runners", func() { | 		m.Group("/runners", func() { | ||||||
| 			m.Get("", repo_setting.Runners) | 			m.Get("", shared_actions.Runners) | ||||||
| 			m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit). | 			m.Combo("/{runnerid}").Get(shared_actions.RunnersEdit). | ||||||
| 				Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost) | 				Post(web.Bind(forms.EditRunnerForm{}), shared_actions.RunnersEditPost) | ||||||
| 			m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost) | 			m.Post("/{runnerid}/delete", shared_actions.RunnerDeletePost) | ||||||
| 			m.Post("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken) | 			m.Post("/reset_registration_token", shared_actions.ResetRunnerRegistrationToken) | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -1147,7 +1147,7 @@ func registerRoutes(m *web.Router) { | |||||||
| 			}) | 			}) | ||||||
| 		}) | 		}) | ||||||
| 		m.Group("/actions", func() { | 		m.Group("/actions", func() { | ||||||
| 			m.Get("", repo_setting.RedirectToDefaultSetting) | 			m.Get("", shared_actions.RedirectToDefaultSetting) | ||||||
| 			addSettingsRunnersRoutes() | 			addSettingsRunnersRoutes() | ||||||
| 			addSettingsSecretsRoutes() | 			addSettingsSecretsRoutes() | ||||||
| 			addSettingsVariablesRoutes() | 			addSettingsVariablesRoutes() | ||||||
|   | |||||||
							
								
								
									
										151
									
								
								tests/integration/actions_runner_modify_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								tests/integration/actions_runner_modify_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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) | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user