Skip to content

Commit 5ed9f6d

Browse files
authored
fix(helmvalues): write helm image-name with or without registry url (#1312)
Assisted-by: Cursor Signed-off-by: Cheng Fang <cfang@redhat.com>
1 parent 8c42fca commit 5ed9f6d

File tree

4 files changed

+615
-2
lines changed

4 files changed

+615
-2
lines changed

pkg/argocd/update.go

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,7 +503,8 @@ func marshalParamsOverride(app *v1alpha1.Application, originalData []byte) ([]by
503503
images := GetImagesAndAliasesFromApplication(app)
504504

505505
var helmNewValues yaml.Node
506-
if isOnlyWhitespace(originalData) {
506+
emptyOriginalData := isOnlyWhitespace(originalData)
507+
if emptyOriginalData {
507508
// allow non-exists target file
508509
helmNewValues = yaml.Node{
509510
Kind: yaml.DocumentNode,
@@ -559,7 +560,26 @@ func marshalParamsOverride(app *v1alpha1.Application, originalData []byte) ([]by
559560
return nil, fmt.Errorf("%s parameter not found", helmAnnotationParamName)
560561
}
561562

562-
err = setHelmValue(&helmNewValues, helmAnnotationParamName, helmParamName.Value)
563+
// Determine which value to use for the image name parameter
564+
valueToSet := helmParamName.Value
565+
if !emptyOriginalData && image.HasRegistryPrefix(valueToSet) {
566+
// helmParamName.Value is in long form (has registry URL)
567+
// Check the original value in helmNewValues to see if it's in short form
568+
// Skip this check if originalData is empty
569+
originalValue, err := getHelmValue(&helmNewValues, helmAnnotationParamName)
570+
if err == nil {
571+
// Original value exists and was found
572+
if !image.HasRegistryPrefix(originalValue) {
573+
// Original value is in short form, use the short form of the value to set
574+
valueToSet = image.ExtractShortForm(valueToSet)
575+
}
576+
// If originalValue is also in long form, keep using helmParamName.Value
577+
}
578+
// If getHelmValue returns an error (key not found), use helmParamName.Value as-is
579+
}
580+
// If helmParamName.Value is already in short form or originalData is empty, use it as-is
581+
582+
err = setHelmValue(&helmNewValues, helmAnnotationParamName, valueToSet)
563583
if err != nil {
564584
return nil, fmt.Errorf("failed to set image parameter name value: %v", err)
565585
}
@@ -757,6 +777,98 @@ func setHelmValue(currentValues *yaml.Node, key string, value interface{}) error
757777
return err
758778
}
759779

780+
// getHelmValue retrieves a value from a yaml.Node using a key path.
781+
// The key can be in the form of "a.b.c" which can be:
782+
// 1. A nested hierarchy where "a" has "b" which has "c"
783+
// 2. A literal key "a.b.c" if the nested structure doesn't exist
784+
// Returns the value as a string and an error if the key is not found.
785+
func getHelmValue(values *yaml.Node, key string) (string, error) {
786+
current := values
787+
788+
// an unmarshalled document has a DocumentNode at the root, but
789+
// we navigate from a MappingNode.
790+
if current.Kind == yaml.DocumentNode {
791+
if len(current.Content) == 0 {
792+
return "", fmt.Errorf("empty document node")
793+
}
794+
current = current.Content[0]
795+
}
796+
797+
if current.Kind != yaml.MappingNode {
798+
return "", fmt.Errorf("unexpected type %s for root", nodeKindString(current.Kind))
799+
}
800+
801+
// First, try to navigate as nested path (a.b.c)
802+
keys := strings.Split(key, ".")
803+
currentForNested := current
804+
805+
for i, k := range keys {
806+
var idPtr *int
807+
// Handle array indexing pattern like "key[0]"
808+
keyPart := k
809+
matches := re.FindStringSubmatch(k)
810+
if matches != nil {
811+
idStr := matches[2]
812+
id, err := strconv.Atoi(idStr)
813+
if err != nil {
814+
return "", fmt.Errorf("id \"%s\" in yaml array must match pattern ^(.*)\\[(.*)\\]$", idStr)
815+
}
816+
idPtr = &id
817+
keyPart = matches[1]
818+
}
819+
820+
if idx, found := findHelmValuesKey(currentForNested, keyPart); found {
821+
// Navigate deeper into the map
822+
currentForNested = currentForNested.Content[idx]
823+
// unpack one level of alias; an alias of an alias is not supported
824+
if currentForNested.Kind == yaml.AliasNode {
825+
currentForNested = currentForNested.Alias
826+
}
827+
828+
if currentForNested.Kind == yaml.SequenceNode {
829+
if idPtr == nil {
830+
// Can't navigate into sequence without index
831+
break
832+
}
833+
if *idPtr < 0 || *idPtr >= len(currentForNested.Content) {
834+
break
835+
}
836+
currentForNested = currentForNested.Content[*idPtr]
837+
}
838+
839+
if i == len(keys)-1 {
840+
// If we're at the final key, return the value
841+
if currentForNested.Kind == yaml.ScalarNode {
842+
return currentForNested.Value, nil
843+
}
844+
// If it's not a scalar, the nested path doesn't match, fall through to literal check
845+
break
846+
} else if currentForNested.Kind != yaml.MappingNode {
847+
// Can't navigate further, nested path doesn't exist
848+
break
849+
}
850+
} else {
851+
// Key not found in nested path, fall through to literal check
852+
break
853+
}
854+
}
855+
856+
// If nested path didn't work, try as a literal key "a.b.c"
857+
if idx, found := findHelmValuesKey(current, key); found {
858+
valueNode := current.Content[idx]
859+
// unpack one level of alias
860+
if valueNode.Kind == yaml.AliasNode {
861+
valueNode = valueNode.Alias
862+
}
863+
if valueNode.Kind == yaml.ScalarNode {
864+
return valueNode.Value, nil
865+
}
866+
return "", fmt.Errorf("literal key \"%s\" found but is not a scalar value", key)
867+
}
868+
869+
return "", fmt.Errorf("key \"%s\" not found as nested path or literal key", key)
870+
}
871+
760872
func getWriteBackConfig(app *v1alpha1.Application, kubeClient *kube.ImageUpdaterKubernetesClient, argoClient ArgoCD) (*WriteBackConfig, error) {
761873
wbc := &WriteBackConfig{}
762874
// Default write-back is to use Argo CD API

0 commit comments

Comments
 (0)