Jenkins授權卡?試試我的方法
使用過 Jenkins 的應該都用過 Role Strategy plugin 來管理用戶/組的權限管理,不是說它有多好用,而是沒太多選擇。
但是,不知道有沒有發現一個問題:當用戶的量級上去之后,打開授權界面經常會把瀏覽器卡死導致無法操作,為此我想是不是可以直接通過接口去授權,這樣就不必打開那么卡的頁面了。
通過 https://github.com/jenkinsci/role-strategy-plugin 學習,發現其有 REST API,提供一部分接口功能。
8ca930960a30b80f87512c5784aaeadd MD5
為此,我先寫了一個 role-srategy-sdk 的SDK,地址是:https://github.com/joker-bai/go-role-strategy-sdk。該 SDK 基本涵蓋了文檔中的所有接口。
- 權限模板管理 - 創建、刪除和查詢權限模板
- 角色管理 - 支持全局角色、項目角色和節點角色的完整生命周期管理
- 用戶/組分配 - 靈活的用戶和組角色分配功能
- 查詢功能 - 獲取角色信息、分配情況和匹配的作業/代理
- 多角色類型 - 支持 GlobalRole、ProjectRole 和 SlaveRole
SDK的使用
安裝
go get github.com/joker-bai/go-role-strategy-sdk初始化客戶端
package main
import (
"fmt"
"log"
rolestrategy "github.com/joker-bai/go-role-strategy-sdk"
)
func main() {
// 創建客戶端
client := rolestrategy.NewClient(
"https://your-jenkins-url",
"your-username",
"your-api-token",
)
}權限模板管理
// 添加權限模板
err := client.AddTemplate(
"developer",
"hudson.model.Item.Read,hudson.model.Item.Build",
true, // overwrite
)
if err != nil {
log.Fatal("AddTemplate:", err)
}
// 獲取權限模板
template, err := client.GetTemplate("developer")
if err != nil {
log.Fatal("GetTemplate:", err)
}
fmt.Printf("Template: %+v\n", template)
// 刪除權限模板
err = client.RemoveTemplates([]string{"developer"}, false)
if err != nil {
log.Fatal("RemoveTemplates:", err)
}角色管理
// 添加全局角色
err := client.AddRole(
rolestrategy.GlobalRole,
"dev-lead",
"hudson.model.Hudson.Administer",
true, // overwrite
"", // pattern (項目角色使用)
"", // template
)
if err != nil {
log.Fatal("AddRole:", err)
}
// 添加項目角色(帶模式匹配)
err = client.AddRole(
rolestrategy.ProjectRole,
"project-dev",
"hudson.model.Item.Read,hudson.model.Item.Build",
true,
"dev-.*", // 匹配以 dev- 開頭的項目
"",
)
// 獲取角色信息
role, err := client.GetRole(rolestrategy.GlobalRole, "dev-lead")
if err != nil {
log.Fatal("GetRole:", err)
}
fmt.Printf("Role: %+v\n", role)
// 刪除角色
err = client.RemoveRoles(rolestrategy.GlobalRole, []string{"dev-lead"})
if err != nil {
log.Fatal("RemoveRoles:", err)
}用戶和組分配
// 分配用戶角色
err := client.AssignUserRole(rolestrategy.GlobalRole, "dev-lead", "alice")
if err != nil {
log.Fatal("AssignUserRole:", err)
}
// 分配組角色
err = client.AssignGroupRole(rolestrategy.ProjectRole, "project-dev", "developers")
if err != nil {
log.Fatal("AssignGroupRole:", err)
}
// 取消用戶角色分配
err = client.UnassignUserRole(rolestrategy.GlobalRole, "dev-lead", "alice")
if err != nil {
log.Fatal("UnassignUserRole:", err)
}
// 取消組角色分配
err = client.UnassignGroupRole(rolestrategy.ProjectRole, "project-dev", "developers")
if err != nil {
log.Fatal("UnassignGroupRole:", err)
}查詢功能
// 獲取所有角色
allRoles, err := client.GetAllRoles(rolestrategy.GlobalRole)
if err != nil {
log.Fatal("GetAllRoles:", err)
}
fmt.Printf("All Roles: %+v\n", allRoles)
// 獲取角色分配情況
assignments, err := client.GetRoleAssignments(rolestrategy.GlobalRole)
if err != nil {
log.Fatal("GetRoleAssignments:", err)
}
fmt.Printf("Assignments: %+v\n", assignments)
// 獲取全局角色名列表
globalRoles, err := client.GetGlobalRoleNames()
if err != nil {
log.Fatal("GetGlobalRoleNames:", err)
}
fmt.Println("Global Roles:", globalRoles)
// 獲取項目角色名列表
projectRoles, err := client.GetProjectRoleNames()
if err != nil {
log.Fatal("GetProjectRoleNames:", err)
}
fmt.Println("Project Roles:", projectRoles)
// 獲取匹配的作業
matchingJobs, err := client.GetMatchingJobs("dev-.*", 10)
if err != nil {
log.Fatal("GetMatchingJobs:", err)
}
fmt.Printf("Matching Jobs: %+v\n", matchingJobs)
// 獲取匹配的代理
matchingAgents, err := client.GetMatchingAgents("agent-.*", 10)
if err != nil {
log.Fatal("GetMatchingAgents:", err)
}
fmt.Printf("Matching Agents: %+v\n", matchingAgents)開發一個 Web UI
我們有了 SDK,現在可以基于 SDK 開發一個簡單的 Web UI,我這里后端使用 Gin 框架,前端就簡單寫個 Html 實現。
對于后端,我們這里只設計了四個接口,它們是:
/api/users:獲取用戶信息/api/roles/global:獲取全局角色/api/roles/project:獲取項目角色/api/users/assign:用戶授權
完整的后端代碼如下:
// main.go
package main
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
"github.com/gin-gonic/gin"
rolestrategy "github.com/joker-bai/go-role-strategy-sdk"
)
// Request models
type AssignRequest struct {
Username string `json:"username" binding:"required"`
GlobalRoles []string`json:"globalRoles"`
ProjectRoles []string`json:"projectRoles"`
}
type UserListResponse struct {
Users []UserRoles `json:"users"`
Total int `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
TotalPages int `json:"totalPages"`
}
type RoleListResponse []string
type UserRoles struct {
Username string `json:"username"`
GlobalRoles []string`json:"globalRoles"`
ProjectRoles []string`json:"projectRoles"`
}
var jenkinsClient *rolestrategy.Client
func main() {
jenkinsClient = rolestrategy.NewClient(
"http://127.0.0.1:8080/jenkins",
"admin",
"your-api-token",
)
r := gin.Default()
r.StaticFile("/", "./web/index.html")
api := r.Group("/api")
{
api.GET("/users", getUsersHandler)
api.GET("/roles/global", getGlobalRolesHandler)
api.GET("/roles/project", getProjectRolesHandler)
api.POST("/users/assign", assignRolesHandler)
}
log.Println("Server starting on :8080")
r.Run(":8080")
}
func getUsersHandler(c *gin.Context) {
var users []UserRoles
userMap := make(map[string]*UserRoles)
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
search := strings.TrimSpace(c.DefaultQuery("search", ""))
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 10
}
// 從 Jenkins 獲取數據
processRoleMap := func(roleType rolestrategy.RoleType, addToGlobal bool) {
roleMap, err := jenkinsClient.GetAllRoles(roleType)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to get %s roles: %v", roleType, err)})
c.Abort()
return
}
for roleName, sids := range roleMap {
for _, sid := range sids {
if sid.Type != "USER" {
continue
}
if _, exists := userMap[sid.SID]; !exists {
userMap[sid.SID] = &UserRoles{
Username: sid.SID,
GlobalRoles: []string{},
ProjectRoles: []string{},
}
}
if addToGlobal {
userMap[sid.SID].GlobalRoles = append(userMap[sid.SID].GlobalRoles, roleName)
} else {
userMap[sid.SID].ProjectRoles = append(userMap[sid.SID].ProjectRoles, roleName)
}
}
}
}
processRoleMap(rolestrategy.GlobalRole, true)
if c.IsAborted() {
return
}
processRoleMap(rolestrategy.ProjectRole, false)
if c.IsAborted() {
return
}
for _, user := range userMap {
users = append(users, *user)
}
// 應用搜索過濾
if search != "" {
var filteredUsers []UserRoles
for _, user := range users {
if strings.Contains(strings.ToLower(user.Username), strings.ToLower(search)) {
filteredUsers = append(filteredUsers, user)
}
}
users = filteredUsers
}
total := len(users)
totalPages := (total + pageSize - 1) / pageSize
start := (page - 1) * pageSize
end := start + pageSize
if start >= total {
users = []UserRoles{}
} else {
if end > total {
end = total
}
users = users[start:end]
}
c.JSON(http.StatusOK, UserListResponse{
Users: users,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
})
}
func getGlobalRolesHandler(c *gin.Context) {
roles, err := jenkinsClient.GetGlobalRoleNames()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, RoleListResponse(roles))
}
func getProjectRolesHandler(c *gin.Context) {
roles, err := jenkinsClient.GetProjectRoleNames()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, RoleListResponse(roles))
}
func assignRolesHandler(c *gin.Context) {
var req AssignRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 獲取用戶當前的角色
globalMap, err := jenkinsClient.GetAllRoles(rolestrategy.GlobalRole)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get current global roles: " + err.Error()})
return
}
projectMap, err := jenkinsClient.GetAllRoles(rolestrategy.ProjectRole)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get current project roles: " + err.Error()})
return
}
// 構建當前角色集合
currentGlobalRoles := make(map[string]bool)
for roleName, sids := range globalMap {
for _, sid := range sids {
if sid.Type == "USER" && sid.SID == req.Username {
currentGlobalRoles[roleName] = true
}
}
}
currentProjectRoles := make(map[string]bool)
for roleName, sids := range projectMap {
for _, sid := range sids {
if sid.Type == "USER" && sid.SID == req.Username {
currentProjectRoles[roleName] = true
}
}
}
// 分配新角色
for _, roleName := range req.GlobalRoles {
if !currentGlobalRoles[roleName] {
if err := jenkinsClient.AssignUserRole(rolestrategy.GlobalRole, roleName, req.Username); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to assign global role: " + err.Error()})
return
}
}
}
for _, roleName := range req.ProjectRoles {
if !currentProjectRoles[roleName] {
if err := jenkinsClient.AssignUserRole(rolestrategy.ProjectRole, roleName, req.Username); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to assign project role: " + err.Error()})
return
}
}
}
// 移除不再擁有的角色
for roleName := range currentGlobalRoles {
if !contains(req.GlobalRoles, roleName) {
if err := jenkinsClient.UnassignUserRole(rolestrategy.GlobalRole, roleName, req.Username); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unassign global role: " + err.Error()})
return
}
}
}
for roleName := range currentProjectRoles {
if !contains(req.ProjectRoles, roleName) {
if err := jenkinsClient.UnassignUserRole(rolestrategy.ProjectRole, roleName, req.Username); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unassign project role: " + err.Error()})
return
}
}
}
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Roles updated successfully"})
}
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
returntrue
}
}
returnfalse
}前端代碼就不寫了,直接到 倉庫 查看。
實際的效果如下:
ff05662851d9db0302a62ec564a96b5b MD5
用戶詳情,可以查看目前該用戶擁有的權限:
971f6b099af019c29ed8856a7b1bcb5d MD5
點擊修改,可以直接修改用戶的權限:
91dcad6460cbdb0c730c3a3843cce753 MD5
對于新用戶,也可以直接授權:
7ac7f6d1b3f1541474cae991a45adef6 MD5
這樣就不用害怕 Jenkins 卡死了。同時,我也將代碼傳到了 Github,有興趣的可以了解一下。
上面的代碼僅僅是一個簡單的 demo,可以對其進行擴充,比如:
- 用戶接入企業用戶中心,判斷該用戶是否存在
- 增加多Jenkins管理,操作者可以切換不同的Jenkins進行授權操作
當然,目前的界面也比較丑,不過,就這樣吧......




























