History

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.

Luis Pater committed 1 year agov6.3.32

Commit dc804e91 parent ab76cb32 files · +58 −20Browse files

Unified · Split

2 files
  1. internal/api/handlers/management/config_basic.go +19 −16
  2. internal/config/config.go +39 −4
internal/api/handlers/management/config_basic.go+19−16
@@ -28,7 +28,7 @@ func (h *Handler) GetConfigYAML(c *gin.Context) { · 27 unmodified lines
28 return28 return
29 }29 }
30 var node yaml.Node30 var node yaml.Node
31 if err := yaml.Unmarshal(data, &node); err != nil {31 if err = yaml.Unmarshal(data, &node); err != nil {
32 c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()})32 c.JSON(http.StatusInternalServerError, gin.H{"error": "parse_failed", "message": err.Error()})
33 return33 return
34 }34 }
@@ -41,17 +41,18 @@ func (h *Handler) GetConfigYAML(c *gin.Context) { · 6 unmodified lines
41}41}
42 42
43func WriteConfig(path string, data []byte) error {43func WriteConfig(path string, data []byte) error {
44 data = config.NormalizeCommentIndentation(data)
44 f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)45 f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
45 if err != nil {46 if err != nil {
46 return err47 return err
47 }48 }
48 if _, err := f.Write(data); err != nil {49 if _, errWrite := f.Write(data); errWrite != nil {
49 f.Close()50 _ = f.Close()
50 return err51 return errWrite
51 }52 }
52 if err := f.Sync(); err != nil {53 if errSync := f.Sync(); errSync != nil {
53 f.Close()54 _ = f.Close()
54 return err55 return errSync
55 }56 }
56 return f.Close()57 return f.Close()
57}58}
@@ -63,7 +64,7 @@ func (h *Handler) PutConfigYAML(c *gin.Context) { · 5 unmodified lines
63 return64 return
64 }65 }
65 var cfg config.Config66 var cfg config.Config
66 if err := yaml.Unmarshal(body, &cfg); err != nil {67 if err = yaml.Unmarshal(body, &cfg); err != nil {
67 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()})68 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_yaml", "message": err.Error()})
68 return69 return
69 }70 }
@@ -75,18 +76,20 @@ func (h *Handler) PutConfigYAML(c *gin.Context) { · 5 unmodified lines
75 return76 return
76 }77 }
77 tempFile := tmpFile.Name()78 tempFile := tmpFile.Name()
78 if _, err := tmpFile.Write(body); err != nil {79 if _, errWrite := tmpFile.Write(body); errWrite != nil {
79 tmpFile.Close()80 _ = tmpFile.Close()
80 os.Remove(tempFile)81 _ = os.Remove(tempFile)
81 c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()})82 c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errWrite.Error()})
82 return83 return
83 }84 }
84 if err := tmpFile.Close(); err != nil {85 if errClose := tmpFile.Close(); errClose != nil {
85 os.Remove(tempFile)86 _ = os.Remove(tempFile)
86 c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": err.Error()})87 c.JSON(http.StatusInternalServerError, gin.H{"error": "write_failed", "message": errClose.Error()})
87 return88 return
88 }89 }
89 defer os.Remove(tempFile)90 defer func() {
91 _ = os.Remove(tempFile)
92 }()
90 _, err = config.LoadConfigOptional(tempFile, false)93 _, err = config.LoadConfigOptional(tempFile, false)
91 if err != nil {94 if err != nil {
92 c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()})95 c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "invalid_config", "message": err.Error()})
internal/config/config.go+39−4
@@ -5,6 +5,7 @@ · 4 unmodified lines
5package config5package config
6 6
7import (7import (
8 "bytes"
8 "errors"9 "errors"
9 "fmt"10 "fmt"
10 "os"11 "os"
@@ -462,13 +463,19 @@ func SaveConfigPreserveComments(configFile string, cfg *Config) error { · 451 unmodified lines
462 return err463 return err
463 }464 }
464 defer func() { _ = f.Close() }()465 defer func() { _ = f.Close() }()
465 enc := yaml.NewEncoder(f)466 var buf bytes.Buffer
467 enc := yaml.NewEncoder(&buf)
466 enc.SetIndent(2)468 enc.SetIndent(2)
467 if err = enc.Encode(&original); err != nil {469 if err = enc.Encode(&original); err != nil {
468 _ = enc.Close()470 _ = enc.Close()
469 return err471 return err
470 }472 }
471 return enc.Close()473 if err = enc.Close(); err != nil {
474 return err
475 }
476 data = NormalizeCommentIndentation(buf.Bytes())
477 _, err = f.Write(data)
478 return err
472}479}
473 480
474func sanitizeConfigForPersist(cfg *Config) *Config {481func sanitizeConfigForPersist(cfg *Config) *Config {
@@ -518,13 +525,40 @@ func SaveConfigPreserveCommentsUpdateNestedScalar(configFile string, path []stri · 43 unmodified lines
518 return err525 return err
519 }526 }
520 defer func() { _ = f.Close() }()527 defer func() { _ = f.Close() }()
521 enc := yaml.NewEncoder(f)528 var buf bytes.Buffer
529 enc := yaml.NewEncoder(&buf)
522 enc.SetIndent(2)530 enc.SetIndent(2)
523 if err = enc.Encode(&root); err != nil {531 if err = enc.Encode(&root); err != nil {
524 _ = enc.Close()532 _ = enc.Close()
525 return err533 return err
526 }534 }
527 return enc.Close()535 if err = enc.Close(); err != nil {
536 return err
537 }
538 data = NormalizeCommentIndentation(buf.Bytes())
539 _, err = f.Write(data)
540 return err
541}
542
543// NormalizeCommentIndentation removes indentation from standalone YAML comment lines to keep them left aligned.
544func NormalizeCommentIndentation(data []byte) []byte {
545 lines := bytes.Split(data, []byte("\n"))
546 changed := false
547 for i, line := range lines {
548 trimmed := bytes.TrimLeft(line, " \t")
549 if len(trimmed) == 0 || trimmed[0] != '#' {
550 continue
551 }
552 if len(trimmed) == len(line) {
553 continue
554 }
555 lines[i] = append([]byte(nil), trimmed...)
556 changed = true
557 }
558 if !changed {
559 return data
560 }
561 return bytes.Join(lines, []byte("\n"))
528}562}
529 563
530// getOrCreateMapValue finds the value node for a given key in a mapping node.564// 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) · 235 unmodified lines
766 }800 }
767 }801 }
768 }802 }
803 default:
769 }804 }
770 // Fallback to structural equality to preserve nodes lacking explicit identifiers.805 // Fallback to structural equality to preserve nodes lacking explicit identifiers.
771 for i := range original {806 for i := range original {