@@ -22,6 +22,10 @@ import (
22
22
23
23
"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
24
24
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
25
+ gyaml "github.com/goccy/go-yaml"
26
+ "github.com/goccy/go-yaml/ast"
27
+ "github.com/goccy/go-yaml/parser"
28
+ "github.com/goccy/go-yaml/token"
25
29
yaml "sigs.k8s.io/yaml/goyaml.v3"
26
30
)
27
31
@@ -465,55 +469,52 @@ func marshalHelmOverride(app *v1alpha1.Application, originalData []byte) (overri
465
469
if appSource .Helm == nil {
466
470
return []byte {}, nil
467
471
}
468
-
469
- if strings .HasPrefix (app . Annotations [ common . WriteBackTargetAnnotation ] , common .HelmPrefix ) {
472
+ target := app . Annotations [ common . WriteBackTargetAnnotation ]
473
+ if strings .HasPrefix (target , common .HelmPrefix ) {
470
474
images := GetImagesAndAliasesFromApplication (app )
471
475
472
- helmNewValues := yaml. Node {}
473
- if unmarshalErr := yaml . Unmarshal ( originalData , & helmNewValues ); unmarshalErr != nil {
474
- return nil , unmarshalErr
476
+ root , err := parser . ParseBytes ([] byte ( originalData ), parser . ParseComments )
477
+ if err != nil {
478
+ return nil , fmt . Errorf ( "failed to parse original helm values: %w" , err )
475
479
}
476
480
477
481
for _ , img := range images {
478
482
if img .ImageAlias == "" {
479
483
continue
480
484
}
481
-
482
485
helmAnnotationParamName , helmAnnotationParamVersion := getHelmParamNamesFromAnnotation (app .Annotations , img )
483
-
484
- if helmAnnotationParamName == "" {
485
- return nil , fmt .Errorf ("could not find an image-name annotation for image %s" , img .ImageName )
486
- }
487
486
// for image-spec annotation, helmAnnotationParamName holds image-spec annotation value,
488
- // and helmAnnotationParamVersion is empty
487
+ // and version is empty
489
488
if helmAnnotationParamVersion == "" {
490
489
if img .GetParameterHelmImageSpec (app .Annotations , common .ImageUpdaterAnnotationPrefix ) == "" {
491
490
// not a full image-spec, so image-tag is required
492
491
return nil , fmt .Errorf ("could not find an image-tag annotation for image %s" , img .ImageName )
493
492
}
494
493
} else {
495
- // image-tag annotation is present, so continue to process image-tag
496
494
helmParamVersion := getHelmParam (appSource .Helm .Parameters , helmAnnotationParamVersion )
497
495
if helmParamVersion == nil {
498
496
return nil , fmt .Errorf ("%s parameter not found" , helmAnnotationParamVersion )
499
497
}
500
- err = setHelmValue ( & helmNewValues , helmAnnotationParamVersion , helmParamVersion .Value )
498
+ err = applyHelmParam ( root , helmAnnotationParamVersion , helmParamVersion .Value )
501
499
if err != nil {
502
- return nil , fmt . Errorf ( "failed to set image parameter version value: %v" , err )
500
+ return nil , err
503
501
}
504
502
}
505
-
503
+ if helmAnnotationParamName == "" {
504
+ return nil , fmt .Errorf ("could not find an image-name annotation for image %s" , img .ImageName )
505
+ }
506
506
helmParamName := getHelmParam (appSource .Helm .Parameters , helmAnnotationParamName )
507
507
if helmParamName == nil {
508
508
return nil , fmt .Errorf ("%s parameter not found" , helmAnnotationParamName )
509
509
}
510
-
511
- err = setHelmValue (& helmNewValues , helmAnnotationParamName , helmParamName .Value )
510
+ err = applyHelmParam (root , helmAnnotationParamName , helmParamName .Value )
512
511
if err != nil {
513
- return nil , fmt . Errorf ( "failed to set image parameter name value: %v" , err )
512
+ return nil , err
514
513
}
515
514
}
516
- return marshalWithIndent (& helmNewValues , defaultIndent )
515
+
516
+ out := root .String ()
517
+ return []byte (out ), nil
517
518
}
518
519
519
520
var params helmOverride
@@ -586,102 +587,112 @@ func mergeKustomizeOverride(t *kustomizeOverride, o *kustomizeOverride) {
586
587
}
587
588
}
588
589
589
- // Check if a key exists in a MappingNode and return the index of its value
590
- func findHelmValuesKey (m * yaml.Node , key string ) (int , bool ) {
591
- for i , item := range m .Content {
592
- if i % 2 == 0 && item .Value == key {
593
- return i + 1 , true
590
+ func findAnchorByName (root ast.Node , name string ) * ast.AnchorNode {
591
+ for _ , n := range ast .Filter (ast .AnchorType , root ) {
592
+ anchor := n .(* ast.AnchorNode )
593
+ nameNode := anchor .Name .(* ast.StringNode )
594
+ if nameNode .Value == name {
595
+ return anchor
594
596
}
595
597
}
596
- return - 1 , false
597
- }
598
-
599
- func nodeKindString (k yaml.Kind ) string {
600
- return map [yaml.Kind ]string {
601
- yaml .DocumentNode : "DocumentNode" ,
602
- yaml .SequenceNode : "SequenceNode" ,
603
- yaml .MappingNode : "MappingNode" ,
604
- yaml .ScalarNode : "ScalarNode" ,
605
- yaml .AliasNode : "AliasNode" ,
606
- }[k ]
598
+ return nil
607
599
}
608
600
609
- // set value of the parameter passed from the annotations.
610
- func setHelmValue (currentValues * yaml.Node , key string , value interface {}) error {
611
- current := currentValues
612
-
613
- // an unmarshalled document has a DocumentNode at the root, but
614
- // we navigate from a MappingNode.
615
- if current .Kind == yaml .DocumentNode {
616
- current = current .Content [0 ]
617
- }
618
-
619
- if current .Kind != yaml .MappingNode {
620
- return fmt .Errorf ("unexpected type %s for root" , nodeKindString (current .Kind ))
621
- }
622
-
623
- // Check if the full key exists
624
- if idx , found := findHelmValuesKey (current , key ); found {
625
- (* current ).Content [idx ].Value = value .(string )
601
+ func createOrUpdateNode (node ast.Node , path []string , value string , root ... ast.Node ) error {
602
+ // Keep track of the root in case we need to find an anchor for an alias
603
+ rootNode := node
604
+ if len (root ) > 0 {
605
+ rootNode = root [0 ]
606
+ }
607
+ // Base case. We've recursed all the way down the path and found a node
608
+ if len (path ) == 0 {
609
+ switch currentNode := node .(type ) {
610
+ case * ast.StringNode :
611
+ currentNode .Value = value
612
+ case * ast.AnchorNode :
613
+ currentNode .Value = ast .String (& token.Token {Value : value })
614
+ case * ast.AliasNode :
615
+ anchorName := currentNode .Value .(* ast.StringNode ).Value
616
+ anchor := findAnchorByName (rootNode .(* ast.MappingNode ), anchorName )
617
+ if anchor == nil {
618
+ return fmt .Errorf ("alias %q not found" , anchorName )
619
+ }
620
+ anchor .Value = ast .String (& token.Token {Value : value })
621
+ default :
622
+ return fmt .Errorf ("unexpected leaf node type %T" , node )
623
+ }
626
624
return nil
627
625
}
628
-
629
- var err error
630
- keys := strings .Split (key , "." )
631
-
632
- for i , k := range keys {
633
- if idx , found := findHelmValuesKey (current , k ); found {
634
- // Navigate deeper into the map
635
- current = (* current ).Content [idx ]
636
- // unpack one level of alias; an alias of an alias is not supported
637
- if current .Kind == yaml .AliasNode {
638
- current = current .Alias
626
+ key , rest := path [0 ], path [1 :]
627
+ switch currentNode := node .(type ) {
628
+ case * ast.DocumentNode :
629
+ // Create a base mapping node if the incoming document is empty
630
+ if currentNode .Body == nil {
631
+ newNode , err := gyaml .ValueToNode (map [string ]any {})
632
+ if err != nil {
633
+ return err
639
634
}
640
- if i == len (keys )- 1 {
641
- // If we're at the final key, set the value and return
642
- if current .Kind == yaml .ScalarNode {
643
- current .Value = value .(string )
644
- current .Tag = "!!str"
645
- } else {
646
- return fmt .Errorf ("unexpected type %s for key %s" , nodeKindString (current .Kind ), k )
647
- }
648
- return nil
649
- } else if current .Kind != yaml .MappingNode {
650
- return fmt .Errorf ("unexpected type %s for key %s" , nodeKindString (current .Kind ), k )
635
+ mn , ok := newNode .(* ast.MappingNode )
636
+ if ! ok {
637
+ return fmt .Errorf ("expected a MappingNode but got %T" , newNode )
651
638
}
652
- } else {
653
- if i == len (keys )- 1 {
654
- current .Content = append (current .Content ,
655
- & yaml.Node {
656
- Kind : yaml .ScalarNode ,
657
- Value : k ,
658
- Tag : "!!str" ,
659
- },
660
- & yaml.Node {
661
- Kind : yaml .ScalarNode ,
662
- Value : value .(string ),
663
- Tag : "!!str" ,
664
- },
665
- )
666
- return nil
667
- } else {
668
- current .Content = append (current .Content ,
669
- & yaml.Node {
670
- Kind : yaml .ScalarNode ,
671
- Value : k ,
672
- Tag : "!!str" ,
673
- },
674
- & yaml.Node {
675
- Kind : yaml .MappingNode ,
676
- Content : []* yaml.Node {},
677
- },
678
- )
679
- current = current .Content [len (current .Content )- 1 ]
639
+ currentNode .Body = mn
640
+ }
641
+ return createOrUpdateNode (currentNode .Body , path , value , currentNode .Body )
642
+ case * ast.AnchorNode :
643
+ return createOrUpdateNode (currentNode .Value , path , value , rootNode )
644
+ case * ast.AliasNode :
645
+ aliasName := currentNode .Value .(* ast.StringNode ).Value
646
+ anchor := findAnchorByName (rootNode .(* ast.MappingNode ), aliasName )
647
+ if anchor == nil {
648
+ return fmt .Errorf ("alias %q not found" , aliasName )
649
+ }
650
+ return createOrUpdateNode (anchor .Value , path , value , rootNode )
651
+ case * ast.MappingNode :
652
+ for _ , mappingValueNode := range currentNode .Values {
653
+ nodeKey := mappingValueNode .Key .String ()
654
+ if nodeKey == key {
655
+ return createOrUpdateNode (mappingValueNode .Value , rest , value , rootNode )
680
656
}
681
657
}
658
+ var newNodeData map [string ]any
659
+ if len (rest ) == 0 {
660
+ newNodeData = map [string ]any {key : value }
661
+ } else {
662
+ newNodeData = map [string ]any {key : map [string ]any {}}
663
+ }
664
+ newNode , err := gyaml .ValueToNode (newNodeData )
665
+ if err != nil {
666
+ return err
667
+ }
668
+ if err := ast .Merge (currentNode , newNode ); err != nil {
669
+ return err
670
+ }
671
+ if mappingValue , ok := newNode .(* ast.MappingNode ); ok {
672
+ return createOrUpdateNode (mappingValue .Values [0 ].Value , rest , value , rootNode )
673
+ }
682
674
}
683
675
684
- return err
676
+ return fmt .Errorf ("unexpected type %T for key attributes" , node )
677
+ }
678
+
679
+ func applyHelmParam (root * ast.File , attrPath string , value string ) error {
680
+ // check if literal path exists, and if it does, replace it
681
+ path , _ := gyaml .PathString (fmt .Sprintf ("$.'%s'" , attrPath ))
682
+ if _ , err := path .FilterFile (root ); err == nil {
683
+ stringNode , err := gyaml .ValueToNode (value )
684
+ if err != nil {
685
+ return err
686
+ }
687
+ if err := path .ReplaceWithNode (root , stringNode ); err != nil {
688
+ return err
689
+ }
690
+ return nil
691
+ }
692
+ if err := createOrUpdateNode (root .Docs [0 ], strings .Split (attrPath , "." ), value ); err != nil {
693
+ return err
694
+ }
695
+ return nil
685
696
}
686
697
687
698
func getWriteBackConfig (app * v1alpha1.Application , kubeClient * kube.ImageUpdaterKubernetesClient , argoClient ArgoCD ) (* WriteBackConfig , error ) {
0 commit comments