Skip to content

Commit f400ee8

Browse files
authored
Merge pull request #34 from vogxn/26-handle-client-python-errors
handle client python errors
2 parents b9fce5c + e65990d commit f400ee8

File tree

7 files changed

+197
-112
lines changed

7 files changed

+197
-112
lines changed

kubeshell/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
__version__ = '0.0.20'
2+
from . import logger

kubeshell/client.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from urllib3.exceptions import NewConnectionError, ConnectTimeoutError, MaxRetryError
2+
from kubernetes import client, config
3+
from kubernetes.client.api_client import ApiException
4+
5+
import logging
6+
import urllib3
7+
8+
# disable warnings on stdout/stderr from urllib3 connection errors
9+
ulogger = logging.getLogger("urllib3")
10+
ulogger.setLevel("ERROR")
11+
12+
13+
class KubernetesClient(object):
14+
15+
def __init__(self):
16+
self.logger = logging.getLogger(__name__)
17+
try:
18+
config.load_kube_config()
19+
except:
20+
self.logger.warning("unable to load kube-config")
21+
22+
self.v1 = client.CoreV1Api()
23+
self.v1Beta1 = client.AppsV1beta1Api()
24+
self.extensionsV1Beta1 = client.ExtensionsV1beta1Api()
25+
self.autoscalingV1Api = client.AutoscalingV1Api()
26+
self.rbacApi = client.RbacAuthorizationV1beta1Api()
27+
self.batchV1Api = client.BatchV1Api()
28+
self.batchV2Api = client.BatchV2alpha1Api()
29+
30+
31+
def get_resource(self, resource, namespace="all"):
32+
ret, resources = None, list()
33+
try:
34+
ret, namespaced_resource = self._call_api_client(resource)
35+
except ApiException as ae:
36+
self.logger.warning("resource autocomplete disabled, encountered "
37+
"ApiException", exc_info=1)
38+
except (NewConnectionError, MaxRetryError, ConnectTimeoutError):
39+
self.logger.warning("unable to connect to k8 cluster", exc_info=1)
40+
if ret:
41+
for i in ret.items:
42+
if namespace == "all" or not namespaced_resource:
43+
resources.append((i.metadata.name, i.metadata.namespace))
44+
elif namespace == i.metadata.namespace:
45+
resources.append((i.metadata.name, i.metadata.namespace))
46+
return resources
47+
48+
def _call_api_client(self, resource):
49+
namespaced_resource = True
50+
51+
if resource == "pod":
52+
ret = self.v1.list_pod_for_all_namespaces(watch=False)
53+
elif resource == "service":
54+
ret = self.v1.list_service_for_all_namespaces(watch=False)
55+
elif resource == "deployment":
56+
ret = self.v1Beta1.list_deployment_for_all_namespaces(watch=False)
57+
elif resource == "statefulset":
58+
ret = self.v1Beta1.list_stateful_set_for_all_namespaces(watch=False)
59+
elif resource == "node":
60+
namespaced_resource = False
61+
ret = self.v1.list_node(watch=False)
62+
elif resource == "namespace":
63+
namespaced_resource = False
64+
ret = self.v1.list_namespace(watch=False)
65+
elif resource == "daemonset":
66+
ret = self.extensionsV1Beta1.list_daemon_set_for_all_namespaces(watch=False)
67+
elif resource == "networkpolicy":
68+
ret = self.extensionsV1Beta1.list_network_policy_for_all_namespaces(watch=False)
69+
elif resource == "thirdpartyresource":
70+
namespaced_resource = False
71+
ret = self.extensionsV1Beta1.list_third_party_resource(watch=False)
72+
elif resource == "replicationcontroller":
73+
ret = self.v1.list_replication_controller_for_all_namespaces(watch=False)
74+
elif resource == "replicaset":
75+
ret = self.extensionsV1Beta1.list_replica_set_for_all_namespaces(watch=False)
76+
elif resource == "ingress":
77+
ret = self.extensionsV1Beta1.list_ingress_for_all_namespaces(watch=False)
78+
elif resource == "endpoints":
79+
ret = self.v1.list_endpoints_for_all_namespaces(watch=False)
80+
elif resource == "configmap":
81+
ret = self.v1.list_config_map_for_all_namespaces(watch=False)
82+
elif resource == "event":
83+
ret = self.v1.list_event_for_all_namespaces(watch=False)
84+
elif resource == "limitrange":
85+
ret = self.v1.list_limit_range_for_all_namespaces(watch=False)
86+
elif resource == "configmap":
87+
ret = self.v1.list_config_map_for_all_namespaces(watch=False)
88+
elif resource == "persistentvolume":
89+
namespaced_resource = False
90+
ret = self.v1.list_persistent_volume(watch=False)
91+
elif resource == "secret":
92+
ret = self.v1.list_secret_for_all_namespaces(watch=False)
93+
elif resource == "resourcequota":
94+
ret = self.v1.list_resource_quota_for_all_namespaces(watch=False)
95+
elif resource == "componentstatus":
96+
namespaced_resource = False
97+
ret = self.v1.list_component_status(watch=False)
98+
elif resource == "podtemplate":
99+
ret = self.v1.list_pod_template_for_all_namespaces(watch=False)
100+
elif resource == "serviceaccount":
101+
ret = self.v1.list_service_account_for_all_namespaces(watch=False)
102+
elif resource == "horizontalpodautoscaler":
103+
ret = self.autoscalingV1Api.list_horizontal_pod_autoscaler_for_all_namespaces(watch=False)
104+
elif resource == "clusterrole":
105+
namespaced_resource = False
106+
ret = self.rbacApi.list_cluster_role(watch=False)
107+
elif resource == "clusterrolebinding":
108+
namespaced_resource = False
109+
ret = self.rbacApi.list_cluster_role_binding(watch=False)
110+
elif resource == "job":
111+
ret = self.batchV1Api.list_job_for_all_namespaces(watch=False)
112+
elif resource == "cronjob":
113+
ret = self.batchV2Api.list_cron_job_for_all_namespaces(watch=False)
114+
elif resource == "scheduledjob":
115+
ret = self.batchV2Api.list_scheduled_job_for_all_namespaces(watch=False)
116+
else:
117+
return None, namespaced_resource
118+
return ret, namespaced_resource

