fix(management): improve error handling and normalize YAML comment indentation

Enhance error management for file operations and clean up temporary files. Add `NormalizeCommentIndentation` function to ensure YAML comments maintain consistent formatting.
This commit is contained in:
Luis Pater
2025-11-11 08:37:57 +08:00
parent ab76cb3662
commit dc804e96fb
2 changed files with 58 additions and 20 deletions

View File

@@ -28,7 +28,7 @@ func (h *Handler) GetConfigYAML(c *gin.Context) {
return return
} }
var node yaml.Node var node yaml.Node
if err := yaml.Unmarshal(data, &node); err != nil { if err = yaml.Unmarshal(data, &node); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()})
return return
} }
@@ -41,17 +41,18 @@ func (h *Handler) GetConfigYAML(c *gin.Context) {
} }
func WriteConfig(path string, data []byte) error { func WriteConfig(path string, data []byte) error {
data = config.NormalizeCommentIndentation(data)
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil { if err != nil {
return err return err
} }
if _, err := f.Write(data); err != nil { if _, errWrite := f.Write(data); errWrite != nil {
f.Close() _ = f.Close()
return err return errWrite
} }
if err := f.Sync(); err != nil { if errSync := f.Sync(); errSync != nil {
f.Close() _ = f.Close()
return err return errSync
} }
return f.Close() return f.Close()
} }
@@ -63,7 +64,7 @@ func (h *Handler) PutConfigYAML(c *gin.Context) {
return return
} }
var cfg config.Config var cfg config.Config
if err := yaml.Unmarshal(body, &cfg); err != nil { if err = yaml.Unmarshal(body, &cfg); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()})
return return
} }
@@ -75,18 +76,20 @@ func (h *Handler) PutConfigYAML(c *gin.Context) {
return return
} }
tempFile := tmpFile.Name() tempFile := tmpFile.Name()
if _, err := tmpFile.Write(body); err != nil { if _, errWrite := tmpFile.Write(body); errWrite != nil {
tmpFile.Close() _ = tmpFile.Close()
os.Remove(tempFile) _ = os.Remove(tempFile)
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errWrite.Error()})
return return
} }
if err := tmpFile.Close(); err != nil { if errClose := tmpFile.Close(); errClose != nil {
os.Remove(tempFile) _ = os.Remove(tempFile)
c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errClose.Error()})
return return
} }
defer os.Remove(tempFile) defer func() {
_ = os.Remove(tempFile)
}()
_, err = config.LoadConfigOptional(tempFile, false) _, err = config.LoadConfigOptional(tempFile, false)
if err != nil { if err != nil {
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()}) c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()})

View File

@@ -5,6 +5,7 @@
package config package config
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@@ -462,13 +463,19 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error {
return err return err
} }
defer func() { _ = f.Close() }() defer func() { _ = f.Close() }()
enc := yaml.NewEncoder(f) var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2) enc.SetIndent(2)
if err = enc.Encode(&original); err != nil { if err = enc.Encode(&original); err != nil {
_ = enc.Close() _ = enc.Close()
return err return err
} }
return enc.Close() if err = enc.Close(); err != nil {
return err
}
data = NormalizeCommentIndentation(buf.Bytes())
_, err = f.Write(data)
return err
} }
func sanitizeConfigForPersist(cfg *Config) *Config { func sanitizeConfigForPersist(cfg *Config) *Config {
@@ -518,13 +525,40 @@ func SaveConfigPreserveCommentsUpdateNestedScalar(configFile string, path []stri
return err return err
} }
defer func() { _ = f.Close() }() defer func() { _ = f.Close() }()
enc := yaml.NewEncoder(f) var buf bytes.Buffer
enc := yaml.NewEncoder(&buf)
enc.SetIndent(2) enc.SetIndent(2)
if err = enc.Encode(&root); err != nil { if err = enc.Encode(&root); err != nil {
_ = enc.Close() _ = enc.Close()
return err return err
} }
return enc.Close() if err = enc.Close(); err != nil {
return err
}
data = NormalizeCommentIndentation(buf.Bytes())
_, err = f.Write(data)
return err
}
// NormalizeCommentIndentation removes indentation from standalone YAML comment lines to keep them left aligned.
func NormalizeCommentIndentation(data []byte) []byte {
lines := bytes.Split(data, []byte("\n"))
changed := false
for i, line := range lines {
trimmed := bytes.TrimLeft(line, " \t")
if len(trimmed) == 0 || trimmed[0] != '#' {
continue
}
if len(trimmed) == len(line) {
continue
}
lines[i] = append([]byte(nil), trimmed...)
changed = true
}
if !changed {
return data
}
return bytes.Join(lines, []byte("\n"))
} }
// getOrCreateMapValue finds the value node for a given key in a mapping node. // getOrCreateMapValue finds the value node for a given key in a mapping node.
@@ -766,6 +800,7 @@ func matchSequenceElement(original []*yaml.Node, used []bool, target *yaml.Node)
} }
} }
} }
default:
} }
// Fallback to structural equality to preserve nodes lacking explicit identifiers. // Fallback to structural equality to preserve nodes lacking explicit identifiers.
for i := range original { for i := range original {