Files
godoxy-yusing/internal/api/v1/agent/verify.go
yusing 6da7227f9b refactor(errs): migrate from gperr.Error to standard Go error interface
This is a large-scale refactoring across the codebase that replaces the custom
`gperr.Error` type with Go's standard `error` interface. The changes include:

- Replacing `gperr.Error` return types with `error` in function signatures
- Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()`
- Using `%w` format verb for error wrapping instead of `.With()` method
- Replacing `gperr.Subject()` calls with `gperr.PrependSubject()`
- Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern
- Update NewLogger to handle multiline error message
- Updating `goutils` submodule to latest commit

This refactoring aligns with Go idioms and removes the dependency on
custom error handling abstractions in favor of standard library patterns.
2026-02-08 12:07:36 +08:00

130 lines
3.8 KiB
Go

package agentapi
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"github.com/gin-gonic/gin"
"github.com/yusing/godoxy/agent/pkg/agent"
"github.com/yusing/godoxy/agent/pkg/certs"
"github.com/yusing/godoxy/internal/agentpool"
config "github.com/yusing/godoxy/internal/config/types"
"github.com/yusing/godoxy/internal/route/provider"
apitypes "github.com/yusing/goutils/apitypes"
)
type VerifyNewAgentRequest struct {
Host string `json:"host"`
CA PEMPairResponse `json:"ca"`
Client PEMPairResponse `json:"client"`
ContainerRuntime agent.ContainerRuntime `json:"container_runtime"`
} // @name VerifyNewAgentRequest
// @x-id "verify"
// @BasePath /api/v1
// @Summary Verify a new agent
// @Description Verify a new agent and return the number of routes added
// @Tags agent
// @Accept json
// @Produce json
// @Param request body VerifyNewAgentRequest true "Request"
// @Success 200 {object} SuccessResponse
// @Failure 400 {object} ErrorResponse
// @Failure 403 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /agent/verify [post]
func Verify(c *gin.Context) {
// avoid timeout waiting for response headers
c.Status(http.StatusContinue)
var request VerifyNewAgentRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
return
}
filename, ok := certs.AgentCertsFilepath(request.Host)
if !ok {
c.JSON(http.StatusBadRequest, apitypes.Error("invalid host", nil))
return
}
ca, err := fromEncryptedPEMPairResponse(request.CA)
if err != nil {
c.JSON(http.StatusBadRequest, apitypes.Error("invalid CA", err))
return
}
client, err := fromEncryptedPEMPairResponse(request.Client)
if err != nil {
c.JSON(http.StatusBadRequest, apitypes.Error("invalid client", err))
return
}
nRoutesAdded, err := verifyNewAgent(c.Request.Context(), request.Host, ca, client, request.ContainerRuntime)
if err != nil {
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
return
}
zip, err := certs.ZipCert(ca.Cert, client.Cert, client.Key)
if err != nil {
c.Error(apitypes.InternalServerError(err, "failed to zip certs"))
return
}
if err := os.WriteFile(filename, zip, 0o600); err != nil {
c.Error(apitypes.InternalServerError(err, "failed to write certs"))
return
}
c.JSON(http.StatusOK, apitypes.Success(fmt.Sprintf("Added %d routes", nRoutesAdded)))
}
var errAgentAlreadyExists = errors.New("agent already exists")
func verifyNewAgent(ctx context.Context, host string, ca agent.PEMPair, client agent.PEMPair, containerRuntime agent.ContainerRuntime) (int, error) {
var agentCfg agent.AgentConfig
agentCfg.Addr = host
agentCfg.Runtime = containerRuntime
// check if agent host exists in the config
cfgState := config.ActiveState.Load()
for _, a := range cfgState.Value().Providers.Agents {
if a.Addr == host {
return 0, errAgentAlreadyExists
}
}
// check if agent host exists in the agent pool
if agentpool.Has(&agentCfg) {
return 0, errAgentAlreadyExists
}
err := agentCfg.InitWithCerts(ctx, ca.Cert, client.Cert, client.Key)
if err != nil {
return 0, fmt.Errorf("failed to initialize agent config: %w", err)
}
provider := provider.NewAgentProvider(&agentCfg)
if _, loaded := cfgState.LoadOrStoreProvider(provider.String(), provider); loaded {
return 0, fmt.Errorf("provider %s already exists", provider.String())
}
// agent must be added before loading routes
added := agentpool.Add(&agentCfg)
if !added {
return 0, errAgentAlreadyExists
}
err = provider.LoadRoutes()
if err != nil {
cfgState.DeleteProvider(provider.String())
agentpool.Remove(&agentCfg)
return 0, fmt.Errorf("failed to load routes: %w", err)
}
return provider.NumRoutes(), nil
}