kubeshell/completer.py

Lines changed: 13 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
from subprocess import check_output
33
from prompt_toolkit.completion import Completer, Completion
44
from fuzzyfinder import fuzzyfinder
5+
import logging
56
import shlex
67
import json
78
import os
89
import os.path
9-
from kubernetes import client, config
10+
11+
from kubeshell.client import KubernetesClient
12+
1013

1114
class KubectlCompleter(Completer):
1215

@@ -17,6 +20,8 @@ def __init__(self):
1720
self.global_opts = []
1821
self.inline_help = True
1922
self.namespace = ""
23+
self.kube_client = KubernetesClient()
24+
self.logger = logging.getLogger(__name__)
2025

2126
try:
2227
DATA_DIR = os.path.dirname(os.path.realpath(__file__))
@@ -25,7 +30,7 @@ def __init__(self):
2530
self.kubectl_dict = json.load(json_file)
2631
self.populate_cmds_args_opts(self.kubectl_dict)
2732
except Exception as ex:
28-
print("got an exception" + ex.message)
33+
self.logger.error("got an exception" + ex.message)
2934

3035
def set_inline_help(self, val):
3136
self.inline_help = val
@@ -124,7 +129,7 @@ def parse_tokens(self, cmdline):
124129
elif state == "KUBECTL_ARG":
125130
if token.startswith("--"):
126131
continue
127-
resources = self.get_resources(arg)
132+
resources = self.kube_client.get_resource(arg)
128133
if resources:
129134
for resource_name, namespace in resources:
130135
if token == resource_name:
@@ -182,7 +187,7 @@ def get_completions(self, document, complete_event, smart_completion=None):
182187
yield Completion(suggestion, -len(last_token), display=suggestion, display_meta=self.help_msg)
183188
if word_before_cursor == "":
184189
if last_token == "--namespace":
185-
namespaces = self.get_resources("namespace")
190+
namespaces = self.kube_client.get_resource("namespace")
186191
for ns in namespaces:
187192
yield Completion(ns[0])
188193
return
@@ -223,7 +228,7 @@ def get_completions(self, document, complete_event, smart_completion=None):
223228
yield Completion(arg, -len(last_token))
224229
elif word_before_cursor == "":
225230
if last_token == "--namespace":
226-
namespaces = self.get_resources("namespace")
231+
namespaces = self.kube_client.get_resource("namespace")
227232
for ns in namespaces:
228233
yield Completion(ns[0])
229234
return
@@ -239,11 +244,11 @@ def get_completions(self, document, complete_event, smart_completion=None):
239244
last_token = tokens[-1]
240245
if word_before_cursor == "":
241246
if last_token == "--namespace":
242-
namespaces = self.get_resources("namespace")
247+
namespaces = self.kube_client.get_resource("namespace")
243248
for ns in namespaces:
244249
yield Completion(ns[0])
245250
return
246-
resources = self.get_resources(arg, namespace)
251+
resources = self.kube_client.get_resource(arg, namespace)
247252
if resources:
248253
for resourceName, namespace in resources:
249254
yield Completion(resourceName, display=resourceName, display_meta=namespace)
@@ -265,105 +270,10 @@ def get_completions(self, document, complete_event, smart_completion=None):
265270
help_msg = self.kubectl_dict['kubectl']['options'][global_opt]['help']
266271
yield Completion(global_opt, -len(word_before_cursor), display=global_opt, display_meta=self.help_msg)
267272
if last_token == "--namespace":
268-
namespaces = self.get_resources("namespace")
273+
namespaces = self.kube_client.get_resource("namespace")
269274
for ns in namespaces:
270275
yield Completion(ns[0])
271276
return
272277
else:
273278
pass
274279
return
275-
276-
def get_resources(self, resource, namespace="all"):
277-
resources = []
278-
try:
279-
config.load_kube_config()
280-
except Exception as e:
281-
# TODO: log errors to log file
282-
return resources
283-
284-
v1 = client.CoreV1Api()
285-
v1Beta1 = client.AppsV1beta1Api()
286-
extensionsV1Beta1 = client.ExtensionsV1beta1Api()
287-
autoscalingV1Api = client.AutoscalingV1Api()
288-
rbacAPi = client.RbacAuthorizationV1beta1Api()
289-
batchV1Api = client.BatchV1Api()
290-
batchV2Api = client.BatchV2alpha1Api()
291-
292-
ret = None
293-
namespaced_resource = True
294-
295-
if resource == "pod":
296-
ret = v1.list_pod_for_all_namespaces(watch=False)
297-
elif resource == "service":
298-
ret = v1.list_service_for_all_namespaces(watch=False)
299-
elif resource == "deployment":
300-
ret = v1Beta1.list_deployment_for_all_namespaces(watch=False)
301-
elif resource == "statefulset":
302-
ret = v1Beta1.list_stateful_set_for_all_namespaces(watch=False)
303-
elif resource == "node":
304-
namespaced_resource = False
305-
ret = v1.list_node(watch=False)
306-
elif resource == "namespace":
307-
namespaced_resource = False
308-
ret = v1.list_namespace(watch=False)
309-
elif resource == "daemonset":
310-
ret = extensionsV1Beta1.list_daemon_set_for_all_namespaces(watch=False)
311-
elif resource == "networkpolicy":
312-
ret = extensionsV1Beta1.list_network_policy_for_all_namespaces(watch=False)
313-
elif resource == "thirdpartyresource":
314-
namespaced_resource = False
315-
ret = extensionsV1Beta1.list_third_party_resource(watch=False)
316-
elif resource == "replicationcontroller":
317-
ret = v1.list_replication_controller_for_all_namespaces(watch=False)
318-
elif resource == "replicaset":
319-
ret = extensionsV1Beta1.list_replica_set_for_all_namespaces(watch=False)
320-
elif resource == "ingress":
321-
ret = extensionsV1Beta1.list_ingress_for_all_namespaces(watch=False)
322-
elif resource == "endpoints":
323-
ret = v1.list_endpoints_for_all_namespaces(watch=False)
324-
elif resource == "configmap":
325-
ret = v1.list_config_map_for_all_namespaces(watch=False)
326-
elif resource == "event":
327-
ret = v1.list_event_for_all_namespaces(watch=False)
328-
elif resource == "limitrange":
329-
ret = v1.list_limit_range_for_all_namespaces(watch=False)
330-
elif resource == "configmap":
331-
ret = v1.list_config_map_for_all_namespaces(watch=False)
332-
elif resource == "persistentvolume":
333-
namespaced_resource = False
334-
ret = v1.list_persistent_volume(watch=False)
335-
elif resource == "secret":
336-
ret = v1.list_secret_for_all_namespaces(watch=False)
337-
elif resource == "resourcequota":
338-
ret = v1.list_resource_quota_for_all_namespaces(watch=False)
339-
elif resource == "componentstatus":
340-
namespaced_resource = False
341-
ret = v1.list_component_status(watch=False)
342-
elif resource == "podtemplate":
343-
ret = v1.list_pod_template_for_all_namespaces(watch=False)
344-
elif resource == "serviceaccount":
345-
ret = v1.list_service_account_for_all_namespaces(watch=False)
346-
elif resource == "horizontalpodautoscaler":
347-
ret = autoscalingV1Api.list_horizontal_pod_autoscaler_for_all_namespaces(watch=False)
348-
elif resource == "clusterrole":
349-
namespaced_resource = False
350-
ret = rbacAPi.list_cluster_role(watch=False)
351-
elif resource == "clusterrolebinding":
352-
namespaced_resource = False
353-
ret = rbacAPi.list_cluster_role_binding(watch=False)
354-
elif resource == "job":
355-
ret = batchV1Api.list_job_for_all_namespaces(watch=False)
356-
elif resource == "cronjob":
357-
ret = batchV2Api.list_cron_job_for_all_namespaces(watch=False)
358-
elif resource == "scheduledjob":
359-
ret = batchV2Api.list_scheduled_job_for_all_namespaces(watch=False)
360-
361-
if ret:
362-
for i in ret.items:
363-
if namespace == "all" or not namespaced_resource:
364-
resources.append((i.metadata.name, i.metadata.namespace))
365-
elif namespace == i.metadata.namespace:
366-
resources.append((i.metadata.name, i.metadata.namespace))
367-
return resources
368-
return None
369-

