istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/translate/translate_value.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package translate
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  	"strings"
    21  
    22  	"sigs.k8s.io/yaml"
    23  
    24  	"istio.io/api/operator/v1alpha1"
    25  	"istio.io/istio/operator/pkg/metrics"
    26  	"istio.io/istio/operator/pkg/name"
    27  	"istio.io/istio/operator/pkg/tpath"
    28  	"istio.io/istio/operator/pkg/util"
    29  	"istio.io/istio/operator/pkg/version"
    30  	oversion "istio.io/istio/operator/version"
    31  )
    32  
    33  // ReverseTranslator is a set of mappings to translate between values.yaml and API paths, charts, k8s paths.
    34  type ReverseTranslator struct {
    35  	Version version.MinorVersion
    36  	// APIMapping is Values.yaml path to API path mapping using longest prefix match. If the path is a non-leaf node,
    37  	// the output path is the matching portion of the path, plus any remaining output path.
    38  	APIMapping map[string]*Translation `yaml:"apiMapping,omitempty"`
    39  	// KubernetesPatternMapping defines mapping patterns from k8s resource paths to IstioOperator API paths.
    40  	KubernetesPatternMapping map[string]string `yaml:"kubernetesPatternMapping,omitempty"`
    41  	// KubernetesMapping defines actual k8s mappings generated from KubernetesPatternMapping before each translation.
    42  	KubernetesMapping map[string]*Translation `yaml:"kubernetesMapping,omitempty"`
    43  	// GatewayKubernetesMapping defines actual k8s mappings for gateway components generated from KubernetesPatternMapping before each translation.
    44  	GatewayKubernetesMapping gatewayKubernetesMapping `yaml:"GatewayKubernetesMapping,omitempty"`
    45  	// ValuesToComponentName defines mapping from value path to component name in API paths.
    46  	ValuesToComponentName map[string]name.ComponentName `yaml:"valuesToComponentName,omitempty"`
    47  }
    48  
    49  type gatewayKubernetesMapping struct {
    50  	IngressMapping map[string]*Translation
    51  	EgressMapping  map[string]*Translation
    52  }
    53  
    54  var (
    55  	// Component enablement mapping. Ex "{{.ValueComponent}}.enabled": Components.{{.ComponentName}}.enabled}", nil},
    56  	componentEnablementPattern = "Components.{{.ComponentName}}.Enabled"
    57  	// specialComponentPath lists cases of component path of values.yaml we need to have special treatment.
    58  	specialComponentPath = map[string]bool{
    59  		"gateways":                      true,
    60  		"gateways.istio-ingressgateway": true,
    61  		"gateways.istio-egressgateway":  true,
    62  	}
    63  
    64  	skipTranslate = map[name.ComponentName]bool{
    65  		name.IstioBaseComponentName:          true,
    66  		name.IstioOperatorComponentName:      true,
    67  		name.IstioOperatorCustomResourceName: true,
    68  		name.CNIComponentName:                true,
    69  		name.IstiodRemoteComponentName:       true,
    70  		name.ZtunnelComponentName:            true,
    71  	}
    72  
    73  	gatewayPathMapping = map[string]name.ComponentName{
    74  		"gateways.istio-ingressgateway": name.IngressComponentName,
    75  		"gateways.istio-egressgateway":  name.EgressComponentName,
    76  	}
    77  )
    78  
    79  // initAPIMapping generate the reverse mapping from original translator apiMapping.
    80  func (t *ReverseTranslator) initAPIAndComponentMapping() {
    81  	ts := NewTranslator()
    82  	t.APIMapping = make(map[string]*Translation)
    83  	t.KubernetesMapping = make(map[string]*Translation)
    84  	t.ValuesToComponentName = make(map[string]name.ComponentName)
    85  	for valKey, outVal := range ts.APIMapping {
    86  		t.APIMapping[outVal.OutPath] = &Translation{valKey, nil}
    87  	}
    88  	for cn, cm := range ts.ComponentMaps {
    89  		// we use dedicated translateGateway for gateway instead
    90  		if !skipTranslate[cn] && !cm.SkipReverseTranslate && !cn.IsGateway() {
    91  			t.ValuesToComponentName[cm.ToHelmValuesTreeRoot] = cn
    92  		}
    93  	}
    94  }
    95  
    96  // initK8SMapping generates the k8s settings mapping for components that are enabled based on templates.
    97  func (t *ReverseTranslator) initK8SMapping() error {
    98  	outputMapping := make(map[string]*Translation)
    99  	for valKey, componentName := range t.ValuesToComponentName {
   100  		for K8SValKey, outPathTmpl := range t.KubernetesPatternMapping {
   101  			newKey, err := renderComponentName(K8SValKey, valKey)
   102  			if err != nil {
   103  				return err
   104  			}
   105  			newVal, err := renderFeatureComponentPathTemplate(outPathTmpl, componentName)
   106  			if err != nil {
   107  				return err
   108  			}
   109  			outputMapping[newKey] = &Translation{newVal, nil}
   110  		}
   111  	}
   112  
   113  	t.KubernetesMapping = outputMapping
   114  
   115  	igwOutputMapping := make(map[string]*Translation)
   116  	egwOutputMapping := make(map[string]*Translation)
   117  	for valKey, componentName := range gatewayPathMapping {
   118  		mapping := igwOutputMapping
   119  		if componentName == name.EgressComponentName {
   120  			mapping = egwOutputMapping
   121  		}
   122  		for K8SValKey, outPathTmpl := range t.KubernetesPatternMapping {
   123  			newKey, err := renderComponentName(K8SValKey, valKey)
   124  			if err != nil {
   125  				return err
   126  			}
   127  			newP := util.PathFromString(outPathTmpl)
   128  			mapping[newKey] = &Translation{newP[len(newP)-2:].String(), nil}
   129  		}
   130  	}
   131  	t.GatewayKubernetesMapping = gatewayKubernetesMapping{IngressMapping: igwOutputMapping, EgressMapping: egwOutputMapping}
   132  	return nil
   133  }
   134  
   135  // NewReverseTranslator creates a new ReverseTranslator for minorVersion and returns a ptr to it.
   136  func NewReverseTranslator() *ReverseTranslator {
   137  	rt := &ReverseTranslator{
   138  		KubernetesPatternMapping: map[string]string{
   139  			"{{.ValueComponentName}}.env":                   "Components.{{.ComponentName}}.K8s.Env",
   140  			"{{.ValueComponentName}}.autoscaleEnabled":      "Components.{{.ComponentName}}.K8s.HpaSpec",
   141  			"{{.ValueComponentName}}.imagePullPolicy":       "Components.{{.ComponentName}}.K8s.ImagePullPolicy",
   142  			"{{.ValueComponentName}}.nodeSelector":          "Components.{{.ComponentName}}.K8s.NodeSelector",
   143  			"{{.ValueComponentName}}.tolerations":           "Components.{{.ComponentName}}.K8s.Tolerations",
   144  			"{{.ValueComponentName}}.podDisruptionBudget":   "Components.{{.ComponentName}}.K8s.PodDisruptionBudget",
   145  			"{{.ValueComponentName}}.podAnnotations":        "Components.{{.ComponentName}}.K8s.PodAnnotations",
   146  			"{{.ValueComponentName}}.priorityClassName":     "Components.{{.ComponentName}}.K8s.PriorityClassName",
   147  			"{{.ValueComponentName}}.readinessProbe":        "Components.{{.ComponentName}}.K8s.ReadinessProbe",
   148  			"{{.ValueComponentName}}.replicaCount":          "Components.{{.ComponentName}}.K8s.ReplicaCount",
   149  			"{{.ValueComponentName}}.resources":             "Components.{{.ComponentName}}.K8s.Resources",
   150  			"{{.ValueComponentName}}.rollingMaxSurge":       "Components.{{.ComponentName}}.K8s.Strategy",
   151  			"{{.ValueComponentName}}.rollingMaxUnavailable": "Components.{{.ComponentName}}.K8s.Strategy",
   152  			"{{.ValueComponentName}}.serviceAnnotations":    "Components.{{.ComponentName}}.K8s.ServiceAnnotations",
   153  		},
   154  	}
   155  	rt.initAPIAndComponentMapping()
   156  	rt.Version = oversion.OperatorBinaryVersion.MinorVersion
   157  	return rt
   158  }
   159  
   160  // TranslateFromValueToSpec translates from values.yaml value to IstioOperatorSpec.
   161  func (t *ReverseTranslator) TranslateFromValueToSpec(values []byte, force bool) (controlPlaneSpec *v1alpha1.IstioOperatorSpec, err error) {
   162  	yamlTree := make(map[string]any)
   163  	err = yaml.Unmarshal(values, &yamlTree)
   164  	if err != nil {
   165  		return nil, fmt.Errorf("error when unmarshalling into untype tree %v", err)
   166  	}
   167  
   168  	outputTree := make(map[string]any)
   169  	err = t.TranslateTree(yamlTree, outputTree, nil)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	outputVal, err := yaml.Marshal(outputTree)
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	cpSpec := &v1alpha1.IstioOperatorSpec{}
   179  	err = util.UnmarshalWithJSONPB(string(outputVal), cpSpec, force)
   180  	if err != nil {
   181  		return nil, fmt.Errorf("error when unmarshalling into control plane spec %v, \nyaml:\n %s", err, outputVal)
   182  	}
   183  
   184  	return cpSpec, nil
   185  }
   186  
   187  // TranslateTree translates input value.yaml Tree to ControlPlaneSpec Tree.
   188  func (t *ReverseTranslator) TranslateTree(valueTree map[string]any, cpSpecTree map[string]any, path util.Path) error {
   189  	// translate enablement and namespace
   190  	err := t.setEnablementFromValue(valueTree, cpSpecTree)
   191  	if err != nil {
   192  		return fmt.Errorf("error when translating enablement and namespace from value.yaml tree: %v", err)
   193  	}
   194  	// translate with api mapping
   195  	err = t.translateAPI(valueTree, cpSpecTree)
   196  	if err != nil {
   197  		return fmt.Errorf("error when translating value.yaml tree with global mapping: %v", err)
   198  	}
   199  
   200  	// translate with k8s mapping
   201  	if err := t.TranslateK8S(valueTree, cpSpecTree); err != nil {
   202  		return err
   203  	}
   204  
   205  	if err := t.translateGateway(valueTree, cpSpecTree); err != nil {
   206  		return fmt.Errorf("error when translating gateway with kubernetes mapping: %v", err.Error())
   207  	}
   208  	// translate remaining untranslated paths into component values
   209  	err = t.translateRemainingPaths(valueTree, cpSpecTree, nil)
   210  	if err != nil {
   211  		return fmt.Errorf("error when translating remaining path: %v", err)
   212  	}
   213  	return nil
   214  }
   215  
   216  // TranslateK8S is a helper function to translate k8s settings from values.yaml to IstioOperator, except for gateways.
   217  func (t *ReverseTranslator) TranslateK8S(valueTree map[string]any, cpSpecTree map[string]any) error {
   218  	// translate with k8s mapping
   219  	if err := t.initK8SMapping(); err != nil {
   220  		return fmt.Errorf("error when initiating k8s mapping: %v", err)
   221  	}
   222  	if err := t.translateK8sTree(valueTree, cpSpecTree, t.KubernetesMapping); err != nil {
   223  		return fmt.Errorf("error when translating value.yaml tree with kubernetes mapping: %v", err)
   224  	}
   225  	return nil
   226  }
   227  
   228  // setEnablementFromValue translates the enablement value of components in the values.yaml
   229  // tree, based on feature/component inheritance relationship.
   230  func (t *ReverseTranslator) setEnablementFromValue(valueSpec map[string]any, root map[string]any) error {
   231  	for _, cni := range t.ValuesToComponentName {
   232  		enabled, pathExist, err := IsComponentEnabledFromValue(cni, valueSpec)
   233  		if err != nil {
   234  			return err
   235  		}
   236  		if !pathExist {
   237  			continue
   238  		}
   239  		tmpl := componentEnablementPattern
   240  		ceVal, err := renderFeatureComponentPathTemplate(tmpl, cni)
   241  		if err != nil {
   242  			return err
   243  		}
   244  		outCP := util.ToYAMLPath(ceVal)
   245  		// set component enablement
   246  		if err := tpath.WriteNode(root, outCP, enabled); err != nil {
   247  			return err
   248  		}
   249  	}
   250  
   251  	return nil
   252  }
   253  
   254  // WarningForGatewayK8SSettings creates deprecated warning messages
   255  // when user try to set kubernetes settings for gateways via values api.
   256  func (t *ReverseTranslator) WarningForGatewayK8SSettings(valuesOverlay string) (string, error) {
   257  	gwOverlay, err := tpath.GetConfigSubtree(valuesOverlay, "gateways")
   258  	if err != nil {
   259  		return "", fmt.Errorf("error getting gateways overlay from valuesOverlayYaml %v", err)
   260  	}
   261  	if gwOverlay == "" {
   262  		return "", nil
   263  	}
   264  	var deprecatedFields []string
   265  	for inPath := range t.GatewayKubernetesMapping.IngressMapping {
   266  		_, found, err := tpath.GetPathContext(valuesOverlay, util.ToYAMLPath(inPath), false)
   267  		if err != nil {
   268  			scope.Debug(err.Error())
   269  			continue
   270  		}
   271  		if found {
   272  			deprecatedFields = append(deprecatedFields, inPath)
   273  		}
   274  	}
   275  	for inPath := range t.GatewayKubernetesMapping.EgressMapping {
   276  		_, found, err := tpath.GetPathContext(valuesOverlay, util.ToYAMLPath(inPath), false)
   277  		if err != nil {
   278  			scope.Debug(err.Error())
   279  			continue
   280  		}
   281  		if found {
   282  			deprecatedFields = append(deprecatedFields, inPath)
   283  		}
   284  	}
   285  	if len(deprecatedFields) == 0 {
   286  		return "", nil
   287  	}
   288  	warningMessage := fmt.Sprintf("using deprecated values api paths: %s.\n"+
   289  		" please use k8s spec of gateway components instead\n", strings.Join(deprecatedFields, ","))
   290  	return warningMessage, nil
   291  }
   292  
   293  // translateGateway handles translation for gateways specific configuration
   294  func (t *ReverseTranslator) translateGateway(valueSpec map[string]any, root map[string]any) error {
   295  	for inPath, outPath := range gatewayPathMapping {
   296  		enabled, pathExist, err := IsComponentEnabledFromValue(outPath, valueSpec)
   297  		if err != nil {
   298  			return err
   299  		}
   300  		if !pathExist && !enabled {
   301  			continue
   302  		}
   303  		gwSpecs := make([]map[string]any, 1)
   304  		gwSpec := make(map[string]any)
   305  		gwSpecs[0] = gwSpec
   306  		gwSpec["enabled"] = enabled
   307  		gwSpec["name"] = util.ToYAMLPath(inPath)[1]
   308  		outCP := util.ToYAMLPath("Components." + string(outPath))
   309  
   310  		if enabled {
   311  			mapping := t.GatewayKubernetesMapping.IngressMapping
   312  			if outPath == name.EgressComponentName {
   313  				mapping = t.GatewayKubernetesMapping.EgressMapping
   314  			}
   315  			err = t.translateK8sTree(valueSpec, gwSpec, mapping)
   316  			if err != nil {
   317  				return err
   318  			}
   319  		}
   320  		err = tpath.WriteNode(root, outCP, gwSpecs)
   321  		if err != nil {
   322  			return err
   323  		}
   324  	}
   325  	return nil
   326  }
   327  
   328  // TranslateK8SfromValueToIOP use reverse translation to convert k8s settings defined in values API to IOP API.
   329  // this ensures that user overlays that set k8s through spec.values
   330  // are not overridden by spec.components.X.k8s settings in the base profiles
   331  func (t *ReverseTranslator) TranslateK8SfromValueToIOP(userOverlayYaml string) (string, error) {
   332  	valuesOverlay, err := tpath.GetConfigSubtree(userOverlayYaml, "spec.values")
   333  	if err != nil {
   334  		scope.Debugf("no spec.values section from userOverlayYaml %v", err)
   335  		return "", nil
   336  	}
   337  	valuesOverlayTree := make(map[string]any)
   338  	err = yaml.Unmarshal([]byte(valuesOverlay), &valuesOverlayTree)
   339  	if err != nil {
   340  		return "", fmt.Errorf("error unmarshalling values overlay yaml into untype tree %v", err)
   341  	}
   342  	iopSpecTree := make(map[string]any)
   343  	iopSpecOverlay, err := tpath.GetConfigSubtree(userOverlayYaml, "spec")
   344  	if err != nil {
   345  		return "", fmt.Errorf("error getting iop spec subtree from overlay yaml %v", err)
   346  	}
   347  	err = yaml.Unmarshal([]byte(iopSpecOverlay), &iopSpecTree)
   348  	if err != nil {
   349  		return "", fmt.Errorf("error unmarshalling spec overlay yaml into tree %v", err)
   350  	}
   351  	if err = t.TranslateK8S(valuesOverlayTree, iopSpecTree); err != nil {
   352  		return "", err
   353  	}
   354  	warning, err := t.WarningForGatewayK8SSettings(valuesOverlay)
   355  	if err != nil {
   356  		return "", fmt.Errorf("error handling values gateway k8s settings: %v", err)
   357  	}
   358  	if warning != "" {
   359  		return "", fmt.Errorf(warning)
   360  	}
   361  	iopSpecTreeYAML, err := yaml.Marshal(iopSpecTree)
   362  	if err != nil {
   363  		return "", fmt.Errorf("error marshaling reverse translated tree %v", err)
   364  	}
   365  	iopTreeYAML, err := tpath.AddSpecRoot(string(iopSpecTreeYAML))
   366  	if err != nil {
   367  		return "", fmt.Errorf("error adding spec root: %v", err)
   368  	}
   369  	// overlay the reverse translated iopTreeYAML back to userOverlayYaml
   370  	finalYAML, err := util.OverlayYAML(userOverlayYaml, iopTreeYAML)
   371  	if err != nil {
   372  		return "", fmt.Errorf("failed to overlay the reverse translated iopTreeYAML: %v", err)
   373  	}
   374  	return finalYAML, err
   375  }
   376  
   377  // translateStrategy translates Deployment Strategy related configurations from helm values.yaml tree.
   378  func translateStrategy(fieldName string, outPath string, value any, cpSpecTree map[string]any) error {
   379  	fieldMap := map[string]string{
   380  		"rollingMaxSurge":       "maxSurge",
   381  		"rollingMaxUnavailable": "maxUnavailable",
   382  	}
   383  	newFieldName, ok := fieldMap[fieldName]
   384  	if !ok {
   385  		return fmt.Errorf("expected field name found in values.yaml: %s", fieldName)
   386  	}
   387  	outPath += ".rollingUpdate." + newFieldName
   388  
   389  	scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", outPath)
   390  	if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath(outPath), value); err != nil {
   391  		return err
   392  	}
   393  	return nil
   394  }
   395  
   396  // translateEnv translates env value from helm values.yaml tree.
   397  func translateEnv(outPath string, value any, cpSpecTree map[string]any) error {
   398  	envMap, ok := value.(map[string]any)
   399  	if !ok {
   400  		return fmt.Errorf("expect env node type to be map[string]interface{} but got: %T", value)
   401  	}
   402  	if len(envMap) == 0 {
   403  		return nil
   404  	}
   405  	scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", outPath)
   406  	nc, found, _ := tpath.GetPathContext(cpSpecTree, util.ToYAMLPath(outPath), false)
   407  	var envValStr []byte
   408  	if nc != nil {
   409  		envValStr, _ = yaml.Marshal(nc.Node)
   410  	}
   411  	if !found || strings.TrimSpace(string(envValStr)) == "{}" {
   412  		scope.Debugf("path doesn't have value in k8s setting with output path %s, override with helm Value.yaml tree", outPath)
   413  		outEnv := make([]map[string]any, len(envMap))
   414  		keys := make([]string, 0, len(envMap))
   415  		for k := range envMap {
   416  			keys = append(keys, k)
   417  		}
   418  		sort.Strings(keys)
   419  		for i, k := range keys {
   420  			outEnv[i] = make(map[string]any)
   421  			outEnv[i]["name"] = k
   422  			outEnv[i]["value"] = fmt.Sprintf("%v", envMap[k])
   423  		}
   424  		if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath(outPath), outEnv); err != nil {
   425  			return err
   426  		}
   427  	} else {
   428  		scope.Debugf("path has value in k8s setting with output path %s, merge it with helm Value.yaml tree", outPath)
   429  		keys := make([]string, 0, len(envMap))
   430  		for k := range envMap {
   431  			keys = append(keys, k)
   432  		}
   433  		sort.Strings(keys)
   434  		for _, k := range keys {
   435  			outEnv := make(map[string]any)
   436  			outEnv["name"] = k
   437  			outEnv["value"] = fmt.Sprintf("%v", envMap[k])
   438  			if err := tpath.MergeNode(cpSpecTree, util.ToYAMLPath(outPath), outEnv); err != nil {
   439  				return err
   440  			}
   441  		}
   442  	}
   443  	return nil
   444  }
   445  
   446  // translateK8sTree is internal method for translating K8s configurations from value.yaml tree.
   447  func (t *ReverseTranslator) translateK8sTree(valueTree map[string]any,
   448  	cpSpecTree map[string]any, mapping map[string]*Translation,
   449  ) error {
   450  	for inPath, v := range mapping {
   451  		scope.Debugf("Checking for k8s path %s in helm Value.yaml tree", inPath)
   452  		path := util.PathFromString(inPath)
   453  		k8sSettingName := ""
   454  		if len(path) != 0 {
   455  			k8sSettingName = path[len(path)-1]
   456  		}
   457  		if k8sSettingName == "autoscaleEnabled" {
   458  			continue
   459  		}
   460  		m, found, err := tpath.Find(valueTree, util.ToYAMLPath(inPath))
   461  		if err != nil {
   462  			return err
   463  		}
   464  		if !found {
   465  			scope.Debugf("path %s not found in helm Value.yaml tree, skip mapping.", inPath)
   466  			continue
   467  		}
   468  
   469  		if mstr, ok := m.(string); ok && mstr == "" {
   470  			scope.Debugf("path %s is empty string, skip mapping.", inPath)
   471  			continue
   472  		}
   473  		// Zero int values are due to proto3 compiling to scalars rather than ptrs. Skip these because values of 0 are
   474  		// the default in destination fields and need not be set explicitly.
   475  		if mint, ok := util.ToIntValue(m); ok && mint == 0 {
   476  			scope.Debugf("path %s is int 0, skip mapping.", inPath)
   477  			continue
   478  		}
   479  
   480  		switch k8sSettingName {
   481  		case "env":
   482  			err := translateEnv(v.OutPath, m, cpSpecTree)
   483  			if err != nil {
   484  				return fmt.Errorf("error in translating k8s Env: %s", err)
   485  			}
   486  
   487  		case "rollingMaxSurge", "rollingMaxUnavailable":
   488  			err := translateStrategy(k8sSettingName, v.OutPath, m, cpSpecTree)
   489  			if err != nil {
   490  				return fmt.Errorf("error in translating k8s Strategy: %s", err)
   491  			}
   492  
   493  		default:
   494  			if util.IsValueNilOrDefault(m) {
   495  				continue
   496  			}
   497  			output := util.ToYAMLPath(v.OutPath)
   498  			scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", output)
   499  
   500  			if err := tpath.WriteNode(cpSpecTree, output, m); err != nil {
   501  				return err
   502  			}
   503  		}
   504  		metrics.LegacyPathTranslationTotal.Increment()
   505  
   506  		if _, err := tpath.Delete(valueTree, util.ToYAMLPath(inPath)); err != nil {
   507  			return err
   508  		}
   509  	}
   510  	return nil
   511  }
   512  
   513  // translateRemainingPaths translates remaining paths that are not available in existing mappings.
   514  func (t *ReverseTranslator) translateRemainingPaths(valueTree map[string]any,
   515  	cpSpecTree map[string]any, path util.Path,
   516  ) error {
   517  	for key, val := range valueTree {
   518  		newPath := append(path, key)
   519  		// value set to nil means no translation needed or being translated already.
   520  		if val == nil {
   521  			continue
   522  		}
   523  		switch node := val.(type) {
   524  		case map[string]any:
   525  			err := t.translateRemainingPaths(node, cpSpecTree, newPath)
   526  			if err != nil {
   527  				return err
   528  			}
   529  		case []any:
   530  			if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath("Values."+newPath.String()), node); err != nil {
   531  				return err
   532  			}
   533  		// remaining leaf need to be put into root.values
   534  		default:
   535  			if t.isEnablementPath(newPath) {
   536  				continue
   537  			}
   538  			if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath("Values."+newPath.String()), val); err != nil {
   539  				return err
   540  			}
   541  		}
   542  	}
   543  	return nil
   544  }
   545  
   546  // translateAPI is internal method for translating value.yaml tree based on API mapping.
   547  func (t *ReverseTranslator) translateAPI(valueTree map[string]any,
   548  	cpSpecTree map[string]any,
   549  ) error {
   550  	for inPath, v := range t.APIMapping {
   551  		scope.Debugf("Checking for path %s in helm Value.yaml tree", inPath)
   552  		m, found, err := tpath.Find(valueTree, util.ToYAMLPath(inPath))
   553  		if err != nil {
   554  			return err
   555  		}
   556  		if !found {
   557  			scope.Debugf("path %s not found in helm Value.yaml tree, skip mapping.", inPath)
   558  			continue
   559  		}
   560  		if mstr, ok := m.(string); ok && mstr == "" {
   561  			scope.Debugf("path %s is empty string, skip mapping.", inPath)
   562  			continue
   563  		}
   564  		// Zero int values are due to proto3 compiling to scalars rather than ptrs. Skip these because values of 0 are
   565  		// the default in destination fields and need not be set explicitly.
   566  		if mint, ok := util.ToIntValue(m); ok && mint == 0 {
   567  			scope.Debugf("path %s is int 0, skip mapping.", inPath)
   568  			continue
   569  		}
   570  
   571  		path := util.ToYAMLPath(v.OutPath)
   572  		scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", path)
   573  		metrics.LegacyPathTranslationTotal.
   574  			With(metrics.ResourceKindLabel.Value(inPath)).Increment()
   575  
   576  		if err := tpath.WriteNode(cpSpecTree, path, m); err != nil {
   577  			return err
   578  		}
   579  
   580  		if _, err := tpath.Delete(valueTree, util.ToYAMLPath(inPath)); err != nil {
   581  			return err
   582  		}
   583  	}
   584  	return nil
   585  }
   586  
   587  // isEnablementPath is helper function to check whether paths represent enablement of components in values.yaml
   588  func (t *ReverseTranslator) isEnablementPath(path util.Path) bool {
   589  	if len(path) < 2 || path[len(path)-1] != "enabled" {
   590  		return false
   591  	}
   592  
   593  	pf := path[:len(path)-1].String()
   594  	if specialComponentPath[pf] {
   595  		return true
   596  	}
   597  
   598  	_, exist := t.ValuesToComponentName[pf]
   599  	return exist
   600  }
   601  
   602  // renderComponentName renders a template of the form <path>{{.ComponentName}}<path> with
   603  // the supplied parameters.
   604  func renderComponentName(tmpl string, componentName string) (string, error) {
   605  	type temp struct {
   606  		ValueComponentName string
   607  	}
   608  	return util.RenderTemplate(tmpl, temp{componentName})
   609  }