Files
godoxy-yusing/internal/api/v1/agent/create.go
yusing 8b5cb947c8 refactor(agent): extract agent pool and HTTP utilities to dedicated package
Moved non-agent-specific logic from agent/pkg/agent/ to internal/agentpool/:
- pool.go: Agent pool management (Get, Add, Remove, List, Iter, etc.)
- http_requests.go: HTTP utilities (health checks, forwarding, websockets, reverse proxy)
- agent.go: Agent struct with HTTP client management

This separates general-purpose pool management from agent-specific configuration,
improving code organization and making the agent package focused on agent config only.
2026-01-08 12:02:21 +08:00

109 lines
3.3 KiB
Go

package agentapi
import (
"net"
"net/http"
"strconv"
_ "embed"
"github.com/gin-gonic/gin"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/internal/agentpool"
apitypes "github.com/yusing/goutils/apitypes"
)
type NewAgentRequest struct {
Name string `json:"name" binding:"required"`
Host string `json:"host" binding:"required"`
Port int `json:"port" binding:"required,min=1,max=65535"`
Type string `json:"type" binding:"required,oneof=docker system"`
Nightly bool `json:"nightly" binding:"omitempty"`
ContainerRuntime agent.ContainerRuntime `json:"container_runtime" binding:"omitempty,oneof=docker podman" default:"docker"`
} // @name NewAgentRequest
type NewAgentResponse struct {
Compose string `json:"compose"`
CA PEMPairResponse `json:"ca"`
Client PEMPairResponse `json:"client"`
} // @name NewAgentResponse
// @x-id "create"
// @BasePath /api/v1
// @Summary Create a new agent
// @Description Create a new agent and return the docker compose file, encrypted CA and client PEMs
// @Description The returned PEMs are encrypted with a random key and will be used for verification when adding a new agent
// @Tags agent
// @Accept json
// @Produce json
// @Param request body NewAgentRequest true "Request"
// @Success 200 {object} NewAgentResponse
// @Failure 400 {object} apitypes.ErrorResponse
// @Failure 403 {object} apitypes.ErrorResponse
// @Failure 409 {object} apitypes.ErrorResponse
// @Failure 500 {object} apitypes.ErrorResponse
// @Router /agent/create [post]
func Create(c *gin.Context) {
var request NewAgentRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
return
}
hostport := net.JoinHostPort(request.Host, strconv.Itoa(request.Port))
if _, ok := agentpool.Get(hostport); ok {
c.JSON(http.StatusConflict, apitypes.Error("agent already exists"))
return
}
var image string
if request.Nightly {
image = agent.DockerImageNightly
} else {
image = agent.DockerImageProduction
}
ca, srv, client, err := agent.NewAgent()
if err != nil {
c.Error(apitypes.InternalServerError(err, "failed to create agent"))
return
}
var cfg agent.Generator = &agent.AgentEnvConfig{
Name: request.Name,
Port: request.Port,
CACert: ca.String(),
SSLCert: srv.String(),
ContainerRuntime: request.ContainerRuntime,
}
if request.Type == "docker" {
cfg = &agent.AgentComposeConfig{
Image: image,
AgentEnvConfig: cfg.(*agent.AgentEnvConfig),
}
}
template, err := cfg.Generate()
if err != nil {
c.Error(apitypes.InternalServerError(err, "failed to generate agent config"))
return
}
key := getEncryptionKey()
encCA, err := ca.Encrypt(key)
if err != nil {
c.Error(apitypes.InternalServerError(err, "failed to encrypt CA PEMs"))
return
}
encClient, err := client.Encrypt(key)
if err != nil {
c.Error(apitypes.InternalServerError(err, "failed to encrypt client PEMs"))
return
}
c.JSON(http.StatusOK, NewAgentResponse{
Compose: template,
CA: toPEMPairResponse(encCA),
Client: toPEMPairResponse(encClient),
})
}