| // Copyright Istio Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package translate |
| |
| import ( |
| "fmt" |
| "sort" |
| "strings" |
| ) |
| |
| import ( |
| "istio.io/api/operator/v1alpha1" |
| "sigs.k8s.io/yaml" |
| ) |
| |
| import ( |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/metrics" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/name" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/tpath" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/util" |
| "github.com/apache/dubbo-go-pixiu/operator/pkg/version" |
| oversion "github.com/apache/dubbo-go-pixiu/operator/version" |
| ) |
| |
| // ReverseTranslator is a set of mappings to translate between values.yaml and API paths, charts, k8s paths. |
| type ReverseTranslator struct { |
| Version version.MinorVersion |
| // APIMapping is Values.yaml path to API path mapping using longest prefix match. If the path is a non-leaf node, |
| // the output path is the matching portion of the path, plus any remaining output path. |
| APIMapping map[string]*Translation `yaml:"apiMapping,omitempty"` |
| // KubernetesPatternMapping defines mapping patterns from k8s resource paths to IstioOperator API paths. |
| KubernetesPatternMapping map[string]string `yaml:"kubernetesPatternMapping,omitempty"` |
| // KubernetesMapping defines actual k8s mappings generated from KubernetesPatternMapping before each translation. |
| KubernetesMapping map[string]*Translation `yaml:"kubernetesMapping,omitempty"` |
| // GatewayKubernetesMapping defines actual k8s mappings for gateway components generated from KubernetesPatternMapping before each translation. |
| GatewayKubernetesMapping gatewayKubernetesMapping `yaml:"GatewayKubernetesMapping,omitempty"` |
| // ValuesToComponentName defines mapping from value path to component name in API paths. |
| ValuesToComponentName map[string]name.ComponentName `yaml:"valuesToComponentName,omitempty"` |
| } |
| |
| type gatewayKubernetesMapping struct { |
| IngressMapping map[string]*Translation |
| EgressMapping map[string]*Translation |
| } |
| |
| var ( |
| // Component enablement mapping. Ex "{{.ValueComponent}}.enabled": Components.{{.ComponentName}}.enabled}", nil}, |
| componentEnablementPattern = "Components.{{.ComponentName}}.Enabled" |
| // specialComponentPath lists cases of component path of values.yaml we need to have special treatment. |
| specialComponentPath = map[string]bool{ |
| "gateways": true, |
| "gateways.istio-ingressgateway": true, |
| "gateways.istio-egressgateway": true, |
| } |
| |
| skipTranslate = map[name.ComponentName]bool{ |
| name.IstioBaseComponentName: true, |
| name.IstioOperatorComponentName: true, |
| name.IstioOperatorCustomResourceName: true, |
| name.CNIComponentName: true, |
| name.IstiodRemoteComponentName: true, |
| } |
| |
| gatewayPathMapping = map[string]name.ComponentName{ |
| "gateways.istio-ingressgateway": name.IngressComponentName, |
| "gateways.istio-egressgateway": name.EgressComponentName, |
| } |
| ) |
| |
| // initAPIMapping generate the reverse mapping from original translator apiMapping. |
| func (t *ReverseTranslator) initAPIAndComponentMapping() { |
| ts := NewTranslator() |
| t.APIMapping = make(map[string]*Translation) |
| t.KubernetesMapping = make(map[string]*Translation) |
| t.ValuesToComponentName = make(map[string]name.ComponentName) |
| for valKey, outVal := range ts.APIMapping { |
| t.APIMapping[outVal.OutPath] = &Translation{valKey, nil} |
| } |
| for cn, cm := range ts.ComponentMaps { |
| // we use dedicated translateGateway for gateway instead |
| if !skipTranslate[cn] && !cm.SkipReverseTranslate && !cn.IsGateway() { |
| t.ValuesToComponentName[cm.ToHelmValuesTreeRoot] = cn |
| } |
| } |
| } |
| |
| // initK8SMapping generates the k8s settings mapping for components that are enabled based on templates. |
| func (t *ReverseTranslator) initK8SMapping() error { |
| outputMapping := make(map[string]*Translation) |
| for valKey, componentName := range t.ValuesToComponentName { |
| for K8SValKey, outPathTmpl := range t.KubernetesPatternMapping { |
| newKey, err := renderComponentName(K8SValKey, valKey) |
| if err != nil { |
| return err |
| } |
| newVal, err := renderFeatureComponentPathTemplate(outPathTmpl, componentName) |
| if err != nil { |
| return err |
| } |
| outputMapping[newKey] = &Translation{newVal, nil} |
| } |
| } |
| |
| t.KubernetesMapping = outputMapping |
| |
| igwOutputMapping := make(map[string]*Translation) |
| egwOutputMapping := make(map[string]*Translation) |
| for valKey, componentName := range gatewayPathMapping { |
| mapping := igwOutputMapping |
| if componentName == name.EgressComponentName { |
| mapping = egwOutputMapping |
| } |
| for K8SValKey, outPathTmpl := range t.KubernetesPatternMapping { |
| newKey, err := renderComponentName(K8SValKey, valKey) |
| if err != nil { |
| return err |
| } |
| newP := util.PathFromString(outPathTmpl) |
| mapping[newKey] = &Translation{newP[len(newP)-2:].String(), nil} |
| } |
| } |
| t.GatewayKubernetesMapping = gatewayKubernetesMapping{IngressMapping: igwOutputMapping, EgressMapping: egwOutputMapping} |
| return nil |
| } |
| |
| // NewReverseTranslator creates a new ReverseTranslator for minorVersion and returns a ptr to it. |
| func NewReverseTranslator() *ReverseTranslator { |
| rt := &ReverseTranslator{ |
| KubernetesPatternMapping: map[string]string{ |
| "{{.ValueComponentName}}.env": "Components.{{.ComponentName}}.K8s.Env", |
| "{{.ValueComponentName}}.autoscaleEnabled": "Components.{{.ComponentName}}.K8s.HpaSpec", |
| "{{.ValueComponentName}}.imagePullPolicy": "Components.{{.ComponentName}}.K8s.ImagePullPolicy", |
| "{{.ValueComponentName}}.nodeSelector": "Components.{{.ComponentName}}.K8s.NodeSelector", |
| "{{.ValueComponentName}}.tolerations": "Components.{{.ComponentName}}.K8s.Tolerations", |
| "{{.ValueComponentName}}.podDisruptionBudget": "Components.{{.ComponentName}}.K8s.PodDisruptionBudget", |
| "{{.ValueComponentName}}.podAnnotations": "Components.{{.ComponentName}}.K8s.PodAnnotations", |
| "{{.ValueComponentName}}.priorityClassName": "Components.{{.ComponentName}}.K8s.PriorityClassName", |
| "{{.ValueComponentName}}.readinessProbe": "Components.{{.ComponentName}}.K8s.ReadinessProbe", |
| "{{.ValueComponentName}}.replicaCount": "Components.{{.ComponentName}}.K8s.ReplicaCount", |
| "{{.ValueComponentName}}.resources": "Components.{{.ComponentName}}.K8s.Resources", |
| "{{.ValueComponentName}}.rollingMaxSurge": "Components.{{.ComponentName}}.K8s.Strategy", |
| "{{.ValueComponentName}}.rollingMaxUnavailable": "Components.{{.ComponentName}}.K8s.Strategy", |
| "{{.ValueComponentName}}.serviceAnnotations": "Components.{{.ComponentName}}.K8s.ServiceAnnotations", |
| }, |
| } |
| rt.initAPIAndComponentMapping() |
| rt.Version = oversion.OperatorBinaryVersion.MinorVersion |
| return rt |
| } |
| |
| // TranslateFromValueToSpec translates from values.yaml value to IstioOperatorSpec. |
| func (t *ReverseTranslator) TranslateFromValueToSpec(values []byte, force bool) (controlPlaneSpec *v1alpha1.IstioOperatorSpec, err error) { |
| yamlTree := make(map[string]interface{}) |
| err = yaml.Unmarshal(values, &yamlTree) |
| if err != nil { |
| return nil, fmt.Errorf("error when unmarshalling into untype tree %v", err) |
| } |
| |
| outputTree := make(map[string]interface{}) |
| err = t.TranslateTree(yamlTree, outputTree, nil) |
| if err != nil { |
| return nil, err |
| } |
| outputVal, err := yaml.Marshal(outputTree) |
| if err != nil { |
| return nil, err |
| } |
| |
| cpSpec := &v1alpha1.IstioOperatorSpec{} |
| err = util.UnmarshalWithJSONPB(string(outputVal), cpSpec, force) |
| |
| if err != nil { |
| return nil, fmt.Errorf("error when unmarshalling into control plane spec %v, \nyaml:\n %s", err, outputVal) |
| } |
| |
| return cpSpec, nil |
| } |
| |
| // TranslateTree translates input value.yaml Tree to ControlPlaneSpec Tree. |
| func (t *ReverseTranslator) TranslateTree(valueTree map[string]interface{}, cpSpecTree map[string]interface{}, path util.Path) error { |
| // translate enablement and namespace |
| err := t.setEnablementFromValue(valueTree, cpSpecTree) |
| if err != nil { |
| return fmt.Errorf("error when translating enablement and namespace from value.yaml tree: %v", err) |
| } |
| // translate with api mapping |
| err = t.translateAPI(valueTree, cpSpecTree) |
| if err != nil { |
| return fmt.Errorf("error when translating value.yaml tree with global mapping: %v", err) |
| } |
| |
| // translate with k8s mapping |
| if err := t.TranslateK8S(valueTree, cpSpecTree); err != nil { |
| return err |
| } |
| |
| if err := t.translateGateway(valueTree, cpSpecTree); err != nil { |
| return fmt.Errorf("error when translating gateway with kubernetes mapping: %v", err.Error()) |
| } |
| // translate remaining untranslated paths into component values |
| err = t.translateRemainingPaths(valueTree, cpSpecTree, nil) |
| if err != nil { |
| return fmt.Errorf("error when translating remaining path: %v", err) |
| } |
| return nil |
| } |
| |
| // TranslateK8S is a helper function to translate k8s settings from values.yaml to IstioOperator, except for gateways. |
| func (t *ReverseTranslator) TranslateK8S(valueTree map[string]interface{}, cpSpecTree map[string]interface{}) error { |
| // translate with k8s mapping |
| if err := t.initK8SMapping(); err != nil { |
| return fmt.Errorf("error when initiating k8s mapping: %v", err) |
| } |
| if err := t.translateK8sTree(valueTree, cpSpecTree, t.KubernetesMapping); err != nil { |
| return fmt.Errorf("error when translating value.yaml tree with kubernetes mapping: %v", err) |
| } |
| return nil |
| } |
| |
| // setEnablementFromValue translates the enablement value of components in the values.yaml |
| // tree, based on feature/component inheritance relationship. |
| func (t *ReverseTranslator) setEnablementFromValue(valueSpec map[string]interface{}, root map[string]interface{}) error { |
| for _, cni := range t.ValuesToComponentName { |
| enabled, pathExist, err := IsComponentEnabledFromValue(cni, valueSpec) |
| if err != nil { |
| return err |
| } |
| if !pathExist { |
| continue |
| } |
| tmpl := componentEnablementPattern |
| ceVal, err := renderFeatureComponentPathTemplate(tmpl, cni) |
| if err != nil { |
| return err |
| } |
| outCP := util.ToYAMLPath(ceVal) |
| // set component enablement |
| if err := tpath.WriteNode(root, outCP, enabled); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // WarningForGatewayK8SSettings creates deprecated warning messages |
| // when user try to set kubernetes settings for gateways via values api. |
| func (t *ReverseTranslator) WarningForGatewayK8SSettings(valuesOverlay string) (string, error) { |
| gwOverlay, err := tpath.GetConfigSubtree(valuesOverlay, "gateways") |
| if err != nil { |
| return "", fmt.Errorf("error getting gateways overlay from valuesOverlayYaml %v", err) |
| } |
| if gwOverlay == "" { |
| return "", nil |
| } |
| var deprecatedFields []string |
| for inPath := range t.GatewayKubernetesMapping.IngressMapping { |
| _, found, err := tpath.GetPathContext(valuesOverlay, util.ToYAMLPath(inPath), false) |
| if err != nil { |
| scope.Debug(err.Error()) |
| continue |
| } |
| if found { |
| deprecatedFields = append(deprecatedFields, inPath) |
| } |
| } |
| for inPath := range t.GatewayKubernetesMapping.EgressMapping { |
| _, found, err := tpath.GetPathContext(valuesOverlay, util.ToYAMLPath(inPath), false) |
| if err != nil { |
| scope.Debug(err.Error()) |
| continue |
| } |
| if found { |
| deprecatedFields = append(deprecatedFields, inPath) |
| } |
| } |
| if len(deprecatedFields) == 0 { |
| return "", nil |
| } |
| warningMessage := fmt.Sprintf("using deprecated values api paths: %s.\n"+ |
| " please use k8s spec of gateway components instead\n", strings.Join(deprecatedFields, ",")) |
| return warningMessage, nil |
| } |
| |
| // translateGateway handles translation for gateways specific configuration |
| func (t *ReverseTranslator) translateGateway(valueSpec map[string]interface{}, root map[string]interface{}) error { |
| for inPath, outPath := range gatewayPathMapping { |
| enabled, pathExist, err := IsComponentEnabledFromValue(outPath, valueSpec) |
| if err != nil { |
| return err |
| } |
| if !pathExist && !enabled { |
| continue |
| } |
| gwSpecs := make([]map[string]interface{}, 1) |
| gwSpec := make(map[string]interface{}) |
| gwSpecs[0] = gwSpec |
| gwSpec["enabled"] = enabled |
| gwSpec["name"] = util.ToYAMLPath(inPath)[1] |
| outCP := util.ToYAMLPath("Components." + string(outPath)) |
| |
| if enabled { |
| mapping := t.GatewayKubernetesMapping.IngressMapping |
| if outPath == name.EgressComponentName { |
| mapping = t.GatewayKubernetesMapping.EgressMapping |
| } |
| err = t.translateK8sTree(valueSpec, gwSpec, mapping) |
| if err != nil { |
| return err |
| } |
| } |
| err = tpath.WriteNode(root, outCP, gwSpecs) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // TranslateK8SfromValueToIOP use reverse translation to convert k8s settings defined in values API to IOP API. |
| // this ensures that user overlays that set k8s through spec.values |
| // are not overridden by spec.components.X.k8s settings in the base profiles |
| func (t *ReverseTranslator) TranslateK8SfromValueToIOP(userOverlayYaml string) (string, error) { |
| valuesOverlay, err := tpath.GetConfigSubtree(userOverlayYaml, "spec.values") |
| if err != nil { |
| scope.Debugf("no spec.values section from userOverlayYaml %v", err) |
| return "", nil |
| } |
| valuesOverlayTree := make(map[string]interface{}) |
| err = yaml.Unmarshal([]byte(valuesOverlay), &valuesOverlayTree) |
| if err != nil { |
| return "", fmt.Errorf("error unmarshalling values overlay yaml into untype tree %v", err) |
| } |
| iopSpecTree := make(map[string]interface{}) |
| iopSpecOverlay, err := tpath.GetConfigSubtree(userOverlayYaml, "spec") |
| if err != nil { |
| return "", fmt.Errorf("error getting iop spec subtree from overlay yaml %v", err) |
| } |
| err = yaml.Unmarshal([]byte(iopSpecOverlay), &iopSpecTree) |
| if err != nil { |
| return "", fmt.Errorf("error unmarshalling spec overlay yaml into tree %v", err) |
| } |
| if err = t.TranslateK8S(valuesOverlayTree, iopSpecTree); err != nil { |
| return "", err |
| } |
| warning, err := t.WarningForGatewayK8SSettings(valuesOverlay) |
| if err != nil { |
| return "", fmt.Errorf("error handling values gateway k8s settings: %v", err) |
| } |
| if warning != "" { |
| return "", fmt.Errorf(warning) |
| } |
| iopSpecTreeYAML, err := yaml.Marshal(iopSpecTree) |
| if err != nil { |
| return "", fmt.Errorf("error marshaling reverse translated tree %v", err) |
| } |
| iopTreeYAML, err := tpath.AddSpecRoot(string(iopSpecTreeYAML)) |
| if err != nil { |
| return "", fmt.Errorf("error adding spec root: %v", err) |
| } |
| // overlay the reverse translated iopTreeYAML back to userOverlayYaml |
| finalYAML, err := util.OverlayYAML(userOverlayYaml, iopTreeYAML) |
| if err != nil { |
| return "", fmt.Errorf("failed to overlay the reverse translated iopTreeYAML: %v", err) |
| } |
| return finalYAML, err |
| } |
| |
| // translateStrategy translates Deployment Strategy related configurations from helm values.yaml tree. |
| func translateStrategy(fieldName string, outPath string, value interface{}, cpSpecTree map[string]interface{}) error { |
| fieldMap := map[string]string{ |
| "rollingMaxSurge": "maxSurge", |
| "rollingMaxUnavailable": "maxUnavailable", |
| } |
| newFieldName, ok := fieldMap[fieldName] |
| if !ok { |
| return fmt.Errorf("expected field name found in values.yaml: %s", fieldName) |
| } |
| outPath += ".rollingUpdate." + newFieldName |
| |
| scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", outPath) |
| if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath(outPath), value); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // translateHPASpec translates HPA related configurations from helm values.yaml tree. |
| // do not translate if autoscaleEnabled is explicitly set to false |
| func translateHPASpec(inPath string, outPath string, valueTree map[string]interface{}, cpSpecTree map[string]interface{}) error { |
| m, found, err := tpath.Find(valueTree, util.ToYAMLPath(inPath)) |
| if err != nil { |
| return err |
| } |
| if found { |
| asEnabled, ok := m.(bool) |
| if !ok { |
| return fmt.Errorf("expect autoscaleEnabled node type to be bool but got: %T", m) |
| } |
| if !asEnabled { |
| return nil |
| } |
| } |
| |
| newP := util.PathFromString(inPath) |
| // last path element is k8s setting name |
| newPS := newP[:len(newP)-1].String() |
| valMap := map[string]string{ |
| ".autoscaleMin": ".minReplicas", |
| ".autoscaleMax": ".maxReplicas", |
| } |
| for key, newVal := range valMap { |
| valPath := newPS + key |
| asVal, found, err := tpath.Find(valueTree, util.ToYAMLPath(valPath)) |
| if found && err == nil { |
| if err := setOutputAndClean(valPath, outPath+newVal, asVal, valueTree, cpSpecTree, true); err != nil { |
| return err |
| } |
| } |
| } |
| valPath := newPS + ".cpu.targetAverageUtilization" |
| asVal, found, err := tpath.Find(valueTree, util.ToYAMLPath(valPath)) |
| if found && err == nil { |
| rs := make([]interface{}, 1) |
| rsVal := ` |
| - type: Resource |
| resource: |
| name: cpu |
| target: |
| type: Utilization |
| averageUtilization: %f` |
| |
| rsString := fmt.Sprintf(rsVal, asVal) |
| if err = yaml.Unmarshal([]byte(rsString), &rs); err != nil { |
| return err |
| } |
| if err := setOutputAndClean(valPath, outPath+".metrics", rs, valueTree, cpSpecTree, true); err != nil { |
| return err |
| } |
| } |
| |
| // There is no direct source from value.yaml for scaleTargetRef value, we need to construct from component name |
| if found { |
| revision := "" |
| rev, ok := cpSpecTree["revision"] |
| if ok { |
| revision = rev.(string) |
| } |
| st := make(map[string]interface{}) |
| stVal := ` |
| apiVersion: apps/v1 |
| kind: Deployment |
| name: %s` |
| |
| // need to do special handling for gateways |
| if specialComponentPath[newPS] && len(newP) > 2 { |
| newPS = newP[1 : len(newP)-1].String() |
| } |
| // convert from values component name to correct deployment target |
| if newPS == "pilot" { |
| newPS = "istiod" |
| if revision != "" { |
| newPS = newPS + "-" + revision |
| } |
| } |
| stString := fmt.Sprintf(stVal, newPS) |
| if err := yaml.Unmarshal([]byte(stString), &st); err != nil { |
| return err |
| } |
| if err := setOutputAndClean(valPath, outPath+".scaleTargetRef", st, valueTree, cpSpecTree, false); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // setOutputAndClean is helper function to set value of iscp tree and clean the original value from value.yaml tree. |
| func setOutputAndClean(valPath, outPath string, outVal interface{}, valueTree, cpSpecTree map[string]interface{}, clean bool) error { |
| scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", outPath) |
| |
| if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath(outPath), outVal); err != nil { |
| return err |
| } |
| if !clean { |
| return nil |
| } |
| if _, err := tpath.Delete(valueTree, util.ToYAMLPath(valPath)); err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // translateEnv translates env value from helm values.yaml tree. |
| func translateEnv(outPath string, value interface{}, cpSpecTree map[string]interface{}) error { |
| envMap, ok := value.(map[string]interface{}) |
| if !ok { |
| return fmt.Errorf("expect env node type to be map[string]interface{} but got: %T", value) |
| } |
| if len(envMap) == 0 { |
| return nil |
| } |
| scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", outPath) |
| nc, found, _ := tpath.GetPathContext(cpSpecTree, util.ToYAMLPath(outPath), false) |
| var envValStr []byte |
| if nc != nil { |
| envValStr, _ = yaml.Marshal(nc.Node) |
| } |
| if !found || strings.TrimSpace(string(envValStr)) == "{}" { |
| scope.Debugf("path doesn't have value in k8s setting with output path %s, override with helm Value.yaml tree", outPath) |
| outEnv := make([]map[string]interface{}, len(envMap)) |
| keys := make([]string, 0, len(envMap)) |
| for k := range envMap { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| for i, k := range keys { |
| outEnv[i] = make(map[string]interface{}) |
| outEnv[i]["name"] = k |
| outEnv[i]["value"] = fmt.Sprintf("%v", envMap[k]) |
| } |
| if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath(outPath), outEnv); err != nil { |
| return err |
| } |
| } else { |
| scope.Debugf("path has value in k8s setting with output path %s, merge it with helm Value.yaml tree", outPath) |
| keys := make([]string, 0, len(envMap)) |
| for k := range envMap { |
| keys = append(keys, k) |
| } |
| sort.Strings(keys) |
| for _, k := range keys { |
| outEnv := make(map[string]interface{}) |
| outEnv["name"] = k |
| outEnv["value"] = fmt.Sprintf("%v", envMap[k]) |
| if err := tpath.MergeNode(cpSpecTree, util.ToYAMLPath(outPath), outEnv); err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // translateK8sTree is internal method for translating K8s configurations from value.yaml tree. |
| func (t *ReverseTranslator) translateK8sTree(valueTree map[string]interface{}, |
| cpSpecTree map[string]interface{}, mapping map[string]*Translation) error { |
| for inPath, v := range mapping { |
| scope.Debugf("Checking for k8s path %s in helm Value.yaml tree", inPath) |
| path := util.PathFromString(inPath) |
| k8sSettingName := "" |
| if len(path) != 0 { |
| k8sSettingName = path[len(path)-1] |
| } |
| if k8sSettingName == "autoscaleEnabled" { |
| if err := translateHPASpec(inPath, v.OutPath, valueTree, cpSpecTree); err != nil { |
| return fmt.Errorf("error in translating K8s HPA spec: %s", err) |
| } |
| metrics.LegacyPathTranslationTotal.Increment() |
| continue |
| } |
| m, found, err := tpath.Find(valueTree, util.ToYAMLPath(inPath)) |
| if err != nil { |
| return err |
| } |
| if !found { |
| scope.Debugf("path %s not found in helm Value.yaml tree, skip mapping.", inPath) |
| continue |
| } |
| |
| if mstr, ok := m.(string); ok && mstr == "" { |
| scope.Debugf("path %s is empty string, skip mapping.", inPath) |
| continue |
| } |
| // Zero int values are due to proto3 compiling to scalars rather than ptrs. Skip these because values of 0 are |
| // the default in destination fields and need not be set explicitly. |
| if mint, ok := util.ToIntValue(m); ok && mint == 0 { |
| scope.Debugf("path %s is int 0, skip mapping.", inPath) |
| continue |
| } |
| |
| switch k8sSettingName { |
| case "env": |
| err := translateEnv(v.OutPath, m, cpSpecTree) |
| if err != nil { |
| return fmt.Errorf("error in translating k8s Env: %s", err) |
| } |
| |
| case "rollingMaxSurge", "rollingMaxUnavailable": |
| err := translateStrategy(k8sSettingName, v.OutPath, m, cpSpecTree) |
| if err != nil { |
| return fmt.Errorf("error in translating k8s Strategy: %s", err) |
| } |
| |
| default: |
| if util.IsValueNilOrDefault(m) { |
| continue |
| } |
| output := util.ToYAMLPath(v.OutPath) |
| scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", output) |
| |
| if err := tpath.WriteNode(cpSpecTree, output, m); err != nil { |
| return err |
| } |
| } |
| metrics.LegacyPathTranslationTotal.Increment() |
| |
| if _, err := tpath.Delete(valueTree, util.ToYAMLPath(inPath)); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // translateRemainingPaths translates remaining paths that are not available in existing mappings. |
| func (t *ReverseTranslator) translateRemainingPaths(valueTree map[string]interface{}, |
| cpSpecTree map[string]interface{}, path util.Path) error { |
| for key, val := range valueTree { |
| newPath := append(path, key) |
| // value set to nil means no translation needed or being translated already. |
| if val == nil { |
| continue |
| } |
| switch node := val.(type) { |
| case map[string]interface{}: |
| err := t.translateRemainingPaths(node, cpSpecTree, newPath) |
| if err != nil { |
| return err |
| } |
| case []interface{}: |
| if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath("Values."+newPath.String()), node); err != nil { |
| return err |
| } |
| // remaining leaf need to be put into root.values |
| default: |
| if t.isEnablementPath(newPath) { |
| continue |
| } |
| if err := tpath.WriteNode(cpSpecTree, util.ToYAMLPath("Values."+newPath.String()), val); err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| // translateAPI is internal method for translating value.yaml tree based on API mapping. |
| func (t *ReverseTranslator) translateAPI(valueTree map[string]interface{}, |
| cpSpecTree map[string]interface{}) error { |
| for inPath, v := range t.APIMapping { |
| scope.Debugf("Checking for path %s in helm Value.yaml tree", inPath) |
| m, found, err := tpath.Find(valueTree, util.ToYAMLPath(inPath)) |
| if err != nil { |
| return err |
| } |
| if !found { |
| scope.Debugf("path %s not found in helm Value.yaml tree, skip mapping.", inPath) |
| continue |
| } |
| if mstr, ok := m.(string); ok && mstr == "" { |
| scope.Debugf("path %s is empty string, skip mapping.", inPath) |
| continue |
| } |
| // Zero int values are due to proto3 compiling to scalars rather than ptrs. Skip these because values of 0 are |
| // the default in destination fields and need not be set explicitly. |
| if mint, ok := util.ToIntValue(m); ok && mint == 0 { |
| scope.Debugf("path %s is int 0, skip mapping.", inPath) |
| continue |
| } |
| |
| path := util.ToYAMLPath(v.OutPath) |
| scope.Debugf("path has value in helm Value.yaml tree, mapping to output path %s", path) |
| metrics.LegacyPathTranslationTotal. |
| With(metrics.ResourceKindLabel.Value(inPath)).Increment() |
| |
| if err := tpath.WriteNode(cpSpecTree, path, m); err != nil { |
| return err |
| } |
| |
| if _, err := tpath.Delete(valueTree, util.ToYAMLPath(inPath)); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // isEnablementPath is helper function to check whether paths represent enablement of components in values.yaml |
| func (t *ReverseTranslator) isEnablementPath(path util.Path) bool { |
| if len(path) < 2 || path[len(path)-1] != "enabled" { |
| return false |
| } |
| |
| pf := path[:len(path)-1].String() |
| if specialComponentPath[pf] { |
| return true |
| } |
| |
| _, exist := t.ValuesToComponentName[pf] |
| return exist |
| } |
| |
| // renderComponentName renders a template of the form <path>{{.ComponentName}}<path> with |
| // the supplied parameters. |
| func renderComponentName(tmpl string, componentName string) (string, error) { |
| type temp struct { |
| ValueComponentName string |
| } |
| return util.RenderTemplate(tmpl, temp{componentName}) |
| } |