0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-11-10 02:38:07 -05:00

Support actions and reusable workflows from private repos (#32562)

Resolve https://gitea.com/gitea/act_runner/issues/102

This PR allows administrators of a private repository to specify some
collaborative owners. The repositories of collaborative owners will be
allowed to access this repository's actions and workflows.

Settings for private repos:


![image](https://github.com/user-attachments/assets/e591c877-f94d-48fb-82f3-3b051f21557e)

---

This PR also moves "Enable Actions" setting to `Actions > General` page

<img width="960" alt="image"
src="https://github.com/user-attachments/assets/49337ec2-afb1-4a67-8516-5c9ef0ce05d4"
/>

<img width="960" alt="image"
src="https://github.com/user-attachments/assets/f58ee6d5-17f9-4180-8760-a78e859f1c37"
/>

---------

Signed-off-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: ChristopherHX <christopher.homberger@web.de>
This commit is contained in:
Zettat123
2025-10-25 11:37:33 -06:00
committed by GitHub
parent 5454fdacd4
commit c9beb0b01f
30 changed files with 408 additions and 45 deletions

View File

@@ -103,7 +103,7 @@ func GetAllOrgs(ctx *context.APIContext) {
users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
Types: []user_model.UserType{user_model.UserTypeOrganization},
OrderBy: db.SearchOrderByAlphabetically,
ListOptions: listOptions,
Visible: []api.VisibleType{api.VisibleTypePublic, api.VisibleTypeLimited, api.VisibleTypePrivate},

View File

@@ -425,7 +425,7 @@ func SearchUsers(ctx *context.APIContext) {
users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
LoginName: ctx.FormTrim("login_name"),
SourceID: ctx.FormInt64("source_id"),
OrderBy: db.SearchOrderByAlphabetically,

View File

@@ -202,7 +202,7 @@ func GetAll(ctx *context.APIContext) {
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
ListOptions: listOptions,
Type: user_model.UserTypeOrganization,
Types: []user_model.UserType{user_model.UserTypeOrganization},
OrderBy: db.SearchOrderByAlphabetically,
Visible: vMode,
})

View File

@@ -77,7 +77,7 @@ func Search(ctx *context.APIContext) {
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
UID: uid,
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
SearchByEmail: true,
Visible: visible,
ListOptions: listOptions,

View File

@@ -29,7 +29,7 @@ func Organizations(ctx *context.Context) {
explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
Types: []user_model.UserType{user_model.UserTypeOrganization},
IncludeReserved: true, // administrator needs to list all accounts include reserved
ListOptions: db.ListOptions{
PageSize: setting.UI.Admin.OrgPagingNum,

View File

@@ -67,7 +67,7 @@ func Users(ctx *context.Context) {
explore.RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
ListOptions: db.ListOptions{
PageSize: setting.UI.Admin.UserPagingNum,
},

View File

@@ -46,7 +46,7 @@ func Organizations(ctx *context.Context) {
RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
Types: []user_model.UserType{user_model.UserTypeOrganization},
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
Visible: visibleTypes,

View File

@@ -153,7 +153,7 @@ func Users(ctx *context.Context) {
RenderUserSearch(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
IsActive: optional.Some(true),
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},

View File

@@ -69,7 +69,7 @@ func HomeSitemap(ctx *context.Context) {
m := sitemap.NewSitemapIndex()
if !setting.Service.Explore.DisableUsersPage {
_, cnt, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
ListOptions: db.ListOptions{PageSize: 1},
IsActive: optional.Some(true),
Visible: []structs.VisibleType{structs.VisibleTypePublic},

View File

@@ -191,7 +191,7 @@ func httpBase(ctx *context.Context) *serviceHandler {
taskID := ctx.Data["ActionsTaskID"].(int64)
p, err := access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
if err != nil {
ctx.ServerError("GetUserRepoPermission", err)
ctx.ServerError("GetActionsUserRepoPermission", err)
return nil
}

View File

@@ -0,0 +1,121 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"errors"
"net/http"
"strings"
repo_model "code.gitea.io/gitea/models/repo"
unit_model "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository"
)
const tplRepoActionsGeneralSettings templates.TplName = "repo/settings/actions"
func ActionsGeneralSettings(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("actions.general")
ctx.Data["PageType"] = "general"
ctx.Data["PageIsActionsSettingsGeneral"] = true
actionsUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeActions)
if err != nil && !repo_model.IsErrUnitTypeNotExist(err) {
ctx.ServerError("GetUnit", err)
return
}
if actionsUnit == nil { // no actions unit
ctx.HTML(http.StatusOK, tplRepoActionsGeneralSettings)
return
}
if ctx.Repo.Repository.IsPrivate {
collaborativeOwnerIDs := actionsUnit.ActionsConfig().CollaborativeOwnerIDs
collaborativeOwners, err := user_model.GetUsersByIDs(ctx, collaborativeOwnerIDs)
if err != nil {
ctx.ServerError("GetUsersByIDs", err)
return
}
ctx.Data["CollaborativeOwners"] = collaborativeOwners
}
ctx.HTML(http.StatusOK, tplRepoActionsGeneralSettings)
}
func ActionsUnitPost(ctx *context.Context) {
redirectURL := ctx.Repo.RepoLink + "/settings/actions/general"
enableActionsUnit := ctx.FormBool("enable_actions")
repo := ctx.Repo.Repository
var err error
if enableActionsUnit && !unit_model.TypeActions.UnitGlobalDisabled() {
err = repo_service.UpdateRepositoryUnits(ctx, repo, []repo_model.RepoUnit{newRepoUnit(repo, unit_model.TypeActions, nil)}, nil)
} else if !unit_model.TypeActions.UnitGlobalDisabled() {
err = repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit_model.Type{unit_model.TypeActions})
}
if err != nil {
ctx.ServerError("UpdateRepositoryUnits", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
ctx.Redirect(redirectURL)
}
func AddCollaborativeOwner(ctx *context.Context) {
name := strings.ToLower(ctx.FormString("collaborative_owner"))
ownerID, err := user_model.GetUserOrOrgIDByName(ctx, name)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
ctx.JSONErrorNotFound()
} else {
ctx.ServerError("GetUserOrOrgIDByName", err)
}
return
}
actionsUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeActions)
if err != nil {
ctx.ServerError("GetUnit", err)
return
}
actionsCfg := actionsUnit.ActionsConfig()
actionsCfg.AddCollaborativeOwner(ownerID)
if err := repo_model.UpdateRepoUnit(ctx, actionsUnit); err != nil {
ctx.ServerError("UpdateRepoUnit", err)
return
}
ctx.JSONOK()
}
func DeleteCollaborativeOwner(ctx *context.Context) {
ownerID := ctx.FormInt64("id")
actionsUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeActions)
if err != nil {
ctx.ServerError("GetUnit", err)
return
}
actionsCfg := actionsUnit.ActionsConfig()
if !actionsCfg.IsCollaborativeOwner(ownerID) {
ctx.Flash.Error(ctx.Tr("actions.general.collaborative_owner_not_exist"))
ctx.JSONErrorNotFound()
return
}
actionsCfg.RemoveCollaborativeOwner(ownerID)
if err := repo_model.UpdateRepoUnit(ctx, actionsUnit); err != nil {
ctx.ServerError("UpdateRepoUnit", err)
return
}
ctx.JSONOK()
}

View File

@@ -613,12 +613,6 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages)
}
if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() {
units = append(units, newRepoUnit(repo, unit_model.TypeActions, nil))
} else if !unit_model.TypeActions.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions)
}
if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() {
units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, &repo_model.PullRequestsConfig{
IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace,

View File

@@ -16,10 +16,14 @@ import (
// SearchCandidates searches candidate users for dropdown list
func SearchCandidates(ctx *context.Context) {
searchUserTypes := []user_model.UserType{user_model.UserTypeIndividual}
if ctx.FormBool("orgs") {
searchUserTypes = append(searchUserTypes, user_model.UserTypeOrganization)
}
users, _, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
Type: user_model.UserTypeIndividual,
Types: searchUserTypes,
IsActive: optional.Some(true),
ListOptions: db.ListOptions{PageSize: setting.UI.MembersPagingNum},
})

View File

@@ -1159,11 +1159,21 @@ func registerWebRoutes(m *web.Router) {
m.Post("/{lid}/unlock", repo_setting.LFSUnlock)
})
})
m.Group("/actions/general", func() {
m.Get("", repo_setting.ActionsGeneralSettings)
m.Post("/actions_unit", repo_setting.ActionsUnitPost)
})
m.Group("/actions", func() {
m.Get("", shared_actions.RedirectToDefaultSetting)
addSettingsRunnersRoutes()
addSettingsSecretsRoutes()
addSettingsVariablesRoutes()
m.Group("/general", func() {
m.Group("/collaborative_owner", func() {
m.Post("/add", repo_setting.AddCollaborativeOwner)
m.Post("/delete", repo_setting.DeleteCollaborativeOwner)
})
})
}, actions.MustEnableActions)
// the follow handler must be under "settings", otherwise this incomplete repo can't be accessed
m.Group("/migrate", func() {