blob: 5b791cd6295a263724db51a3eabf13f72c3c5189 [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
17
18urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
19
20
21def _init_kube_conf_local(config):
22 # Init kube library locally
23 try:
24 kconfig.load_kube_config()
Alex33747812021-04-07 10:11:39 -050025 if config.insecure:
26 kconfig.assert_hostname = False
27 kconfig.client_side_validation = False
Alex9a4ad212020-10-01 18:04:25 -050028 logger_cli.debug(
29 "...found Kube env: core, {}". format(
30 ",".join(
31 kclient.CoreApi().get_api_versions().versions
32 )
33 )
34 )
35 return kconfig, kclient.ApiClient()
36 except Exception as e:
37 logger.warn("Failed to init local Kube client: {}".format(
38 str(e)
39 )
40 )
41 return None, None
42
43
44def _init_kube_conf_remote(config):
45 # init remote client
46 # Preload Kube token
47 """
48 APISERVER=$(kubectl config view --minify |
49 grep server | cut -f 2- -d ":" | tr -d " ")
50 SECRET_NAME=$(kubectl get secrets |
51 grep ^default | cut -f1 -d ' ')
52 TOKEN=$(kubectl describe secret $SECRET_NAME |
53 grep -E '^token' | cut -f2 -d':' | tr -d " ")
54
55 echo "Detected API Server at: '${APISERVER}'"
56 echo "Got secret: '${SECRET_NAME}'"
57 echo "Loaded token: '${TOKEN}'"
58
59 curl $APISERVER/api
60 --header "Authorization: Bearer $TOKEN" --insecure
61 """
62 import yaml
Alex9d913532021-03-24 18:01:45 -050063 if not config.kube_config_path:
64 _c_data = ssh_shell_p(
65 "sudo cat " + config.kube_config_path,
66 config.ssh_host,
67 username=config.ssh_user,
68 keypath=config.ssh_key,
69 piped=False,
70 use_sudo=config.ssh_uses_sudo,
71 )
72 else:
73 with open(config.kube_config_path, 'r') as ff:
74 _c_data = ff.read()
Alex9a4ad212020-10-01 18:04:25 -050075
76 _conf = yaml.load(_c_data, Loader=yaml.SafeLoader)
77
78 _kube_conf = kclient.Configuration()
79 # A remote host configuration
80
81 # To work with remote cluster, we need to extract these
82 # keys = ['host', 'ssl_ca_cert', 'cert_file', 'key_file', 'verify_ssl']
83 # When v12 of the client will be release, we will use load_from_dict
84
85 _kube_conf.ssl_ca_cert = create_temp_file_with_content(
86 base64.standard_b64decode(
87 _conf['clusters'][0]['cluster']['certificate-authority-data']
88 )
89 )
90 _host = _conf['clusters'][0]['cluster']['server']
91 _kube_conf.cert_file = create_temp_file_with_content(
92 base64.standard_b64decode(
93 _conf['users'][0]['user']['client-certificate-data']
94 )
95 )
96 _kube_conf.key_file = create_temp_file_with_content(
97 base64.standard_b64decode(
98 _conf['users'][0]['user']['client-key-data']
99 )
100 )
101 if "http" not in _host or "443" not in _host:
102 logger_cli.error(
103 "Failed to extract Kube host: '{}'".format(_host)
104 )
105 else:
106 logger_cli.debug(
107 "...'context' host extracted: '{}' via SSH@{}".format(
108 _host,
109 config.ssh_host
110 )
111 )
112
113 # Substitute context host to ours
114 _tmp = _host.split(':')
115 _kube_conf.host = \
116 _tmp[0] + "://" + config.mcp_host + ":" + _tmp[2]
117 config.kube_port = _tmp[2]
118 logger_cli.debug(
119 "...kube remote host updated to {}".format(
120 _kube_conf.host
121 )
122 )
123 _kube_conf.verify_ssl = False
124 _kube_conf.debug = config.debug
Alex33747812021-04-07 10:11:39 -0500125 if config.insecure:
126 _kube_conf.assert_hostname = False
127 _kube_conf.client_side_validation = False
128
Alex9a4ad212020-10-01 18:04:25 -0500129 # Nevertheless if you want to do it
130 # you can with these 2 parameters
131 # configuration.verify_ssl=True
132 # ssl_ca_cert is the filepath
133 # to the file that contains the certificate.
134 # configuration.ssl_ca_cert="certificate"
135
136 # _kube_conf.api_key = {
137 # "authorization": "Bearer " + config.kube_token
138 # }
139
140 # Create a ApiClient with our config
141 _kube_api = kclient.ApiClient(_kube_conf)
142
143 return _kube_conf, _kube_api
144
145
146class KubeApi(object):
147 def __init__(self, config):
148 self.config = config
149 self._init_kclient()
150 self.last_response = None
151
152 def _init_kclient(self):
153 # if there is no password - try to get local, if this available
154 logger_cli.debug("# Initializong Kube config...")
155 if self.config.env_name == "local":
156 self.kConf, self.kApi = _init_kube_conf_local(self.config)
157 self.is_local = True
158 # Load local config data
159 if os.path.exists(self.config.kube_config_path):
160 _c_data = shell("sudo cat " + self.config.kube_config_path)
161 _conf = yaml.load(_c_data, Loader=yaml.SafeLoader)
162 self.user_keypath = create_temp_file_with_content(
163 base64.standard_b64decode(
164 _conf['users'][0]['user']['client-key-data']
165 )
166 )
167 self.yaml_conf = _c_data
168 else:
169 self.kConf, self.kApi = _init_kube_conf_remote(self.config)
170 self.is_local = False
171
172 def get_versions_api(self):
173 # client.CoreApi().get_api_versions().versions
174 return kclient.VersionApi(self.kApi)
175
176
177class KubeRemote(KubeApi):
178 def __init__(self, config):
179 super(KubeRemote, self).__init__(config)
180 self._coreV1 = None
181
182 @property
183 def CoreV1(self):
184 if not self._coreV1:
185 self._coreV1 = kclient.CoreV1Api(self.kApi)
186 return self._coreV1
187
188 @staticmethod
189 def _typed_list_to_dict(i_list):
190 _dict = {}
191 for _item in i_list:
192 _d = _item.to_dict()
193 _type = _d.pop("type")
194 _dict[_type.lower()] = _d
195
196 return _dict
197
198 @staticmethod
199 def _get_listed_attrs(items, _path):
200 _list = []
201 for _n in items:
202 _list.append(utils.rgetattr(_n, _path))
203
204 return _list
205
206 def get_node_info(self, http=False):
207 # Query API for the nodes and do some presorting
208 _nodes = {}
209 if http:
210 _raw_nodes = self.CoreV1.list_node_with_http_info()
211 else:
212 _raw_nodes = self.CoreV1.list_node()
213
214 if not isinstance(_raw_nodes, kclient.models.v1_node_list.V1NodeList):
215 raise InvalidReturnException(
216 "Invalid return type: '{}'".format(type(_raw_nodes))
217 )
218
219 for _n in _raw_nodes.items:
220 _name = _n.metadata.name
221 _d = _n.to_dict()
222 # parse inner data classes as dicts
223 _d['addresses'] = self._typed_list_to_dict(_n.status.addresses)
224 _d['conditions'] = self._typed_list_to_dict(_n.status.conditions)
225 # Update 'status' type
226 if isinstance(_d['conditions']['ready']['status'], str):
227 _d['conditions']['ready']['status'] = utils.to_bool(
228 _d['conditions']['ready']['status']
229 )
230 # Parse image names?
231 # TODO: Here is the place where we can parse each node image names
232
233 # Parse roles
234 _d['labels'] = {}
235 for _label, _data in _d["metadata"]["labels"].items():
236 if _data.lower() in ["true", "false"]:
237 _d['labels'][_label] = utils.to_bool(_data)
238 else:
239 _d['labels'][_label] = _data
240
241 # Save
242 _nodes[_name] = _d
243
244 # debug report on how many nodes detected
245 logger_cli.debug("...node items returned '{}'".format(len(_nodes)))
246
247 return _nodes
248
249 def exec_on_target_pod(
250 self,
251 cmd,
252 pod_name,
253 namespace,
254 strict=False,
255 _request_timeout=120,
256 **kwargs
257 ):
Alex33747812021-04-07 10:11:39 -0500258 logger_cli.debug(
259 "...searching for pods with the name '{}'".format(pod_name)
260 )
Alex9a4ad212020-10-01 18:04:25 -0500261 _pods = {}
262 _pods = self._coreV1.list_namespaced_pod(namespace)
263 _names = self._get_listed_attrs(_pods.items, "metadata.name")
264
265 _pname = ""
266 if not strict:
Alex33747812021-04-07 10:11:39 -0500267 _pnames = [n for n in _names if n.startswith(pod_name)]
268 if len(_pnames) > 1:
Alex9a4ad212020-10-01 18:04:25 -0500269 logger_cli.debug(
270 "...more than one pod found for '{}': {}\n"
271 "...using first one".format(
272 pod_name,
Alex33747812021-04-07 10:11:39 -0500273 ", ".join(_pnames)
Alex9a4ad212020-10-01 18:04:25 -0500274 )
275 )
Alex33747812021-04-07 10:11:39 -0500276 _pname = _pnames[0]
Alex9a4ad212020-10-01 18:04:25 -0500277 elif len(_pname) < 1:
278 raise KubeException("No pods found for '{}'".format(pod_name))
279 else:
280 _pname = pod_name
Alex33747812021-04-07 10:11:39 -0500281 logger_cli.debug(
282 "...cmd: [CoreV1] exec {} -n {} -- {}".format(
283 _pname,
284 namespace,
285 cmd
286 )
287 )
Alex9a4ad212020-10-01 18:04:25 -0500288 _r = stream(
289 self.CoreV1.connect_get_namespaced_pod_exec,
290 _pname,
291 namespace,
292 command=cmd.split(),
293 stderr=True,
294 stdin=False,
295 stdout=True,
296 tty=False,
297 _request_timeout=_request_timeout,
298 **kwargs
299 )
300
301 return _r