@@ -21,9 +21,12 @@ import (
2121 "fmt"
2222 "io"
2323 "os"
24+ "strconv"
2425 "strings"
26+ "unicode"
2527
26- "gopkg.in/yaml.v3"
28+ "github.com/goccy/go-yaml"
29+ "github.com/goccy/go-yaml/parser"
2730)
2831
2932// inplaceReadValuesYAML reads the provided chart tar file and returns the values
@@ -124,16 +127,126 @@ func modifyTarStreamValuesYAML(in io.Reader, out io.Writer, modFn modFunction) e
124127}
125128
126129func modifyStreamValuesYAML (in io.Reader , out io.Writer , modFn modFunction ) error {
127- // Parse YAML
130+ inputBytes , err := io .ReadAll (in )
131+ if err != nil {
132+ return err
133+ }
134+ // Parse YAML into Go values for modification logic.
128135 var data map [string ]any
129- if err := yaml .NewDecoder (in ).Decode (& data ); err != nil {
136+ if err := yaml .Unmarshal (inputBytes , & data ); err != nil {
137+ return err
138+ }
139+ originalStrings := map [string ]string {}
140+ collectStringValues (data , nil , originalStrings )
141+ // Modify YAML values via the existing callback.
142+ data , err = modFn (data )
143+ if err != nil {
130144 return err
131145 }
132- // Modify YAML
133- data , err := modFn (data )
146+ updatedStrings := map [string ]string {}
147+ collectStringValues (data , nil , updatedStrings )
148+ // Parse YAML into an AST so we can update nodes without losing comments.
149+ astFile , err := parser .ParseBytes (inputBytes , parser .ParseComments )
134150 if err != nil {
135151 return err
136152 }
137- // Marshal back to YAML
138- return yaml .NewEncoder (out ).Encode (data )
153+ for yamlPath , newValue := range updatedStrings {
154+ if originalValue , ok := originalStrings [yamlPath ]; ok && originalValue == newValue {
155+ continue
156+ }
157+ path , err := yaml .PathString (yamlPath )
158+ if err != nil {
159+ return err
160+ }
161+ node , err := yaml .ValueToNode (newValue )
162+ if err != nil {
163+ return err
164+ }
165+ if err := path .ReplaceWithNode (astFile , node ); err != nil {
166+ return err
167+ }
168+ }
169+ _ , err = io .WriteString (out , astFile .String ())
170+ return err
171+ }
172+
173+ type pathSegment struct {
174+ key string
175+ isIndex bool
176+ }
177+
178+ func collectStringValues (object any , path []pathSegment , out map [string ]string ) {
179+ switch t := object .(type ) {
180+ case map [string ]any :
181+ for key , value := range t {
182+ keyPath := append (path , pathSegment {key : key })
183+ if stringValue , ok := value .(string ); ok {
184+ out [pathToYAMLPath (keyPath )] = stringValue
185+ continue
186+ }
187+ collectStringValues (value , keyPath , out )
188+ }
189+ case map [string ]string :
190+ for key , value := range t {
191+ keyPath := append (path , pathSegment {key : key })
192+ out [pathToYAMLPath (keyPath )] = value
193+ }
194+ case []any :
195+ for i , value := range t {
196+ keyPath := append (path , pathSegment {key : strconv .Itoa (i ), isIndex : true })
197+ if stringValue , ok := value .(string ); ok {
198+ out [pathToYAMLPath (keyPath )] = stringValue
199+ continue
200+ }
201+ collectStringValues (value , keyPath , out )
202+ }
203+ case []string :
204+ for i , value := range t {
205+ keyPath := append (path , pathSegment {key : strconv .Itoa (i ), isIndex : true })
206+ out [pathToYAMLPath (keyPath )] = value
207+ }
208+ default :
209+ // ignore object
210+ }
211+ }
212+
213+ func pathToYAMLPath (path []pathSegment ) string {
214+ if len (path ) == 0 {
215+ return "$"
216+ }
217+ var builder strings.Builder
218+ builder .WriteString ("$" )
219+ for _ , segment := range path {
220+ if segment .isIndex {
221+ builder .WriteString ("[" )
222+ builder .WriteString (segment .key )
223+ builder .WriteString ("]" )
224+ continue
225+ }
226+ builder .WriteString ("." )
227+ if isSimpleYAMLPathKey (segment .key ) {
228+ builder .WriteString (segment .key )
229+ continue
230+ }
231+ builder .WriteString ("'" )
232+ builder .WriteString (strings .ReplaceAll (segment .key , "'" , "\\ '" ))
233+ builder .WriteString ("'" )
234+ }
235+ return builder .String ()
236+ }
237+
238+ func isSimpleYAMLPathKey (key string ) bool {
239+ if key == "" {
240+ return false
241+ }
242+ for i , r := range key {
243+ if i == 0 && unicode .IsDigit (r ) {
244+ return false
245+ }
246+ if unicode .IsLetter (r ) || unicode .IsDigit (r ) || r == '_' || r == '-' {
247+ continue
248+ }
249+ return false
250+ }
251+ return true
139252}
0 commit comments