mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-25 10:31:30 +01:00
refactor: improve error handling, validation and proper cleanup
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -40,4 +40,8 @@ tsconfig.tsbuildinfo
|
||||
|
||||
!agent.compose.yml
|
||||
!agent/pkg/**
|
||||
dev-data/
|
||||
dev-data/
|
||||
|
||||
RELEASE_NOTES.md
|
||||
CLAUDE.md
|
||||
.kilocode/**
|
||||
@@ -106,7 +106,7 @@ func (c *Config) Validate() gperr.Error {
|
||||
c.allowLocal = true
|
||||
}
|
||||
|
||||
if c.Notify.Interval < 0 {
|
||||
if c.Notify.Interval <= 0 {
|
||||
c.Notify.Interval = defaultNotifyInterval
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ type LogsQueryParams struct {
|
||||
Since string `form:"from"`
|
||||
Until string `form:"to"`
|
||||
Levels string `form:"levels"`
|
||||
Limit int `form:"limit,default=100" binding:"omitempty,min=1,max=1000"`
|
||||
Limit int `form:"limit,default=100" binding:"min=1,max=1000"`
|
||||
} // @name LogsQueryParams
|
||||
|
||||
// @x-id "logs"
|
||||
|
||||
@@ -3,4 +3,4 @@ package proxmoxapi
|
||||
type ActionRequest struct {
|
||||
Node string `uri:"node" binding:"required"`
|
||||
VMID int `uri:"vmid" binding:"required"`
|
||||
}
|
||||
} // @name ProxmoxVMActionRequest
|
||||
|
||||
@@ -11,18 +11,18 @@ import (
|
||||
)
|
||||
|
||||
type JournalctlRequest struct {
|
||||
Node string `uri:"node" binding:"required"`
|
||||
VMID *int `uri:"vmid"` // optional - if not provided, streams node journalctl
|
||||
Service string `uri:"service"`
|
||||
Limit int `query:"limit" binding:"omitempty,min=1,max=1000"`
|
||||
}
|
||||
Node string `uri:"node" binding:"required"` // Node name
|
||||
VMID *int `uri:"vmid"` // Container VMID (optional - if not provided, streams node journalctl)
|
||||
Service string `uri:"service"` // Service name (e.g., 'pveproxy' for node, 'container@.service' format for LXC)
|
||||
Limit int `query:"limit" default:"100" binding:"min=1,max=1000"` // Limit output lines (1-1000)
|
||||
} // @name ProxmoxJournalctlRequest
|
||||
|
||||
// @x-id "journalctl"
|
||||
// @BasePath /api/v1
|
||||
// @Summary Get journalctl output
|
||||
// @Description Get journalctl output for node or LXC container. If vmid is not provided, streams node journalctl.
|
||||
// @Tags proxmox,websocket
|
||||
// @Accept json
|
||||
// @Accept json
|
||||
// @Produce application/json
|
||||
// @Param node path string true "Node name"
|
||||
// @Param vmid path int false "Container VMID (optional - if not provided, streams node journalctl)"
|
||||
@@ -42,6 +42,10 @@ func Journalctl(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||
return
|
||||
}
|
||||
if err := c.ShouldBindQuery(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||
return
|
||||
}
|
||||
|
||||
node, ok := proxmox.Nodes.Get(request.Node)
|
||||
if !ok {
|
||||
@@ -49,14 +53,10 @@ func Journalctl(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
manager, err := websocket.NewManagerWithUpgrade(c)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to upgrade to websocket"))
|
||||
return
|
||||
}
|
||||
defer manager.Close()
|
||||
c.Status(http.StatusContinue)
|
||||
|
||||
var reader io.ReadCloser
|
||||
var err error
|
||||
if request.VMID == nil {
|
||||
reader, err = node.NodeJournalctl(c.Request.Context(), request.Service, request.Limit)
|
||||
} else {
|
||||
@@ -68,6 +68,13 @@ func Journalctl(c *gin.Context) {
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
manager, err := websocket.NewManagerWithUpgrade(c)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to upgrade to websocket"))
|
||||
return
|
||||
}
|
||||
defer manager.Close()
|
||||
|
||||
writer := manager.NewWriter(websocket.TextMessage)
|
||||
_, err = io.Copy(writer, reader)
|
||||
if err != nil {
|
||||
|
||||
@@ -37,5 +37,5 @@ func Route(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, route)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusNotFound, nil)
|
||||
c.JSON(http.StatusNotFound, apitypes.Error("route not found"))
|
||||
}
|
||||
|
||||
@@ -52,11 +52,11 @@ graph TD
|
||||
```go
|
||||
type Config struct {
|
||||
URL string `json:"url" validate:"required,url"`
|
||||
Username string `json:"username" validate:"required_without=TokenID Secret"`
|
||||
Password strutils.Redacted `json:"password" validate:"required_without=TokenID Secret"`
|
||||
Realm string `json:"realm" validate:"required_without=TokenID Secret"`
|
||||
TokenID string `json:"token_id" validate:"required_without=Username Password"`
|
||||
Secret strutils.Redacted `json:"secret" validate:"required_without=Username Password"`
|
||||
Username string `json:"username" validate:"required_without_all=TokenID Secret"`
|
||||
Password strutils.Redacted `json:"password" validate:"required_without_all=TokenID Secret"`
|
||||
Realm string `json:"realm"`
|
||||
TokenID string `json:"token_id" validate:"required_without_all=Username Password"`
|
||||
Secret strutils.Redacted `json:"secret" validate:"required_without_all=Username Password"`
|
||||
NoTLSVerify bool `json:"no_tls_verify"`
|
||||
|
||||
client *Client
|
||||
|
||||
@@ -65,6 +65,9 @@ func (c *Client) UpdateClusterInfo(ctx context.Context) (err error) {
|
||||
}
|
||||
|
||||
func (c *Client) UpdateResources(ctx context.Context) error {
|
||||
if c.Cluster == nil {
|
||||
return errors.New("cluster not initialized, call UpdateClusterInfo first")
|
||||
}
|
||||
resourcesSlice, err := c.Cluster.Resources(ctx, "vm")
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -18,12 +18,12 @@ import (
|
||||
type Config struct {
|
||||
URL string `json:"url" validate:"required,url"`
|
||||
|
||||
Username string `json:"username" validate:"required_without=TokenID Secret"`
|
||||
Password strutils.Redacted `json:"password" validate:"required_without=TokenID Secret"`
|
||||
Realm string `json:"realm" validate:"required_without=TokenID Secret"`
|
||||
Username string `json:"username" validate:"required_without_all=TokenID Secret"`
|
||||
Password strutils.Redacted `json:"password" validate:"required_without_all=TokenID Secret"`
|
||||
Realm string `json:"realm"` // default is "pam"
|
||||
|
||||
TokenID string `json:"token_id" validate:"required_without=Username Password"`
|
||||
Secret strutils.Redacted `json:"secret" validate:"required_without=Username Password"`
|
||||
TokenID string `json:"token_id" validate:"required_without_all=Username Password"`
|
||||
Secret strutils.Redacted `json:"secret" validate:"required_without_all=Username Password"`
|
||||
|
||||
NoTLSVerify bool `json:"no_tls_verify" yaml:"no_tls_verify,omitempty"`
|
||||
|
||||
@@ -65,6 +65,9 @@ func (c *Config) Init(ctx context.Context) gperr.Error {
|
||||
}
|
||||
useCredentials := false
|
||||
if c.Username != "" && c.Password != "" {
|
||||
if c.Realm == "" {
|
||||
c.Realm = "pam"
|
||||
}
|
||||
opts = append(opts, proxmox.WithCredentials(&proxmox.Credentials{
|
||||
Username: c.Username,
|
||||
Password: c.Password.String(),
|
||||
|
||||
@@ -50,6 +50,7 @@ func (n *Node) NodeCommand(ctx context.Context, command string) (io.ReadCloser,
|
||||
// Send command
|
||||
cmd := []byte(command + "\n")
|
||||
if err := handleSend(cmd); err != nil {
|
||||
closeFn()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -70,6 +71,7 @@ func (n *Node) NodeCommand(ctx context.Context, command string) (io.ReadCloser,
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
_ = pw.CloseWithError(ctx.Err())
|
||||
return
|
||||
case msg := <-recv:
|
||||
// skip the header message like
|
||||
@@ -106,7 +108,6 @@ func (n *Node) NodeCommand(ctx context.Context, command string) (io.ReadCloser,
|
||||
case err := <-errs:
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||
_ = pw.Close()
|
||||
return
|
||||
}
|
||||
_ = pw.CloseWithError(err)
|
||||
|
||||
@@ -218,7 +218,7 @@ func (r *Route) validate() gperr.Error {
|
||||
r.Proxmox.VMName = res.Name
|
||||
|
||||
if r.Host == DefaultHost {
|
||||
containerName := r.Idlewatcher.ContainerName()
|
||||
containerName := res.Name
|
||||
// get ip addresses of the vmid
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
@@ -336,7 +336,7 @@ func (r *Route) validate() gperr.Error {
|
||||
}
|
||||
}
|
||||
|
||||
if r.Proxmox == nil && r.Container == nil {
|
||||
if r.Proxmox == nil && r.Container == nil && r.ProxyURL != nil {
|
||||
proxmoxProviders := config.WorkingState.Load().Value().Providers.Proxmox
|
||||
if len(proxmoxProviders) > 0 {
|
||||
// it's fine if ip is nil
|
||||
|
||||
@@ -79,6 +79,10 @@ var commands = map[string]struct {
|
||||
},
|
||||
build: func(args any) CommandHandler {
|
||||
return NonTerminatingCommand(func(w http.ResponseWriter, r *http.Request) error {
|
||||
if authHandler == nil {
|
||||
http.Error(w, "Auth handler not initialized", http.StatusInternalServerError)
|
||||
return errTerminated
|
||||
}
|
||||
if !authHandler(w, r) {
|
||||
return errTerminated
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user