Repository: kubernetes-client/python
Pull request #2187 (2024-01-31 22:55:52)

Fix merging current-context in kube-configs
#### What type of PR is this?

It fixes merging kube configs when current-context is not defined in the first config.

/kind bug

#### What this PR does / why we need it:

To merge multiple kube configs in the same way as kubectl does.

#### Which issue(s) this PR fixes:

Fixes https://github.com/kubernetes-client/python/issues/2180

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?

```release-note
Fix bug related to merging context when multiple kubeconfigs are provided.
```

#### Additional documentation e.g., KEPs (Kubernetes Enhancement Proposals), usage docs, etc.:



Patch:
diff --git a/kubernetes/base/config/kube_config.py b/kubernetes/base/config/kube_config.py
index d8c63a826..96e5bd633 100644
--- a/kubernetes/base/config/kube_config.py
+++ b/kubernetes/base/config/kube_config.py
@@ -727,6 +727,10 @@ class KubeConfigMerger:
             self.config_merged = ConfigNode(path, config_merged, path)
         for item in ('clusters', 'contexts', 'users'):
             self._merge(item, config.get(item, []) or [], path)
+
+        if 'current-context' in config:
+            self.config_merged.value['current-context'] = config['current-context']
+
         self.config_files[path] = config
 
     def _merge(self, item, add_cfg, path):


Test patch:
diff --git a/kubernetes/base/config/kube_config_test.py b/kubernetes/base/config/kube_config_test.py
index b41549203..9b12b2d69 100644
--- a/kubernetes/base/config/kube_config_test.py
+++ b/kubernetes/base/config/kube_config_test.py
@@ -1653,7 +1653,7 @@ class TestKubernetesClientConfiguration(BaseTestCase):
 
 
 class TestKubeConfigMerger(BaseTestCase):
-    TEST_KUBE_CONFIG_PART1 = {
+    TEST_KUBE_CONFIG_SET1 = [{
         "current-context": "no_user",
         "contexts": [
             {
@@ -1672,9 +1672,7 @@ class TestKubeConfigMerger(BaseTestCase):
             },
         ],
         "users": []
-    }
-
-    TEST_KUBE_CONFIG_PART2 = {
+    }, {
         "current-context": "",
         "contexts": [
             {
@@ -1712,9 +1710,7 @@ class TestKubeConfigMerger(BaseTestCase):
                 }
             },
         ]
-    }
-
-    TEST_KUBE_CONFIG_PART3 = {
+    }, {
         "current-context": "no_user",
         "contexts": [
             {
@@ -1761,12 +1757,10 @@ class TestKubeConfigMerger(BaseTestCase):
                 }
             },
         ]
-    }
-    TEST_KUBE_CONFIG_PART4 = {
+    }, {
         "current-context": "no_user",
-    }
-    # Config with user having cmd-path
-    TEST_KUBE_CONFIG_PART5 = {
+    }, {
+        # Config with user having cmd-path
         "contexts": [
             {
                 "name": "contexttestcmdpath",
@@ -1795,8 +1789,7 @@ class TestKubeConfigMerger(BaseTestCase):
                 }
             }
         ]
-    }
-    TEST_KUBE_CONFIG_PART6 = {
+    }, {
         "current-context": "no_user",
         "contexts": [
             {
@@ -1815,22 +1808,49 @@ class TestKubeConfigMerger(BaseTestCase):
             },
         ],
         "users": None
-    }
+    }]
+    # 3 parts with different keys/data to merge
+    TEST_KUBE_CONFIG_SET2 = [{
+        "clusters": [
+            {
+                "name": "default",
+                "cluster": {
+                    "server": TEST_HOST
+                }
+            },
+        ],
+    }, {
+        "current-context": "simple_token",
+        "contexts": [
+            {
+                "name": "simple_token",
+                "context": {
+                    "cluster": "default",
+                    "user": "simple_token"
+                }
+            },
+        ],
+    }, {
+        "users": [
+            {
+                "name": "simple_token",
+                "user": {
+                    "token": TEST_DATA_BASE64,
+                    "username": TEST_USERNAME,
+                    "password": TEST_PASSWORD,
+                }
+            },
+        ]
+    }]
 
-    def _create_multi_config(self):
+    def _create_multi_config(self, parts):
         files = []
-        for part in (
-                self.TEST_KUBE_CONFIG_PART1,
-                self.TEST_KUBE_CONFIG_PART2,
-                self.TEST_KUBE_CONFIG_PART3,
-                self.TEST_KUBE_CONFIG_PART4,
-                self.TEST_KUBE_CONFIG_PART5,
-                self.TEST_KUBE_CONFIG_PART6):
+        for part in parts:
             files.append(self._create_temp_file(yaml.safe_dump(part)))
         return ENV_KUBECONFIG_PATH_SEPARATOR.join(files)
 
     def test_list_kube_config_contexts(self):
-        kubeconfigs = self._create_multi_config()
+        kubeconfigs = self._create_multi_config(self.TEST_KUBE_CONFIG_SET1)
         expected_contexts = [
             {'context': {'cluster': 'default'}, 'name': 'no_user'},
             {'context': {'cluster': 'ssl', 'user': 'ssl'}, 'name': 'ssl'},
@@ -1849,15 +1869,31 @@ class TestKubeConfigMerger(BaseTestCase):
         self.assertEqual(active_context, expected_contexts[0])
 
     def test_new_client_from_config(self):
-        kubeconfigs = self._create_multi_config()
+        kubeconfigs = self._create_multi_config(self.TEST_KUBE_CONFIG_SET1)
         client = new_client_from_config(
             config_file=kubeconfigs, context="simple_token")
         self.assertEqual(TEST_HOST, client.configuration.host)
         self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64,
                          client.configuration.api_key['authorization'])
 
+    def test_merge_with_context_in_different_file(self):
+        kubeconfigs = self._create_multi_config(self.TEST_KUBE_CONFIG_SET2)
+        client = new_client_from_config(config_file=kubeconfigs)
+
+        expected_contexts = [
+            {'context': {'cluster': 'default', 'user': 'simple_token'},
+             'name': 'simple_token'}
+        ]
+        contexts, active_context = list_kube_config_contexts(
+            config_file=kubeconfigs)
+        self.assertEqual(contexts, expected_contexts)
+        self.assertEqual(active_context, expected_contexts[0])
+        self.assertEqual(TEST_HOST, client.configuration.host)
+        self.assertEqual(BEARER_TOKEN_FORMAT % TEST_DATA_BASE64,
+                         client.configuration.api_key['authorization'])
+
     def test_save_changes(self):
-        kubeconfigs = self._create_multi_config()
+        kubeconfigs = self._create_multi_config(self.TEST_KUBE_CONFIG_SET1)
 
         # load configuration, update token, save config
         kconf = KubeConfigMerger(kubeconfigs)