Skip to content

Commit 74a3001

Browse files
authored
Move to yaml.v3 and preserve comments, multiline strings, anchors in helm values (#1055)
Signed-off-by: Brian Ewins <bewins@nerdwallet.com>
1 parent 99a0cb2 commit 74a3001

File tree

3 files changed

+407
-155
lines changed

3 files changed

+407
-155
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ require (
2424
golang.org/x/oauth2 v0.27.0
2525
golang.org/x/sync v0.11.0
2626
google.golang.org/grpc v1.70.0
27-
gopkg.in/yaml.v2 v2.4.0
27+
gopkg.in/yaml.v3 v3.0.1
2828
k8s.io/api v0.31.0
2929
k8s.io/apimachinery v0.31.0
3030
k8s.io/client-go v0.31.0
@@ -152,7 +152,7 @@ require (
152152
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
153153
gopkg.in/inf.v0 v0.9.1 // indirect
154154
gopkg.in/warnings.v0 v0.1.2 // indirect
155-
gopkg.in/yaml.v3 v3.0.1 // indirect
155+
gopkg.in/yaml.v2 v2.4.0 // indirect
156156
k8s.io/apiextensions-apiserver v0.31.2 // indirect
157157
k8s.io/apiserver v0.31.0 // indirect
158158
k8s.io/cli-runtime v0.31.0 // indirect

pkg/argocd/update.go

Lines changed: 119 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package argocd
22

33
import (
4+
"bytes"
45
"context"
56
"fmt"
67
"path/filepath"
@@ -21,7 +22,7 @@ import (
2122

2223
"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
2324
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
24-
"gopkg.in/yaml.v2"
25+
"gopkg.in/yaml.v3"
2526
)
2627

2728
// Stores some statistics about the results of a run
@@ -418,6 +419,45 @@ func setAppImage(app *v1alpha1.Application, img *image.ContainerImage) error {
418419
return err
419420
}
420421

422+
func marshalWithIndent(in interface{}, indent int) (out []byte, err error) {
423+
var b bytes.Buffer
424+
encoder := yaml.NewEncoder(&b)
425+
defer encoder.Close()
426+
// note: yaml.v3 will only respect indents from 1 to 9 inclusive.
427+
encoder.SetIndent(indent)
428+
if err = encoder.Encode(in); err != nil {
429+
return nil, err
430+
}
431+
if err = encoder.Close(); err != nil {
432+
return nil, err
433+
}
434+
return b.Bytes(), nil
435+
}
436+
437+
func guessIndent(root *yaml.Node) int {
438+
node := root
439+
if root.Kind == yaml.DocumentNode {
440+
if len(node.Content) == 0 {
441+
return 2
442+
}
443+
node = root.Content[0]
444+
}
445+
// anything other than a map at the root makes guessing difficult
446+
if node.Kind != yaml.MappingNode || len(node.Content) == 0 {
447+
return 2
448+
}
449+
// first level map entries that are themselves mappings or sequences,
450+
// in block style, and are indented, allow guessing the preferred indent.
451+
for i, child := range node.Content {
452+
if i%2 == 1 && child.Column > 1 && child.Column < 10 && child.Style != yaml.FlowStyle {
453+
if child.Kind == yaml.MappingNode || child.Kind == yaml.SequenceNode {
454+
return child.Column - 1
455+
}
456+
}
457+
}
458+
return 2
459+
}
460+
421461
// marshalParamsOverride marshals the parameter overrides of a given application
422462
// into YAML bytes
423463
func marshalParamsOverride(app *v1alpha1.Application, originalData []byte) ([]byte, error) {
@@ -441,16 +481,16 @@ func marshalParamsOverride(app *v1alpha1.Application, originalData []byte) ([]by
441481
}
442482

443483
if len(originalData) == 0 {
444-
override, err = yaml.Marshal(newParams)
484+
override, err = marshalWithIndent(newParams, 2)
445485
break
446486
}
447487
err = yaml.Unmarshal(originalData, &params)
448488
if err != nil {
449-
override, err = yaml.Marshal(newParams)
489+
override, err = marshalWithIndent(newParams, 2)
450490
break
451491
}
452492
mergeKustomizeOverride(&params, &newParams)
453-
override, err = yaml.Marshal(params)
493+
override, err = marshalWithIndent(params, 2)
454494
case ApplicationTypeHelm:
455495
if appSource.Helm == nil {
456496
return []byte{}, nil
@@ -459,11 +499,12 @@ func marshalParamsOverride(app *v1alpha1.Application, originalData []byte) ([]by
459499
if strings.HasPrefix(app.Annotations[common.WriteBackTargetAnnotation], common.HelmPrefix) {
460500
images := GetImagesAndAliasesFromApplication(app)
461501

462-
helmNewValues := yaml.MapSlice{}
502+
helmNewValues := yaml.Node{}
463503
err = yaml.Unmarshal(originalData, &helmNewValues)
464504
if err != nil {
465505
return nil, err
466506
}
507+
indent := guessIndent(&helmNewValues)
467508

468509
for _, c := range images {
469510
if c.ImageAlias == "" {
@@ -505,7 +546,7 @@ func marshalParamsOverride(app *v1alpha1.Application, originalData []byte) ([]by
505546
}
506547
}
507548

508-
override, err = yaml.Marshal(helmNewValues)
549+
override, err = marshalWithIndent(&helmNewValues, indent)
509550
} else {
510551
var params helmOverride
511552
newParams := helmOverride{
@@ -518,16 +559,16 @@ func marshalParamsOverride(app *v1alpha1.Application, originalData []byte) ([]by
518559
log.WithContext().AddField("application", app).Debugf("values: '%s'", outputParams)
519560

520561
if len(originalData) == 0 {
521-
override, err = yaml.Marshal(newParams)
562+
override, err = marshalWithIndent(newParams, 2)
522563
break
523564
}
524565
err = yaml.Unmarshal(originalData, &params)
525566
if err != nil {
526-
override, err = yaml.Marshal(newParams)
567+
override, err = marshalWithIndent(newParams, 2)
527568
break
528569
}
529570
mergeHelmOverride(&params, &newParams)
530-
override, err = yaml.Marshal(params)
571+
override, err = marshalWithIndent(params, 2)
531572
}
532573
default:
533574
err = fmt.Errorf("unsupported application type")
@@ -572,72 +613,98 @@ func mergeKustomizeOverride(t *kustomizeOverride, o *kustomizeOverride) {
572613
}
573614
}
574615

575-
// Check if a key exists in a MapSlice and return its index and value
576-
func findHelmValuesKey(m yaml.MapSlice, key string) (int, bool) {
577-
for i, item := range m {
578-
if item.Key == key {
579-
return i, true
616+
// Check if a key exists in a MappingNode and return the index of its value
617+
func findHelmValuesKey(m *yaml.Node, key string) (int, bool) {
618+
for i, item := range m.Content {
619+
if i%2 == 0 && item.Value == key {
620+
return i + 1, true
580621
}
581622
}
582623
return -1, false
583624
}
584625

626+
func nodeKindString(k yaml.Kind) string {
627+
return map[yaml.Kind]string{
628+
yaml.DocumentNode: "DocumentNode",
629+
yaml.SequenceNode: "SequenceNode",
630+
yaml.MappingNode: "MappingNode",
631+
yaml.ScalarNode: "ScalarNode",
632+
yaml.AliasNode: "AliasNode",
633+
}[k]
634+
}
635+
585636
// set value of the parameter passed from the annotations.
586-
func setHelmValue(currentValues *yaml.MapSlice, key string, value interface{}) error {
637+
func setHelmValue(currentValues *yaml.Node, key string, value interface{}) error {
638+
current := currentValues
639+
640+
// an unmarshalled document has a DocumentNode at the root, but
641+
// we navigate from a MappingNode.
642+
if current.Kind == yaml.DocumentNode {
643+
current = current.Content[0]
644+
}
645+
646+
if current.Kind != yaml.MappingNode {
647+
return fmt.Errorf("unexpected type %s for root", nodeKindString(current.Kind))
648+
}
649+
587650
// Check if the full key exists
588-
if idx, found := findHelmValuesKey(*currentValues, key); found {
589-
(*currentValues)[idx].Value = value
651+
if idx, found := findHelmValuesKey(current, key); found {
652+
(*current).Content[idx].Value = value.(string)
590653
return nil
591654
}
592655

593656
var err error
594657
keys := strings.Split(key, ".")
595-
current := currentValues
596-
var parent *yaml.MapSlice
597-
parentIdx := -1
598658

599659
for i, k := range keys {
600-
if idx, found := findHelmValuesKey(*current, k); found {
660+
if idx, found := findHelmValuesKey(current, k); found {
661+
// Navigate deeper into the map
662+
current = (*current).Content[idx]
663+
// unpack one level of alias; an alias of an alias is not supported
664+
if current.Kind == yaml.AliasNode {
665+
current = current.Alias
666+
}
601667
if i == len(keys)-1 {
602668
// If we're at the final key, set the value and return
603-
(*current)[idx].Value = value
604-
return nil
605-
} else {
606-
// Navigate deeper into the map
607-
if nestedMap, ok := (*current)[idx].Value.(yaml.MapSlice); ok {
608-
parent = current
609-
parentIdx = idx
610-
current = &nestedMap
669+
if current.Kind == yaml.ScalarNode {
670+
current.Value = value.(string)
671+
current.Tag = "!!str"
611672
} else {
612-
return fmt.Errorf("unexpected type %T for key %s", (*current)[idx].Value, k)
673+
return fmt.Errorf("unexpected type %s for key %s", nodeKindString(current.Kind), k)
613674
}
675+
return nil
676+
} else if current.Kind != yaml.MappingNode {
677+
return fmt.Errorf("unexpected type %s for key %s", nodeKindString(current.Kind), k)
614678
}
615679
} else {
616-
newCurrent := yaml.MapSlice{}
617-
var newParent yaml.MapSlice
618-
619680
if i == len(keys)-1 {
620-
newParent = append(*current, yaml.MapItem{Key: k, Value: value})
621-
} else {
622-
newParent = append(*current, yaml.MapItem{Key: k, Value: newCurrent})
623-
}
624-
625-
if parent == nil {
626-
*currentValues = newParent
681+
current.Content = append(current.Content,
682+
&yaml.Node{
683+
Kind: yaml.ScalarNode,
684+
Value: k,
685+
Tag: "!!str",
686+
},
687+
&yaml.Node{
688+
Kind: yaml.ScalarNode,
689+
Value: value.(string),
690+
Tag: "!!str",
691+
},
692+
)
693+
return nil
627694
} else {
628-
// if parentIdx has not been set (parent element is also new), set it to the last element
629-
if parentIdx == -1 {
630-
parentIdx = len(*parent) - 1
631-
if parentIdx < 0 {
632-
parentIdx = 0
633-
}
634-
}
635-
(*parent)[parentIdx].Value = newParent
695+
current.Content = append(current.Content,
696+
&yaml.Node{
697+
Kind: yaml.ScalarNode,
698+
Value: k,
699+
Tag: "!!str",
700+
},
701+
&yaml.Node{
702+
Kind: yaml.MappingNode,
703+
Content: []*yaml.Node{},
704+
},
705+
)
706+
current = current.Content[len(current.Content)-1]
636707
}
637-
638-
parent = &newParent
639-
current = &newCurrent
640-
parentIdx = -1
641708
}
642709
}
643710

0 commit comments

Comments
 (0)