mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-23 01:04:53 +01:00
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.
130 lines
3.8 KiB
Go
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
|
|
}
|