Repository: kserve/kserve

Issue:
Load kubeconfig from dict
/kind feature

**Allow loading of kubeconfig from dictionary in KServe python client**
The Kubernetes python client has a function to load kubeconfigs from dict which is quite handy in somecases compared to the kubecfg file. Currently the `KServeClient` supports only loading from kubeconfig i.e, `config.load_kube_config()`.

I have verified that the [KServeClient](https://github.com/kserve/kserve/blob/master/python/kserve/kserve/api/kserve_client.py#L40) in the master branch does not have it.




Discussion:
@yuzisun 
Let me know if it makes sense. I would be happy to work on adding this functionality.

Resolving patch:
diff --git a/python/kserve/kserve/api/kserve_client.py b/python/kserve/kserve/api/kserve_client.py
--- a/python/kserve/kserve/api/kserve_client.py
+++ b/python/kserve/kserve/api/kserve_client.py
@@ -27,21 +27,31 @@
 
 class KServeClient(object):
 
-    def __init__(self, config_file=None, context=None,  # pylint: disable=too-many-arguments
+    def __init__(self, config_file=None, config_dict=None, context=None,  # pylint: disable=too-many-arguments
                  client_configuration=None, persist_config=True):
         """
         KServe client constructor
         :param config_file: kubeconfig file, defaults to ~/.kube/config
+        :param config_dict: Takes the config file as a dict.
         :param context: kubernetes context
         :param client_configuration: kubernetes configuration object
         :param persist_config:
         """
-        if config_file or not utils.is_running_in_k8s():
-            config.load_kube_config(
-                config_file=config_file,
-                context=context,
-                client_configuration=client_configuration,
-                persist_config=persist_config)
+        if config_file or config_dict or not utils.is_running_in_k8s():
+            if config_dict:
+                config.load_kube_config_from_dict(
+                    config_dict=config_dict,
+                    context=context,
+                    client_configuration=None,
+                    persist_config=True
+                )
+            else:
+                config.load_kube_config(
+                    config_file=config_file,
+                    context=context,
+                    client_configuration=client_configuration,
+                    persist_config=persist_config
+                )
         else:
             config.load_incluster_config()
         self.core_api = client.CoreV1Api()


Test patch:
diff --git a/python/kserve/test/test_kubeconfig_dict.py b/python/kserve/test/test_kubeconfig_dict.py
new file mode 100644
--- /dev/null
+++ b/python/kserve/test/test_kubeconfig_dict.py
@@ -0,0 +1,112 @@
+# Copyright 2021 The KServe 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.
+
+from unittest.mock import patch
+
+from kubernetes import client
+
+from kserve import V1beta1PredictorSpec
+from kserve import V1beta1TFServingSpec
+from kserve import V1beta1InferenceServiceSpec
+from kserve import V1beta1InferenceService
+from kserve import KServeClient
+
+
+KUBECONFIG_DICT = {
+    "apiVersion": "v1",
+    "kind": "Config",
+    "metadata": {
+        "name": "example-cluster"
+    },
+    "clusters": [
+        {
+            "name": "example-cluster",
+            "cluster": {
+                "certificate-authority": "./ca.pem",
+                "server": "http://127.0.0.1:8080"
+            }
+        }
+    ],
+    "contexts": [
+        {
+            "name": "example-cluster-context",
+            "context": {
+                "cluster": "example-cluster",
+                "namespace": "example",
+                "user": "example-cluster-admin"
+            }
+        }
+    ],
+    "users": [
+        {
+            "name": "example-cluster-admin",
+            "user": {
+                "client-certificate": "./admin.pem",
+                "client-key": "./admin-key.pem"
+            }
+        }
+    ],
+    "current-context": "example-cluster-context"
+}
+
+kserve_client = KServeClient(config_dict=KUBECONFIG_DICT)
+
+mocked_unit_result = \
+    '''
+{
+    "api_version": "serving.kserve.io/v1beta1",
+    "kind": "InferenceService",
+    "metadata": {
+        "name": "flower-sample",
+        "namespace": "kubeflow"
+    },
+    "spec": {
+        "predictor": {
+            "tensorflow": {
+                "storage_uri": "gs://kfserving-samples/models/tensorflow/flowers"
+            }
+        }
+    }
+}
+ '''
+
+
+def generate_inferenceservice():
+    tf_spec = V1beta1TFServingSpec(
+        storage_uri='gs://kfserving-samples/models/tensorflow/flowers')
+    predictor_spec = V1beta1PredictorSpec(tensorflow=tf_spec)
+
+    isvc = V1beta1InferenceService(
+        api_version='serving.kserve.io/v1beta1',
+        kind='InferenceService',
+        metadata=client.V1ObjectMeta(name='flower-sample'),
+        spec=V1beta1InferenceServiceSpec(predictor=predictor_spec))
+    return isvc
+
+
+def test_inferenceservice_client_create():
+    '''Unit test for kserve create api'''
+    with patch('kserve.api.kserve_client.KServeClient.create',
+               return_value=mocked_unit_result):
+        isvc = generate_inferenceservice()
+        assert mocked_unit_result == kserve_client.create(
+            isvc, namespace='kubeflow')
+
+
+def test_inferenceservice_client_get():
+    '''Unit test for kserve get api'''
+    with patch('kserve.api.kserve_client.KServeClient.get',
+               return_value=mocked_unit_result):
+        assert mocked_unit_result == kserve_client.get(
+            'flower-sample', namespace='kubeflow')