Skip to content

Commit 7320a55

Browse files
authored
fix(helmvalues): write helm image-name with or without registry url (#1319)
Signed-off-by: Cheng Fang <[email protected]>
1 parent 7ae5ee4 commit 7320a55

File tree

4 files changed

+637
-2
lines changed

4 files changed

+637
-2
lines changed

pkg/argocd/update.go

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,8 @@ func marshalParamsOverride(ctx context.Context, applicationImages *ApplicationIm
383383
images := GetImagesAndAliasesFromApplication(applicationImages)
384384

385385
var helmNewValues yaml.Node
386-
if isOnlyWhitespace(originalData) {
386+
emptyOriginalData := isOnlyWhitespace(originalData)
387+
if emptyOriginalData {
387388
// allow non-exists target file
388389
helmNewValues = yaml.Node{
389390
Kind: yaml.DocumentNode,
@@ -439,7 +440,26 @@ func marshalParamsOverride(ctx context.Context, applicationImages *ApplicationIm
439440
return nil, fmt.Errorf("%s parameter not found", helmParamName)
440441
}
441442

442-
err = setHelmValue(&helmNewValues, helmParamName, helmParamN.Value)
443+
// Determine which value to use for the image name parameter
444+
valueToSet := helmParamN.Value
445+
if !emptyOriginalData && image.HasRegistryPrefix(valueToSet) {
446+
// helmParamN.Value is in long form (has registry URL)
447+
// Check the original value in helmNewValues to see if it's in short form
448+
// Skip this check if originalData is empty
449+
originalValue, err := getHelmValue(&helmNewValues, helmParamName)
450+
if err == nil {
451+
// Original value exists and was found
452+
if !image.HasRegistryPrefix(originalValue) {
453+
// Original value is in short form, use the short form of the value to set
454+
valueToSet = image.ExtractShortForm(valueToSet)
455+
}
456+
// If originalValue is also in long form, keep using helmParamN.Value
457+
}
458+
// If getHelmValue returns an error (key not found), use helmParamN.Value as-is
459+
}
460+
// If helmParamN.Value is already in short form or originalData is empty, use it as-is
461+
462+
err = setHelmValue(&helmNewValues, helmParamName, valueToSet)
443463
if err != nil {
444464
return nil, fmt.Errorf("failed to set image parameter name value: %v", err)
445465
}
@@ -637,6 +657,98 @@ func setHelmValue(currentValues *yaml.Node, key string, value interface{}) error
637657
return err
638658
}
639659

660+
// getHelmValue retrieves a value from a yaml.Node using a key path.
661+
// The key can be in the form of "a.b.c" which can be:
662+
// 1. A nested hierarchy where "a" has "b" which has "c"
663+
// 2. A literal key "a.b.c" if the nested structure doesn't exist
664+
// Returns the value as a string and an error if the key is not found.
665+
func getHelmValue(values *yaml.Node, key string) (string, error) {
666+
current := values
667+
668+
// an unmarshalled document has a DocumentNode at the root, but
669+
// we navigate from a MappingNode.
670+
if current.Kind == yaml.DocumentNode {
671+
if len(current.Content) == 0 {
672+
return "", fmt.Errorf("empty document node")
673+
}
674+
current = current.Content[0]
675+
}
676+
677+
if current.Kind != yaml.MappingNode {
678+
return "", fmt.Errorf("unexpected type %s for root", nodeKindString(current.Kind))
679+
}
680+
681+
// First, try to navigate as nested path (a.b.c)
682+
keys := strings.Split(key, ".")
683+
currentForNested := current
684+
685+
for i, k := range keys {
686+
var idPtr *int
687+
// Handle array indexing pattern like "key[0]"
688+
keyPart := k
689+
matches := re.FindStringSubmatch(k)
690+
if matches != nil {
691+
idStr := matches[2]
692+
id, err := strconv.Atoi(idStr)
693+
if err != nil {
694+
return "", fmt.Errorf("id \"%s\" in yaml array must match pattern ^(.*)\\[(.*)\\]$", idStr)
695+
}
696+
idPtr = &id
697+
keyPart = matches[1]
698+
}
699+
700+
if idx, found := findHelmValuesKey(currentForNested, keyPart); found {
701+
// Navigate deeper into the map
702+
currentForNested = currentForNested.Content[idx]
703+
// unpack one level of alias; an alias of an alias is not supported
704+
if currentForNested.Kind == yaml.AliasNode {
705+
currentForNested = currentForNested.Alias
706+
}
707+
708+
if currentForNested.Kind == yaml.SequenceNode {
709+
if idPtr == nil {
710+
// Can't navigate into sequence without index
711+
break
712+
}
713+
if *idPtr < 0 || *idPtr >= len(currentForNested.Content) {
714+
break
715+
}
716+
currentForNested = currentForNested.Content[*idPtr]
717+
}
718+
719+
if i == len(keys)-1 {
720+
// If we're at the final key, return the value
721+
if currentForNested.Kind == yaml.ScalarNode {
722+
return currentForNested.Value, nil
723+
}
724+
// If it's not a scalar, the nested path doesn't match, fall through to literal check
725+
break
726+
} else if currentForNested.Kind != yaml.MappingNode {
727+
// Can't navigate further, nested path doesn't exist
728+
break
729+
}
730+
} else {
731+
// Key not found in nested path, fall through to literal check
732+
break
733+
}
734+
}
735+
736+
// If nested path didn't work, try as a literal key "a.b.c"
737+
if idx, found := findHelmValuesKey(current, key); found {
738+
valueNode := current.Content[idx]
739+
// unpack one level of alias
740+
if valueNode.Kind == yaml.AliasNode {
741+
valueNode = valueNode.Alias
742+
}
743+
if valueNode.Kind == yaml.ScalarNode {
744+
return valueNode.Value, nil
745+
}
746+
return "", fmt.Errorf("literal key \"%s\" found but is not a scalar value", key)
747+
}
748+
749+
return "", fmt.Errorf("key \"%s\" not found as nested path or literal key", key)
750+
}
751+
640752
func parseDefaultTarget(appNamespace string, appName string, path string, kubeClient *kube.ImageUpdaterKubernetesClient) string {
641753
// when running from command line and argocd-namespace is not set, e.g., via --argocd-namespace option,
642754
// kubeClient.Namespace may be resolved to "default". In this case, also use the file name without namespace

0 commit comments

Comments
 (0)