Repository: kiali/kiali

Issue:
Error Unmarshalling the config_dump in Istio Ambient 1.26 (beta)
### What do you want to improve?

Issue with the Ztunnel config dump:

`[GetZtunnelDump] Error Unmarshalling the config_dump: json: cannot unmarshal string into Go struct field DNSResolverOptions.config.dnsResolverOpts.use_hosts_file of type bool`

### What is the current behavior?

The type changed from Istio 1.25 (bool) to string in istio 1.26

### What is the new behavior?

handle the condition and return other data

Hints from issue comments:
@josunect: Backport to 2.4: https://github.com/kiali/kiali/pull/8410

Solution patch:
diff --git a/kubernetes/cache/ztunnel_dump.go b/kubernetes/cache/ztunnel_dump.go
index d3c9cc5ff..9060f419c 100644
--- a/kubernetes/cache/ztunnel_dump.go
+++ b/kubernetes/cache/ztunnel_dump.go
@@ -2,9 +2,9 @@ package cache
 
 import (
 	"encoding/json"
-	"fmt"
 
 	"github.com/kiali/kiali/kubernetes"
+	"github.com/kiali/kiali/log"
 )
 
 func ztunnelDumpKey(cluster, namespace, pod string) string {
@@ -12,54 +12,56 @@ func ztunnelDumpKey(cluster, namespace, pod string) string {
 }
 
 type ZtunnelDumpCache interface {
-	SetZtunnelDump(ztunnelConfig map[string]*kubernetes.ZtunnelConfigDump)
-	GetAllZtunnelDump() map[string]*kubernetes.ZtunnelConfigDump
+	SetZtunnelDump(key string, config *kubernetes.ZtunnelConfigDump)
 	GetZtunnelDump(cluster, namespace, pod string) *kubernetes.ZtunnelConfigDump
 }
 
-func (c *kialiCacheImpl) GetAllZtunnelDump() map[string]*kubernetes.ZtunnelConfigDump {
-	return c.ztunnelConfigStore.Items()
-}
-
-func (c *kialiCacheImpl) SetZtunnelDump(ztunnelConfig map[string]*kubernetes.ZtunnelConfigDump) {
-	c.ztunnelConfigStore.Replace(ztunnelConfig)
+func (c *kialiCacheImpl) SetZtunnelDump(key string, config *kubernetes.ZtunnelConfigDump) {
+	c.ztunnelConfigStore.Set(key, config)
 }
 
 func (c *kialiCacheImpl) GetZtunnelDump(cluster, namespace, pod string) *kubernetes.ZtunnelConfigDump {
+
 	key := ztunnelDumpKey(cluster, namespace, pod)
+
 	config, found := c.ztunnelConfigStore.Get(key)
-	if !found {
-		if c.IsAmbientEnabled(cluster) {
+	if found {
+		return config
+	}
+
+	if !c.IsAmbientEnabled(cluster) {
+		return nil
+	}
 
-			ztunnelPods := c.GetZtunnelPods(cluster)
-			if len(ztunnelPods) > 0 {
-				client, err := c.GetKubeCache(cluster)
-				if err != nil {
-					klog.Errorf("[GetZtunnelDump] Error getting kubecache for cluster %s: %v", cluster, err)
-				} else {
-					for _, zPod := range ztunnelPods {
-						if zPod.Name == pod {
-							resp, err := client.Client().ForwardGetRequest(zPod.Namespace, zPod.Name, 15000, "/config_dump")
-							if err != nil {
-								klog.Errorf("[GetZtunnelDump] Error forwarding the /config_dump request: %v", err)
-								continue
-							}
-							configDump := &kubernetes.ZtunnelConfigDump{}
-							err = json.Unmarshal(resp, configDump)
-							if err != nil {
-								klog.Errorf("[GetZtunnelDump] Error Unmarshalling the config_dump: %v", err)
-							} else {
-								key := fmt.Sprintf("%s%s%s", client.Client().ClusterInfo().Name, zPod.Namespace, zPod.Name)
-								zTunnelConfigStore := c.GetAllZtunnelDump()
-								zTunnelConfigStore[key] = configDump
-								c.SetZtunnelDump(zTunnelConfigStore)
-								return configDump
-							}
-						}
-					}
-				}
+	ztunnelPods := c.GetZtunnelPods(cluster)
+	if len(ztunnelPods) <= 0 {
+		return nil
+	}
+
+	client, err := c.GetKubeCache(cluster)
+	if err != nil {
+		log.Errorf("[GetZtunnelDump] Error getting kubecache for cluster %s: %v", cluster, err)
+		return nil
+	}
+
+	for _, zPod := range ztunnelPods {
+		if zPod.Name == pod {
+			resp, err := client.Client().ForwardGetRequest(zPod.Namespace, zPod.Name, 15000, "/config_dump")
+			if err != nil {
+				log.Errorf("[GetZtunnelDump] Error forwarding the /config_dump request: %v", err)
+				return nil
+			}
+			var configDump *kubernetes.ZtunnelConfigDump
+			err = json.Unmarshal(resp, &configDump)
+			if err != nil {
+				log.Errorf("[GetZtunnelDump] Error Unmarshalling the config_dump: %v", err)
+				return nil
 			}
+
+			c.SetZtunnelDump(key, configDump)
+			return configDump
 		}
 	}
-	return config
+	log.Errorf("[GetZtunnelDump] Error Unmarshalling the config_dump: %v", err)
+	return nil
 }
diff --git a/kubernetes/ztunnel.go b/kubernetes/ztunnel.go
index ddae108c5..841caa749 100644
--- a/kubernetes/ztunnel.go
+++ b/kubernetes/ztunnel.go
@@ -1,5 +1,10 @@
 package kubernetes
 
+import (
+	"encoding/json"
+	"fmt"
+)
+
 type ZtunnelConfigDump struct {
 	Certificates []Certificate `json:"certificates"`
 	Config       Config        `json:"config"`
@@ -88,6 +93,24 @@ type NameServer struct {
 	TrustNegativeResponses bool    `json:"trust_negative_responses"`
 }
 
+// Workaround to handle different data types from Istio 1.25 -> Istio 1.26
+type BoolOrString string
+
+func (s *BoolOrString) UnmarshalJSON(data []byte) (err error) {
+	var boolVal bool
+	if err := json.Unmarshal(data, &boolVal); err == nil {
+		*s = BoolOrString(fmt.Sprintf("%t", boolVal))
+		return nil
+	}
+
+	var strVal string
+	if err := json.Unmarshal(data, &strVal); err == nil {
+		*s = BoolOrString(strVal)
+		return nil
+	}
+	return err
+}
+
 type DNSResolverOptions struct {
 	Attempts               int          `json:"attempts"`
 	AuthenticData          bool         `json:"authentic_data"`
@@ -108,7 +131,7 @@ type DNSResolverOptions struct {
 	ShuffleDnsServers      bool         `json:"shuffle_dns_servers"`
 	Timeout                TimeDuration `json:"timeout"`
 	TryTcpOnError          bool         `json:"try_tcp_on_error"`
-	UseHostsFile           bool         `json:"use_hosts_file"`
+	UseHostsFile           BoolOrString `json:"use_hosts_file"`
 	Validate               bool         `json:"validate"`
 }
 



Test patch:
diff --git a/kubernetes/cache/cache_test.go b/kubernetes/cache/cache_test.go
index 4045397ee..d50001de0 100644
--- a/kubernetes/cache/cache_test.go
+++ b/kubernetes/cache/cache_test.go
@@ -293,7 +293,6 @@ func TestZtunnelDump(t *testing.T) {
 	initData := cache.GetZtunnelDump("cluster-default", "istio-system", "ztunnel-7hml8")
 	require.Nil(initData)
 
-	zTunnel := make(map[string]*kubernetes.ZtunnelConfigDump)
 	zTunnelData, err := os.Open("../testdata/ztunnel-config.json")
 	require.NoError(err)
 	defer zTunnelData.Close()
@@ -302,8 +301,7 @@ func TestZtunnelDump(t *testing.T) {
 	errD := json.NewDecoder(zTunnelData).Decode(&configD)
 	require.NoError(errD)
 
-	zTunnel["cluster-defaultistio-systemztunnel-7hml8"] = configD
-	cache.SetZtunnelDump(zTunnel)
+	cache.SetZtunnelDump("cluster-defaultistio-systemztunnel-7hml8", configD)
 
 	cacheData := cache.GetZtunnelDump("cluster-default", "istio-system", "ztunnel-7hml8")
 	require.NotNil(cacheData)
@@ -314,6 +312,8 @@ func TestZtunnelDump(t *testing.T) {
 	require.Equal(cacheData.Services[36].Hostname, "waypoint.bookinfo.svc.cluster.local")
 	require.Equal(cacheData.Services[36].Name, "waypoint")
 	require.Equal(cacheData.Services[36].Namespace, "bookinfo")
+
+	require.Equal(cacheData.Config.DNSResolverOpts.UseHostsFile, kubernetes.BoolOrString("Auto"))
 }
 
 func TestGetNoZtunnelPods(t *testing.T) {
diff --git a/kubernetes/testdata/ztunnel-config.json b/kubernetes/testdata/ztunnel-config.json
index 1a1742af6..c09f24293 100644
--- a/kubernetes/testdata/ztunnel-config.json
+++ b/kubernetes/testdata/ztunnel-config.json
@@ -65,7 +65,7 @@
         "secs": 5
       },
       "try_tcp_on_error": false,
-      "use_hosts_file": true,
+      "use_hosts_file": "Auto",
       "validate": false
     },
     "fakeCa": false,