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