mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-03-16 17:54:07 +01:00
Implement org/user agents (#3539)
This commit is contained in:
@@ -15,12 +15,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
@@ -28,6 +26,10 @@ import (
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
//
|
||||
// Global Agents.
|
||||
//
|
||||
|
||||
// GetAgents
|
||||
//
|
||||
// @Summary List agents
|
||||
@@ -50,14 +52,14 @@ func GetAgents(c *gin.Context) {
|
||||
// GetAgent
|
||||
//
|
||||
// @Summary Get an agent
|
||||
// @Router /agents/{agent} [get]
|
||||
// @Router /agents/{agent_id} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param agent path int true "the agent's id"
|
||||
func GetAgent(c *gin.Context) {
|
||||
agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64)
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
@@ -74,14 +76,14 @@ func GetAgent(c *gin.Context) {
|
||||
// GetAgentTasks
|
||||
//
|
||||
// @Summary List agent tasks
|
||||
// @Router /agents/{agent}/tasks [get]
|
||||
// @Router /agents/{agent_id}/tasks [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Task
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param agent path int true "the agent's id"
|
||||
func GetAgentTasks(c *gin.Context) {
|
||||
agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64)
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
@@ -107,7 +109,7 @@ func GetAgentTasks(c *gin.Context) {
|
||||
// PatchAgent
|
||||
//
|
||||
// @Summary Update an agent
|
||||
// @Router /agents/{agent} [patch]
|
||||
// @Router /agents/{agent_id} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
// @Tags Agents
|
||||
@@ -124,7 +126,7 @@ func PatchAgent(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64)
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
@@ -135,6 +137,8 @@ func PatchAgent(c *gin.Context) {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Update allowed fields
|
||||
agent.Name = in.Name
|
||||
agent.NoSchedule = in.NoSchedule
|
||||
if agent.NoSchedule {
|
||||
@@ -172,11 +176,10 @@ func PostAgent(c *gin.Context) {
|
||||
|
||||
agent := &model.Agent{
|
||||
Name: in.Name,
|
||||
NoSchedule: in.NoSchedule,
|
||||
OwnerID: user.ID,
|
||||
Token: base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32),
|
||||
),
|
||||
OrgID: model.IDNotSet,
|
||||
NoSchedule: in.NoSchedule,
|
||||
Token: model.GenerateNewAgentToken(),
|
||||
}
|
||||
if err = store.FromContext(c).AgentCreate(agent); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
@@ -188,7 +191,7 @@ func PostAgent(c *gin.Context) {
|
||||
// DeleteAgent
|
||||
//
|
||||
// @Summary Delete an agent
|
||||
// @Router /agents/{agent} [delete]
|
||||
// @Router /agents/{agent_id} [delete]
|
||||
// @Produce plain
|
||||
// @Success 200
|
||||
// @Tags Agents
|
||||
@@ -197,7 +200,7 @@ func PostAgent(c *gin.Context) {
|
||||
func DeleteAgent(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64)
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
@@ -227,3 +230,194 @@ func DeleteAgent(c *gin.Context) {
|
||||
}
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
//
|
||||
// Org/User Agents.
|
||||
//
|
||||
|
||||
// PostOrgAgent
|
||||
//
|
||||
// @Summary Create a new organization-scoped agent
|
||||
// @Description Creates a new agent with a random token, scoped to the specified organization
|
||||
// @Router /orgs/{org_id}/agents [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path int true "the organization's id"
|
||||
// @Param agent body Agent true "the agent's data (only 'name' and 'no_schedule' are read)"
|
||||
func PostOrgAgent(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
user := session.User(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Invalid organization ID")
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Agent)
|
||||
err = c.Bind(in)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
agent := &model.Agent{
|
||||
Name: in.Name,
|
||||
OwnerID: user.ID,
|
||||
OrgID: orgID,
|
||||
NoSchedule: in.NoSchedule,
|
||||
Token: model.GenerateNewAgentToken(),
|
||||
}
|
||||
|
||||
if err = _store.AgentCreate(agent); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agent)
|
||||
}
|
||||
|
||||
// GetOrgAgents
|
||||
//
|
||||
// @Summary List agents for an organization
|
||||
// @Router /orgs/{org_id}/agents [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Agent
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path int true "the organization's id"
|
||||
// @Param page query int false "for response pagination, page offset number" default(1)
|
||||
// @Param perPage query int false "for response pagination, max items per page" default(50)
|
||||
func GetOrgAgents(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
agents, err := _store.AgentListForOrg(orgID, session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting agent list. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agents)
|
||||
}
|
||||
|
||||
// PatchOrgAgent
|
||||
//
|
||||
// @Summary Update an organization-scoped agent
|
||||
// @Router /orgs/{org_id}/agents/{agent_id} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Agent
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path int true "the organization's id"
|
||||
// @Param agent_id path int true "the agent's id"
|
||||
// @Param agent body Agent true "the agent's updated data"
|
||||
func PatchOrgAgent(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Invalid organization ID")
|
||||
return
|
||||
}
|
||||
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Invalid agent ID")
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := _store.AgentFind(agentID)
|
||||
if err != nil {
|
||||
c.String(http.StatusNotFound, "Agent not found")
|
||||
return
|
||||
}
|
||||
|
||||
if agent.OrgID != orgID {
|
||||
c.String(http.StatusBadRequest, "Agent does not belong to this organization")
|
||||
return
|
||||
}
|
||||
|
||||
in := new(model.Agent)
|
||||
if err := c.Bind(in); err != nil {
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Update allowed fields
|
||||
agent.Name = in.Name
|
||||
agent.NoSchedule = in.NoSchedule
|
||||
if agent.NoSchedule {
|
||||
server.Config.Services.Queue.KickAgentWorkers(agent.ID)
|
||||
}
|
||||
|
||||
if err := _store.AgentUpdate(agent); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agent)
|
||||
}
|
||||
|
||||
// DeleteOrgAgent
|
||||
//
|
||||
// @Summary Delete an organization-scoped agent
|
||||
// @Router /orgs/{org_id}/agents/{agent_id} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Agents
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param org_id path int true "the organization's id"
|
||||
// @Param agent_id path int true "the agent's id"
|
||||
func DeleteOrgAgent(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Invalid organization ID")
|
||||
return
|
||||
}
|
||||
|
||||
agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Invalid agent ID")
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := _store.AgentFind(agentID)
|
||||
if err != nil {
|
||||
c.String(http.StatusNotFound, "Agent not found")
|
||||
return
|
||||
}
|
||||
|
||||
if agent.OrgID != orgID {
|
||||
c.String(http.StatusBadRequest, "Agent does not belong to this organization")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the agent has any running tasks
|
||||
info := server.Config.Services.Queue.Info(c)
|
||||
for _, task := range info.Running {
|
||||
if task.AgentID == agent.ID {
|
||||
c.String(http.StatusConflict, "Agent has running tasks")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Kick workers to remove the agent from the queue
|
||||
server.Config.Services.Queue.KickAgentWorkers(agent.ID)
|
||||
|
||||
if err := _store.AgentDelete(agent); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error deleting agent. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user