kubeshell/kubeshell.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
import sys
1717
import subprocess
1818
import yaml
19-
19+
import logging
20+
logger = logging.getLogger(__name__)
2021

2122
inline_help = True
22-
2323
registry = load_key_bindings_for_prompt()
2424
completer = KubectlCompleter()
2525

@@ -102,17 +102,15 @@ def _(event):
102102
KubeConfig.switch_to_next_cluster()
103103
Kubeshell.clustername, Kubeshell.user, Kubeshell.namespace = KubeConfig.parse_kubeconfig()
104104
except Exception as e:
105-
# TODO: log errors to log file
106-
pass
105+
logger.warning("failed switching clusters", exc_info=1)
107106

108107
@registry.add_binding(Keys.F5)
109108
def _(event):
110109
try:
111110
KubeConfig.switch_to_next_namespace(Kubeshell.namespace)
112111
Kubeshell.clustername, Kubeshell.user, Kubeshell.namespace = KubeConfig.parse_kubeconfig()
113112
except Exception as e:
114-
# TODO: log errors to log file
115-
pass
113+
logger.warning("failed namespace switching", exc_info=1)
116114

117115
@registry.add_binding(Keys.F9)
118116
def _(event):
@@ -141,6 +139,7 @@ def run_cli(self):
141139
def get_title():
142140
return "kube-shell"
143141

142+
logger.info("running kube-shell event loop")
144143
if not os.path.exists(os.path.expanduser("~/.kube/config")):
145144
click.secho('Kube-shell uses ~/.kube/config for server side completion. Could not find ~/.kube/config. '
146145
'Server side completion functionality may not work.', fg='red', blink=True, bold=True)
@@ -149,8 +148,7 @@ def get_title():
149148
try:
150149
Kubeshell.clustername, Kubeshell.user, Kubeshell.namespace = KubeConfig.parse_kubeconfig()
151150
except:
152-
# TODO: log errors to log file
153-
pass
151+
logger.error("unable to parse ~/.kube/config %s", exc_info=1)
154152
completer.set_namespace(self.namespace)
155153

156154
try:

0 commit comments

Comments
 (0)