blob: f6f499dbce4057609cf84a26c2c5d6e848065805 [file] [log] [blame]
Alex9a4ad212020-10-01 18:04:25 -05001"""
2Module to handle interaction with Kube
3"""
4import base64
5import os
6import urllib3
7import yaml
8
9from kubernetes import client as kclient, config as kconfig
10from kubernetes.stream import stream
11
12from cfg_checker.common import logger, logger_cli
13from cfg_checker.common.exception import InvalidReturnException, KubeException
14from cfg_checker.common.file_utils import create_temp_file_with_content
15from cfg_checker.common.other import utils, shell
16from cfg_checker.common.ssh_utils import ssh_shell_p
Alex359e5752021-08-16 17:28:30 -050017from cfg_checker.common.const import ENV_LOCAL
Alex9a4ad212020-10-01 18:04:25 -050018
19urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
20
21
22def _init_kube_conf_local(config):
23 # Init kube library locally
Alex359e5752021-08-16 17:28:30 -050024 _path = "local:{}".format(config.kube_config_path)
Alex9a4ad212020-10-01 18:04:25 -050025 try:
26 kconfig.load_kube_config()
Alex33747812021-04-07 10:11:39 -050027 if config.insecure:
28 kconfig.assert_hostname = False
29 kconfig.client_side_validation = False
Alex9a4ad212020-10-01 18:04:25 -050030 logger_cli.debug(
31 "...found Kube env: core, {}". format(
32 ",".join(
33 kclient.CoreApi().get_api_versions().versions
34 )
35 )
36 )
37 return kconfig, kclient.ApiClient()
38 except Exception as e:
39 logger.warn("Failed to init local Kube client: {}".format(
40 str(e)
41 )
42 )
Alex359e5752021-08-16 17:28:30 -050043 return None, None, _path
Alex9a4ad212020-10-01 18:04:25 -050044
45
46def _init_kube_conf_remote(config):
47 # init remote client
48 # Preload Kube token
49 """
50 APISERVER=$(kubectl config view --minify |
51 grep server | cut -f 2- -d ":" | tr -d " ")
52 SECRET_NAME=$(kubectl get secrets |
53 grep ^default | cut -f1 -d ' ')
54 TOKEN=$(kubectl describe secret $SECRET_NAME |
55 grep -E '^token' | cut -f2 -d':' | tr -d " ")
56
57 echo "Detected API Server at: '${APISERVER}'"
58 echo "Got secret: '${SECRET_NAME}'"
59 echo "Loaded token: '${TOKEN}'"
60
61 curl $APISERVER/api
62 --header "Authorization: Bearer $TOKEN" --insecure
63 """
64 import yaml
Alex359e5752021-08-16 17:28:30 -050065 _path = ''
66 if not config.env_name == ENV_LOCAL:
67 _path = "{}@{}:{}".format(
68 config.ssh_user,
69 config.ssh_host,
70 config.kube_config_path
71 )
Alex9d913532021-03-24 18:01:45 -050072 _c_data = ssh_shell_p(
73 "sudo cat " + config.kube_config_path,
74 config.ssh_host,
75 username=config.ssh_user,
76 keypath=config.ssh_key,
77 piped=False,
78 use_sudo=config.ssh_uses_sudo,
79 )
80 else:
Alex359e5752021-08-16 17:28:30 -050081 _path = "local:{}".format(config.kube_config_path)
Alex9d913532021-03-24 18:01:45 -050082 with open(config.kube_config_path, 'r') as ff:
83 _c_data = ff.read()
Alex9a4ad212020-10-01 18:04:25 -050084
Alex359e5752021-08-16 17:28:30 -050085 if len(_c_data) < 1:
86 return None, None, _path
87
Alex9a4ad212020-10-01 18:04:25 -050088 _conf = yaml.load(_c_data, Loader=yaml.SafeLoader)
89
90 _kube_conf = kclient.Configuration()
91 # A remote host configuration
92
93 # To work with remote cluster, we need to extract these
94 # keys = ['host', 'ssl_ca_cert', 'cert_file', 'key_file', 'verify_ssl']
95 # When v12 of the client will be release, we will use load_from_dict
96
97 _kube_conf.ssl_ca_cert = create_temp_file_with_content(
98 base64.standard_b64decode(
99 _conf['clusters'][0]['cluster']['certificate-authority-data']
100 )
101 )
102 _host = _conf['clusters'][0]['cluster']['server']
103 _kube_conf.cert_file = create_temp_file_with_content(
104 base64.standard_b64decode(
105 _conf['users'][0]['user']['client-certificate-data']
106 )
107 )
108 _kube_conf.key_file = create_temp_file_with_content(
109 base64.standard_b64decode(
110 _conf['users'][0]['user']['client-key-data']
111 )
112 )
113 if "http" not in _host or "443" not in _host:
114 logger_cli.error(
115 "Failed to extract Kube host: '{}'".format(_host)
116 )
117 else:
118 logger_cli.debug(
119 "...'context' host extracted: '{}' via SSH@{}".format(
120 _host,
121 config.ssh_host
122 )
123 )
124
125 # Substitute context host to ours
126 _tmp = _host.split(':')
127 _kube_conf.host = \
128 _tmp[0] + "://" + config.mcp_host + ":" + _tmp[2]
129 config.kube_port = _tmp[2]
130 logger_cli.debug(
131 "...kube remote host updated to {}".format(
132 _kube_conf.host
133 )
134 )
135 _kube_conf.verify_ssl = False
136 _kube_conf.debug = config.debug
Alex33747812021-04-07 10:11:39 -0500137 if config.insecure:
138 _kube_conf.assert_hostname = False
139 _kube_conf.client_side_validation = False
140
Alex9a4ad212020-10-01 18:04:25 -0500141 # Nevertheless if you want to do it
142 # you can with these 2 parameters
143 # configuration.verify_ssl=True
144 # ssl_ca_cert is the filepath
145 # to the file that contains the certificate.
146 # configuration.ssl_ca_cert="certificate"
147
148 # _kube_conf.api_key = {
149 # "authorization": "Bearer " + config.kube_token
150 # }
151
152 # Create a ApiClient with our config
153 _kube_api = kclient.ApiClient(_kube_conf)
154
Alex359e5752021-08-16 17:28:30 -0500155 return _kube_conf, _kube_api, _path
Alex9a4ad212020-10-01 18:04:25 -0500156
157
158class KubeApi(object):
159 def __init__(self, config):
160 self.config = config
Alex359e5752021-08-16 17:28:30 -0500161 self.initialized = self._init_kclient()
Alex9a4ad212020-10-01 18:04:25 -0500162 self.last_response = None
163
164 def _init_kclient(self):
165 # if there is no password - try to get local, if this available
Alex359e5752021-08-16 17:28:30 -0500166 logger_cli.debug("... init kube config")
Alex9a4ad212020-10-01 18:04:25 -0500167 if self.config.env_name == "local":
Alex359e5752021-08-16 17:28:30 -0500168 self.kConf, self.kApi, self.kConfigPath = _init_kube_conf_local(
169 self.config
170 )
Alex9a4ad212020-10-01 18:04:25 -0500171 self.is_local = True
172 # Load local config data
173 if os.path.exists(self.config.kube_config_path):
174 _c_data = shell("sudo cat " + self.config.kube_config_path)
175 _conf = yaml.load(_c_data, Loader=yaml.SafeLoader)
176 self.user_keypath = create_temp_file_with_content(
177 base64.standard_b64decode(
178 _conf['users'][0]['user']['client-key-data']
179 )
180 )
181 self.yaml_conf = _c_data
182 else:
Alex359e5752021-08-16 17:28:30 -0500183 self.kConf, self.kApi, self.kConfigPath = _init_kube_conf_remote(
184 self.config
185 )
Alex9a4ad212020-10-01 18:04:25 -0500186 self.is_local = False
187
Alex359e5752021-08-16 17:28:30 -0500188 if self.kConf is None or self.kApi is None:
189 return False
190 else:
191 return True
192
Alex9a4ad212020-10-01 18:04:25 -0500193 def get_versions_api(self):
194 # client.CoreApi().get_api_versions().versions
195 return kclient.VersionApi(self.kApi)
196
197
198class KubeRemote(KubeApi):
199 def __init__(self, config):
200 super(KubeRemote, self).__init__(config)
201 self._coreV1 = None
202
203 @property
204 def CoreV1(self):
205 if not self._coreV1:
206 self._coreV1 = kclient.CoreV1Api(self.kApi)
207 return self._coreV1
208
209 @staticmethod
210 def _typed_list_to_dict(i_list):
211 _dict = {}
212 for _item in i_list:
213 _d = _item.to_dict()
214 _type = _d.pop("type")
215 _dict[_type.lower()] = _d
216
217 return _dict
218
219 @staticmethod
220 def _get_listed_attrs(items, _path):
221 _list = []
222 for _n in items:
223 _list.append(utils.rgetattr(_n, _path))
224
225 return _list
226
227 def get_node_info(self, http=False):
228 # Query API for the nodes and do some presorting
229 _nodes = {}
230 if http:
231 _raw_nodes = self.CoreV1.list_node_with_http_info()
232 else:
233 _raw_nodes = self.CoreV1.list_node()
234
235 if not isinstance(_raw_nodes, kclient.models.v1_node_list.V1NodeList):
236 raise InvalidReturnException(
237 "Invalid return type: '{}'".format(type(_raw_nodes))
238 )
239
240 for _n in _raw_nodes.items:
241 _name = _n.metadata.name
242 _d = _n.to_dict()
243 # parse inner data classes as dicts
244 _d['addresses'] = self._typed_list_to_dict(_n.status.addresses)
245 _d['conditions'] = self._typed_list_to_dict(_n.status.conditions)
246 # Update 'status' type
247 if isinstance(_d['conditions']['ready']['status'], str):
248 _d['conditions']['ready']['status'] = utils.to_bool(
249 _d['conditions']['ready']['status']
250 )
251 # Parse image names?
252 # TODO: Here is the place where we can parse each node image names
253
254 # Parse roles
255 _d['labels'] = {}
256 for _label, _data in _d["metadata"]["labels"].items():
257 if _data.lower() in ["true", "false"]:
258 _d['labels'][_label] = utils.to_bool(_data)
259 else:
260 _d['labels'][_label] = _data
261
262 # Save
263 _nodes[_name] = _d
264
265 # debug report on how many nodes detected
266 logger_cli.debug("...node items returned '{}'".format(len(_nodes)))
267
268 return _nodes
269
270 def exec_on_target_pod(
271 self,
272 cmd,
273 pod_name,
274 namespace,
275 strict=False,
276 _request_timeout=120,
277 **kwargs
278 ):
Alex33747812021-04-07 10:11:39 -0500279 logger_cli.debug(
280 "...searching for pods with the name '{}'".format(pod_name)
281 )
Alex9a4ad212020-10-01 18:04:25 -0500282 _pods = {}
283 _pods = self._coreV1.list_namespaced_pod(namespace)
284 _names = self._get_listed_attrs(_pods.items, "metadata.name")
285
286 _pname = ""
287 if not strict:
Alex33747812021-04-07 10:11:39 -0500288 _pnames = [n for n in _names if n.startswith(pod_name)]
289 if len(_pnames) > 1:
Alex9a4ad212020-10-01 18:04:25 -0500290 logger_cli.debug(
291 "...more than one pod found for '{}': {}\n"
292 "...using first one".format(
293 pod_name,
Alex33747812021-04-07 10:11:39 -0500294 ", ".join(_pnames)
Alex9a4ad212020-10-01 18:04:25 -0500295 )
296 )
Alex33747812021-04-07 10:11:39 -0500297 _pname = _pnames[0]
Alex9a4ad212020-10-01 18:04:25 -0500298 elif len(_pname) < 1:
299 raise KubeException("No pods found for '{}'".format(pod_name))
300 else:
301 _pname = pod_name
Alex33747812021-04-07 10:11:39 -0500302 logger_cli.debug(
303 "...cmd: [CoreV1] exec {} -n {} -- {}".format(
304 _pname,
305 namespace,
306 cmd
307 )
308 )
Alex9a4ad212020-10-01 18:04:25 -0500309 _r = stream(
310 self.CoreV1.connect_get_namespaced_pod_exec,
311 _pname,
312 namespace,
313 command=cmd.split(),
314 stderr=True,
315 stdin=False,
316 stdout=True,
317 tty=False,
318 _request_timeout=_request_timeout,
319 **kwargs
320 )
321
322 return _r