25
"google.golang.org/protobuf/proto"
26
"google.golang.org/protobuf/types/known/structpb"
27
v1 "k8s.io/api/core/v1"
28
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
29
"k8s.io/apimachinery/pkg/util/strategicpatch"
30
"k8s.io/client-go/kubernetes/scheme"
33
"istio.io/api/operator/v1alpha1"
34
"istio.io/istio/operator/pkg/apis/istio"
35
iopv1alpha1 "istio.io/istio/operator/pkg/apis/istio/v1alpha1"
36
"istio.io/istio/operator/pkg/name"
37
"istio.io/istio/operator/pkg/object"
38
"istio.io/istio/operator/pkg/tpath"
39
"istio.io/istio/operator/pkg/util"
40
"istio.io/istio/operator/pkg/version"
41
oversion "istio.io/istio/operator/version"
42
"istio.io/istio/pkg/log"
43
"istio.io/istio/pkg/util/sets"
48
HelmValuesEnabledSubpath = "enabled"
50
HelmValuesNamespaceSubpath = "namespace"
52
HelmValuesHubSubpath = "hub"
54
HelmValuesTagSubpath = "tag"
57
var scope = log.RegisterScope("translator", "API translator")
60
type Translator struct {
62
Version version.MinorVersion
66
APIMapping map[string]*Translation `yaml:"apiMapping"`
68
KubernetesMapping map[string]*Translation `yaml:"kubernetesMapping"`
70
GlobalNamespaces map[name.ComponentName]string `yaml:"globalNamespaces"`
72
ComponentMaps map[name.ComponentName]*ComponentMaps `yaml:"componentMaps"`
76
type ComponentMaps struct {
86
ToHelmValuesTreeRoot string
88
SkipReverseTranslate bool
95
type TranslationFunc func(t *Translation, root map[string]any, valuesPath string, value any) error
98
type Translation struct {
100
OutPath string `yaml:"outPath"`
101
translationFunc TranslationFunc
105
func NewTranslator() *Translator {
107
Version: oversion.OperatorBinaryVersion.MinorVersion,
108
APIMapping: map[string]*Translation{
109
"hub": {OutPath: "global.hub"},
110
"tag": {OutPath: "global.tag"},
111
"revision": {OutPath: "revision"},
112
"meshConfig": {OutPath: "meshConfig"},
113
"compatibilityVersion": {OutPath: "compatibilityVersion"},
115
GlobalNamespaces: map[name.ComponentName]string{
116
name.PilotComponentName: "istioNamespace",
118
ComponentMaps: map[name.ComponentName]*ComponentMaps{
119
name.IstioBaseComponentName: {
121
ToHelmValuesTreeRoot: "global",
122
SkipReverseTranslate: true,
124
name.PilotComponentName: {
125
ResourceType: "Deployment",
126
ResourceName: "istiod",
127
ContainerName: "discovery",
128
HelmSubdir: "istio-control/istio-discovery",
129
ToHelmValuesTreeRoot: "pilot",
131
name.IngressComponentName: {
132
ResourceType: "Deployment",
133
ResourceName: "istio-ingressgateway",
134
ContainerName: "istio-proxy",
135
HelmSubdir: "gateways/istio-ingress",
136
ToHelmValuesTreeRoot: "gateways.istio-ingressgateway",
138
name.EgressComponentName: {
139
ResourceType: "Deployment",
140
ResourceName: "istio-egressgateway",
141
ContainerName: "istio-proxy",
142
HelmSubdir: "gateways/istio-egress",
143
ToHelmValuesTreeRoot: "gateways.istio-egressgateway",
145
name.CNIComponentName: {
146
ResourceType: "DaemonSet",
147
ResourceName: "istio-cni-node",
148
ContainerName: "install-cni",
149
HelmSubdir: "istio-cni",
150
ToHelmValuesTreeRoot: "cni",
152
name.IstiodRemoteComponentName: {
153
HelmSubdir: "istiod-remote",
154
ToHelmValuesTreeRoot: "global",
155
SkipReverseTranslate: true,
157
name.ZtunnelComponentName: {
158
ResourceType: "DaemonSet",
159
ResourceName: "ztunnel",
160
HelmSubdir: "ztunnel",
161
ToHelmValuesTreeRoot: "ztunnel",
162
ContainerName: "istio-proxy",
167
KubernetesMapping: map[string]*Translation{
168
"Components.{{.ComponentName}}.K8S.Affinity": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.affinity"},
169
"Components.{{.ComponentName}}.K8S.Env": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.containers.[name:{{.ContainerName}}].env"},
170
"Components.{{.ComponentName}}.K8S.HpaSpec": {OutPath: "[HorizontalPodAutoscaler:{{.ResourceName}}].spec"},
171
"Components.{{.ComponentName}}.K8S.ImagePullPolicy": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.containers.[name:{{.ContainerName}}].imagePullPolicy"},
172
"Components.{{.ComponentName}}.K8S.NodeSelector": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.nodeSelector"},
173
"Components.{{.ComponentName}}.K8S.PodDisruptionBudget": {OutPath: "[PodDisruptionBudget:{{.ResourceName}}].spec"},
174
"Components.{{.ComponentName}}.K8S.PodAnnotations": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.metadata.annotations"},
175
"Components.{{.ComponentName}}.K8S.PriorityClassName": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.priorityClassName."},
176
"Components.{{.ComponentName}}.K8S.ReadinessProbe": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.containers.[name:{{.ContainerName}}].readinessProbe"},
177
"Components.{{.ComponentName}}.K8S.ReplicaCount": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.replicas"},
178
"Components.{{.ComponentName}}.K8S.Resources": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.containers.[name:{{.ContainerName}}].resources"},
179
"Components.{{.ComponentName}}.K8S.Strategy": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.strategy"},
180
"Components.{{.ComponentName}}.K8S.Tolerations": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.tolerations"},
181
"Components.{{.ComponentName}}.K8S.ServiceAnnotations": {OutPath: "[Service:{{.ResourceName}}].metadata.annotations"},
182
"Components.{{.ComponentName}}.K8S.Service": {OutPath: "[Service:{{.ResourceName}}].spec"},
183
"Components.{{.ComponentName}}.K8S.SecurityContext": {OutPath: "[{{.ResourceType}}:{{.ResourceName}}].spec.template.spec.securityContext"},
190
func (t *Translator) OverlayK8sSettings(yml string, iop *v1alpha1.IstioOperatorSpec, componentName name.ComponentName,
191
resourceName string, index int) (string, error,
195
var om map[string]*object.K8sObject
196
var objects object.K8sObjects
198
for inPath, v := range t.KubernetesMapping {
199
inPath, err := renderFeatureComponentPathTemplate(inPath, componentName)
203
renderedInPath := strings.Replace(inPath, "gressGateways.", "gressGateways."+fmt.Sprint(index)+".", 1)
204
scope.Debugf("Checking for path %s in IstioOperatorSpec", renderedInPath)
206
m, found, err := tpath.GetFromStructPath(iop, renderedInPath)
211
scope.Debugf("path %s not found in IstioOperatorSpec, skip mapping.", renderedInPath)
214
if mstr, ok := m.(string); ok && mstr == "" {
215
scope.Debugf("path %s is empty string, skip mapping.", renderedInPath)
220
if mint, ok := util.ToIntValue(m); ok && mint == 0 {
221
scope.Debugf("path %s is int 0, skip mapping.", renderedInPath)
224
if componentName == name.IstioBaseComponentName {
225
return "", fmt.Errorf("base component can only have k8s.overlays, not other K8s settings")
227
inPathParts := strings.Split(inPath, ".")
228
outPath, err := t.renderResourceComponentPathTemplate(v.OutPath, componentName, resourceName, iop.Revision)
232
scope.Debugf("path has value in IstioOperatorSpec, mapping to output path %s", outPath)
233
path := util.PathFromString(outPath)
236
if !util.IsKVPathElement(pe) {
237
return "", fmt.Errorf("path %s has an unexpected first element %s in OverlayK8sSettings", path, pe)
242
objects, err = object.ParseK8sObjectsFromYAMLManifest(yml)
246
if scope.DebugEnabled() {
247
scope.Debugf("Manifest contains the following objects:")
248
for _, o := range objects {
249
scope.Debugf("%s", o.HashNameKind())
252
om = objects.ToNameKindMap()
256
pe, _ = util.RemoveBrackets(pe)
260
scope.Infof("resource Kind:name %s doesn't exist in the output manifest, skip overlay.", pe)
271
if inPathParts[len(inPathParts)-1] == "ReplicaCount" {
272
if skipReplicaCountWithAutoscaleEnabled(iop, componentName) {
278
mergedObj, err := MergeK8sObject(oo, m, path[1:])
285
if inPathParts[len(inPathParts)-1] == "Service" {
286
if msvc, ok := m.(*v1alpha1.ServiceSpec); ok {
287
mergedObj, err = t.fixMergedObjectWithCustomServicePortOverlay(oo, msvc, mergedObj)
295
*(om[pe]) = *mergedObj
299
return objects.YAMLManifest()
304
var componentToAutoScaleEnabledPath = map[name.ComponentName]string{
305
name.PilotComponentName: "pilot.autoscaleEnabled",
306
name.IngressComponentName: "gateways.istio-ingressgateway.autoscaleEnabled",
307
name.EgressComponentName: "gateways.istio-egressgateway.autoscaleEnabled",
310
func skipReplicaCountWithAutoscaleEnabled(iop *v1alpha1.IstioOperatorSpec, componentName name.ComponentName) bool {
311
values := iop.GetValues().AsMap()
312
path, ok := componentToAutoScaleEnabledPath[componentName]
317
enabledVal, found, err := tpath.GetFromStructPath(values, path)
318
if err != nil || !found {
322
enabled, ok := enabledVal.(bool)
326
func (t *Translator) fixMergedObjectWithCustomServicePortOverlay(oo *object.K8sObject,
327
msvc *v1alpha1.ServiceSpec, mergedObj *object.K8sObject,
328
) (*object.K8sObject, error) {
329
var basePorts []*v1.ServicePort
330
bps, _, err := unstructured.NestedSlice(oo.Unstructured(), "spec", "ports")
334
bby, err := json.Marshal(bps)
338
if err = json.Unmarshal(bby, &basePorts); err != nil {
341
overlayPorts := make([]*v1.ServicePort, 0, len(msvc.GetPorts()))
342
for _, p := range msvc.GetPorts() {
344
switch strings.ToLower(p.GetProtocol()) {
350
port := &v1.ServicePort{
354
NodePort: p.GetNodePort(),
356
if p.GetAppProtocol() != "" {
358
port.AppProtocol = &ap
360
if p.TargetPort != nil {
361
port.TargetPort = p.TargetPort.ToKubernetes()
363
overlayPorts = append(overlayPorts, port)
365
mergedPorts := strategicMergePorts(basePorts, overlayPorts)
366
mpby, err := json.Marshal(mergedPorts)
370
var mergedPortSlice []any
371
if err = json.Unmarshal(mpby, &mergedPortSlice); err != nil {
374
if err = unstructured.SetNestedSlice(mergedObj.Unstructured(), mergedPortSlice, "spec", "ports"); err != nil {
378
mjsonby, err := json.Marshal(mergedObj.Unstructured())
382
if mergedObj, err = object.ParseJSONToK8sObject(mjsonby); err != nil {
385
return mergedObj, nil
388
type portWithProtocol struct {
393
func portIndexOf(element portWithProtocol, data []portWithProtocol) int {
394
for k, v := range data {
408
func strategicMergePorts(base, overlay []*v1.ServicePort) []*v1.ServicePort {
419
portPriority := make([]portWithProtocol, 0, len(base)+len(overlay))
420
for _, p := range base {
421
if p.Protocol == "" {
422
p.Protocol = v1.ProtocolTCP
424
portPriority = append(portPriority, portWithProtocol{port: p.Port, protocol: p.Protocol})
426
for _, p := range overlay {
427
if p.Protocol == "" {
428
p.Protocol = v1.ProtocolTCP
430
portPriority = append(portPriority, portWithProtocol{port: p.Port, protocol: p.Protocol})
432
sortFn := func(ps []*v1.ServicePort) func(int, int) bool {
433
return func(i, j int) bool {
434
pi := portIndexOf(portWithProtocol{port: ps[i].Port, protocol: ps[i].Protocol}, portPriority)
435
pj := portIndexOf(portWithProtocol{port: ps[j].Port, protocol: ps[j].Protocol}, portPriority)
440
sort.Slice(base, sortFn(base))
444
sort.Slice(overlay, sortFn(overlay))
449
merged := make(map[portWithProtocol]*v1.ServicePort)
450
for _, p := range base {
451
key := portWithProtocol{port: p.Port, protocol: p.Protocol}
454
for _, p := range overlay {
455
key := portWithProtocol{port: p.Port, protocol: p.Protocol}
458
res := make([]*v1.ServicePort, 0, len(merged))
459
for _, pv := range merged {
460
res = append(res, pv)
462
sort.Slice(res, sortFn(res))
467
func (t *Translator) ProtoToValues(ii *v1alpha1.IstioOperatorSpec) (string, error) {
468
root, err := t.ProtoToHelmValues2(ii)
474
if err := t.setComponentProperties(root, ii); err != nil {
483
y, err := yaml.Marshal(root)
488
return string(y), nil
492
var topLevelFields = sets.New(
495
"compatibilityVersion",
500
func (t *Translator) TranslateHelmValues(iop *v1alpha1.IstioOperatorSpec, componentsSpec any, componentName name.ComponentName) (string, error) {
501
apiVals := make(map[string]any)
504
apiValsStr, err := t.ProtoToValues(iop)
508
err = yaml.Unmarshal([]byte(apiValsStr), &apiVals)
513
scope.Debugf("Values translated from IstioOperator API:\n%s", apiValsStr)
516
globalVals := iop.GetValues().AsMap()
517
globalUnvalidatedVals := iop.GetUnvalidatedValues().AsMap()
519
if scope.DebugEnabled() {
520
scope.Debugf("Values from IstioOperatorSpec.Values:\n%s", util.ToYAML(globalVals))
521
scope.Debugf("Values from IstioOperatorSpec.UnvalidatedValues:\n%s", util.ToYAML(globalUnvalidatedVals))
524
mergedVals, err := util.OverlayTrees(apiVals, globalVals)
528
mergedVals, err = util.OverlayTrees(mergedVals, globalUnvalidatedVals)
532
c, f := t.ComponentMaps[componentName]
533
if f && c.FlattenValues {
534
globals, ok := mergedVals["global"].(map[string]any)
536
return "", fmt.Errorf("global value isn't a map")
538
components, ok := mergedVals[c.ToHelmValuesTreeRoot].(map[string]any)
540
return "", fmt.Errorf("component value isn't a map")
542
finalVals := map[string]any{}
544
for k, v := range apiVals {
545
_, isMap := v.(map[string]any)
550
for k := range topLevelFields {
551
if v, f := mergedVals[k]; f {
555
for k, v := range globals {
558
for k, v := range components {
561
mergedVals = finalVals
564
mergedYAML, err := yaml.Marshal(mergedVals)
569
mergedYAML, err = applyGatewayTranslations(mergedYAML, componentName, componentsSpec)
574
return string(mergedYAML), err
579
func applyGatewayTranslations(iop []byte, componentName name.ComponentName, componentSpec any) ([]byte, error) {
580
if !componentName.IsGateway() {
583
iopt := make(map[string]any)
584
if err := yaml.Unmarshal(iop, &iopt); err != nil {
587
gwSpec := componentSpec.(*v1alpha1.GatewaySpec)
589
switch componentName {
590
case name.IngressComponentName:
591
setYAMLNodeByMapPath(iopt, util.PathFromString("gateways.istio-ingressgateway.name"), gwSpec.Name)
592
if len(gwSpec.Label) != 0 {
593
setYAMLNodeByMapPath(iopt, util.PathFromString("gateways.istio-ingressgateway.labels"), gwSpec.Label)
595
if k8s != nil && k8s.Service != nil && k8s.Service.Ports != nil {
596
setYAMLNodeByMapPath(iopt, util.PathFromString("gateways.istio-ingressgateway.ports"), k8s.Service.Ports)
598
case name.EgressComponentName:
599
setYAMLNodeByMapPath(iopt, util.PathFromString("gateways.istio-egressgateway.name"), gwSpec.Name)
600
if len(gwSpec.Label) != 0 {
601
setYAMLNodeByMapPath(iopt, util.PathFromString("gateways.istio-egressgateway.labels"), gwSpec.Label)
603
if k8s != nil && k8s.Service != nil && k8s.Service.Ports != nil {
604
setYAMLNodeByMapPath(iopt, util.PathFromString("gateways.istio-egressgateway.ports"), k8s.Service.Ports)
607
return yaml.Marshal(iopt)
612
func setYAMLNodeByMapPath(treeNode any, path util.Path, val any) {
613
if len(path) == 0 || treeNode == nil {
617
switch nt := treeNode.(type) {
626
setYAMLNodeByMapPath(nt[pe], path[1:], val)
635
setYAMLNodeByMapPath(nt[pe], path[1:], val)
642
func (t *Translator) ComponentMap(cns string) *ComponentMaps {
643
cn := name.TitleCase(name.ComponentName(cns))
644
return t.ComponentMaps[cn]
647
func (t *Translator) ProtoToHelmValues2(ii *v1alpha1.IstioOperatorSpec) (map[string]any, error) {
648
by, err := json.Marshal(ii)
652
res := map[string]any{}
653
err = json.Unmarshal(by, &res)
657
r2 := map[string]any{}
658
errs := t.ProtoToHelmValues(res, r2, nil)
659
return r2, errs.ToError()
668
func (t *Translator) ProtoToHelmValues(node any, root map[string]any, path util.Path) (errs util.Errors) {
669
scope.Debugf("ProtoToHelmValues with path %s, %v (%T)", path, node, node)
670
if util.IsValueNil(node) {
674
vv := reflect.ValueOf(node)
675
vt := reflect.TypeOf(node)
678
if !util.IsNilOrInvalidValue(vv.Elem()) {
679
errs = util.AppendErrs(errs, t.ProtoToHelmValues(vv.Elem().Interface(), root, path))
682
scope.Debug("Struct")
683
for i := 0; i < vv.NumField(); i++ {
684
fieldName := vv.Type().Field(i).Name
685
fieldValue := vv.Field(i)
686
scope.Debugf("Checking field %s", fieldName)
687
if a, ok := vv.Type().Field(i).Tag.Lookup("json"); ok && a == "-" {
690
if !fieldValue.CanInterface() {
693
errs = util.AppendErrs(errs, t.ProtoToHelmValues(fieldValue.Interface(), root, append(path, fieldName)))
697
for _, key := range vv.MapKeys() {
698
nnp := append(path, key.String())
699
errs = util.AppendErrs(errs, t.insertLeaf(root, nnp, vv.MapIndex(key)))
703
for i := 0; i < vv.Len(); i++ {
704
errs = util.AppendErrs(errs, t.ProtoToHelmValues(vv.Index(i).Interface(), root, path))
708
scope.Debugf("field has kind %s", vt.Kind())
709
if vv.CanInterface() {
710
errs = util.AppendErrs(errs, t.insertLeaf(root, path, vv))
719
func (t *Translator) setComponentProperties(root map[string]any, iop *v1alpha1.IstioOperatorSpec) error {
721
for k := range t.ComponentMaps {
722
if k != name.IngressComponentName && k != name.EgressComponentName {
723
keys = append(keys, string(k))
728
for i := l - 1; i >= 0; i-- {
729
cn := name.ComponentName(keys[i])
730
c := t.ComponentMaps[cn]
731
e, err := t.IsComponentEnabled(cn, iop)
736
enablementPath := c.ToHelmValuesTreeRoot
738
if cn == name.CNIComponentName {
739
enablementPath = "istio_cni"
741
if err := tpath.WriteNode(root, util.PathFromString(enablementPath+"."+HelmValuesEnabledSubpath), e); err != nil {
745
ns, err := name.Namespace(cn, iop)
749
if err := tpath.WriteNode(root, util.PathFromString(c.ToHelmValuesTreeRoot+"."+HelmValuesNamespaceSubpath), ns); err != nil {
753
hub, found, _ := tpath.GetFromStructPath(iop, "Components."+string(cn)+".Hub")
756
hubStr, ok := hub.(string)
757
if found && !(ok && hubStr == "") {
758
if err := tpath.WriteNode(root, util.PathFromString(c.ToHelmValuesTreeRoot+"."+HelmValuesHubSubpath), hub); err != nil {
763
tag, found, _ := tpath.GetFromStructPath(iop, "Components."+string(cn)+".Tag")
764
tagv, ok := tag.(*structpb.Value)
765
if found && !(ok && util.ValueString(tagv) == "") {
766
if err := tpath.WriteNode(root, util.PathFromString(c.ToHelmValuesTreeRoot+"."+HelmValuesTagSubpath), util.ValueString(tagv)); err != nil {
772
for cn, gns := range t.GlobalNamespaces {
773
ns, err := name.Namespace(cn, iop)
777
if err := tpath.WriteNode(root, util.PathFromString("global."+gns), ns); err != nil {
787
func (t *Translator) IsComponentEnabled(cn name.ComponentName, iop *v1alpha1.IstioOperatorSpec) (bool, error) {
788
if t.ComponentMaps[cn] == nil {
791
return IsComponentEnabledInSpec(cn, iop)
795
func (t *Translator) insertLeaf(root map[string]any, path util.Path, value reflect.Value) (errs util.Errors) {
797
valuesPath, m := getValuesPathMapping(t.APIMapping, path)
799
if value.Kind() == reflect.Ptr {
800
v = value.Elem().Interface()
802
v = value.Interface()
807
case m.translationFunc == nil:
809
errs = util.AppendErr(errs, defaultTranslationFunc(m, root, valuesPath, v))
812
errs = util.AppendErr(errs, m.translationFunc(m, root, valuesPath, v))
820
func getValuesPathMapping(mappings map[string]*Translation, path util.Path) (string, *Translation) {
823
for ; len(p) > 0; p = p[0 : len(p)-1] {
824
m = mappings[p.String()]
837
out := m.OutPath + "." + path[len(p):].String()
838
scope.Debugf("translating %s to %s", path, out)
844
func renderFeatureComponentPathTemplate(tmpl string, componentName name.ComponentName) (string, error) {
846
ComponentName name.ComponentName
849
ComponentName: componentName,
851
return util.RenderTemplate(tmpl, ts)
856
func (t *Translator) renderResourceComponentPathTemplate(tmpl string, componentName name.ComponentName,
857
resourceName, revision string,
859
cn := string(componentName)
860
cmp := t.ComponentMap(cn)
862
return "", fmt.Errorf("component: %s does not exist in the componentMap", cn)
864
if resourceName == "" {
865
resourceName = cmp.ResourceName
868
if revision != "" && resourceName == "istiod" {
869
resourceName += "-" + revision
876
ResourceType: cmp.ResourceType,
877
ResourceName: resourceName,
878
ContainerName: cmp.ContainerName,
880
return util.RenderTemplate(tmpl, ts)
884
func defaultTranslationFunc(m *Translation, root map[string]any, valuesPath string, value any) error {
887
if util.IsEmptyString(value) {
888
scope.Debugf("Skip empty string value for path %s", m.OutPath)
891
if valuesPath == "" {
892
scope.Debugf("Not mapping to values, resources path is %s", m.OutPath)
896
for _, p := range util.PathFromString(valuesPath) {
897
path = append(path, firstCharToLower(p))
900
return tpath.WriteNode(root, path, value)
903
func firstCharToLower(s string) string {
904
return strings.ToLower(s[0:1]) + s[1:]
910
func MergeK8sObject(base *object.K8sObject, overlayNode any, path util.Path) (*object.K8sObject, error) {
911
overlay, err := createPatchObjectFromPath(overlayNode, path)
915
overlayYAML, err := yaml.Marshal(overlay)
919
overlayJSON, err := yaml.YAMLToJSON(overlayYAML)
921
return nil, fmt.Errorf("yamlToJSON error in overlayYAML: %s\n%s", err, overlayYAML)
923
baseJSON, err := base.JSON()
930
versionedObject, err := scheme.Scheme.New(base.GroupVersionKind())
935
newBytes, err := strategicpatch.StrategicMergePatch(baseJSON, overlayJSON, versionedObject)
937
return nil, fmt.Errorf("get error: %s to merge patch:\n%s for base:\n%s", err, overlayJSON, baseJSON)
940
newObj, err := object.ParseJSONToK8sObject(newBytes)
945
return newObj.ResolveK8sConflict(), nil
965
func createPatchObjectFromPath(node any, path util.Path) (map[string]any, error) {
967
return nil, fmt.Errorf("empty path %s", path)
969
if util.IsKVPathElement(path[0]) {
970
return nil, fmt.Errorf("path %s has an unexpected first element %s", path, path[0])
973
if util.IsKVPathElement(path[length-1]) {
974
return nil, fmt.Errorf("path %s has an unexpected last element %s", path, path[length-1])
977
patchObj := make(map[string]any)
978
var currentNode, nextNode any
980
for i, pe := range path {
981
currentNode = nextNode
984
currentNode, ok := currentNode.(map[string]any)
986
return nil, fmt.Errorf("path %s has an unexpected non KV element %s", path, pe)
988
currentNode[pe] = node
992
if util.IsKVPathElement(pe) {
993
currentNode, ok := currentNode.([]any)
995
return nil, fmt.Errorf("path %s has an unexpected KV element %s", path, pe)
997
k, v, err := util.PathKV(pe)
1001
if k == "" || v == "" {
1002
return nil, fmt.Errorf("path %s has an invalid KV element %s", path, pe)
1004
currentNode[0] = map[string]any{k: v}
1005
nextNode = currentNode[0]
1009
currentNode, ok := currentNode.(map[string]any)
1011
return nil, fmt.Errorf("path %s has an unexpected non KV element %s", path, pe)
1014
if util.IsKVPathElement(path[i+1]) {
1015
currentNode[pe] = make([]any, 1)
1017
currentNode[pe] = make(map[string]any)
1019
nextNode = currentNode[pe]
1021
return patchObj, nil
1025
func IOPStoIOP(iops proto.Message, name, namespace string) (*iopv1alpha1.IstioOperator, error) {
1026
iopStr, err := IOPStoIOPstr(iops, name, namespace)
1030
iop, err := istio.UnmarshalIstioOperator(iopStr, false)
1038
func IOPStoIOPstr(iops proto.Message, name, namespace string) (string, error) {
1039
iopsStr, err := util.MarshalWithJSONPB(iops)
1043
spec, err := tpath.AddSpecRoot(iopsStr)
1049
apiVersion: install.istio.io/v1alpha1
1052
namespace: {{ .Namespace }}
1063
Namespace: namespace,
1066
return util.RenderTemplate(tmpl, ts)