base.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. # pylint: skip-file
  2. # flake8: noqa
  3. # pylint: disable=too-many-lines
  4. # noqa: E301,E302,E303,T001
  5. class OpenShiftCLIError(Exception):
  6. '''Exception class for openshiftcli'''
  7. pass
  8. # pylint: disable=too-few-public-methods
  9. class OpenShiftCLI(object):
  10. ''' Class to wrap the command line tools '''
  11. def __init__(self,
  12. namespace,
  13. kubeconfig='/etc/origin/master/admin.kubeconfig',
  14. verbose=False,
  15. all_namespaces=False):
  16. ''' Constructor for OpenshiftCLI '''
  17. self.namespace = namespace
  18. self.verbose = verbose
  19. self.kubeconfig = kubeconfig
  20. self.all_namespaces = all_namespaces
  21. # Pylint allows only 5 arguments to be passed.
  22. # pylint: disable=too-many-arguments
  23. def _replace_content(self, resource, rname, content, force=False, sep='.'):
  24. ''' replace the current object with the content '''
  25. res = self._get(resource, rname)
  26. if not res['results']:
  27. return res
  28. fname = '/tmp/%s' % rname
  29. yed = Yedit(fname, res['results'][0], separator=sep)
  30. changes = []
  31. for key, value in content.items():
  32. changes.append(yed.put(key, value))
  33. if any([change[0] for change in changes]):
  34. yed.write()
  35. atexit.register(Utils.cleanup, [fname])
  36. return self._replace(fname, force)
  37. return {'returncode': 0, 'updated': False}
  38. def _replace(self, fname, force=False):
  39. '''replace the current object with oc replace'''
  40. cmd = ['replace', '-f', fname]
  41. if force:
  42. cmd.append('--force')
  43. return self.openshift_cmd(cmd)
  44. def _create_from_content(self, rname, content):
  45. '''create a temporary file and then call oc create on it'''
  46. fname = '/tmp/%s' % rname
  47. yed = Yedit(fname, content=content)
  48. yed.write()
  49. atexit.register(Utils.cleanup, [fname])
  50. return self._create(fname)
  51. def _create(self, fname):
  52. '''call oc create on a filename'''
  53. return self.openshift_cmd(['create', '-f', fname])
  54. def _delete(self, resource, rname, selector=None):
  55. '''call oc delete on a resource'''
  56. cmd = ['delete', resource, rname]
  57. if selector:
  58. cmd.append('--selector=%s' % selector)
  59. return self.openshift_cmd(cmd)
  60. def _process(self, template_name, create=False, params=None, template_data=None): # noqa: E501
  61. '''process a template
  62. template_name: the name of the template to process
  63. create: whether to send to oc create after processing
  64. params: the parameters for the template
  65. template_data: the incoming template's data; instead of a file
  66. '''
  67. cmd = ['process']
  68. if template_data:
  69. cmd.extend(['-f', '-'])
  70. else:
  71. cmd.append(template_name)
  72. if params:
  73. param_str = ["%s=%s" % (key, value) for key, value in params.items()]
  74. cmd.append('-v')
  75. cmd.extend(param_str)
  76. results = self.openshift_cmd(cmd, output=True, input_data=template_data)
  77. if results['returncode'] != 0 or not create:
  78. return results
  79. fname = '/tmp/%s' % template_name
  80. yed = Yedit(fname, results['results'])
  81. yed.write()
  82. atexit.register(Utils.cleanup, [fname])
  83. return self.openshift_cmd(['create', '-f', fname])
  84. def _get(self, resource, rname=None, selector=None):
  85. '''return a resource by name '''
  86. cmd = ['get', resource]
  87. if selector:
  88. cmd.append('--selector=%s' % selector)
  89. elif rname:
  90. cmd.append(rname)
  91. cmd.extend(['-o', 'json'])
  92. rval = self.openshift_cmd(cmd, output=True)
  93. # Ensure results are retuned in an array
  94. if 'items' in rval:
  95. rval['results'] = rval['items']
  96. elif not isinstance(rval['results'], list):
  97. rval['results'] = [rval['results']]
  98. return rval
  99. def _schedulable(self, node=None, selector=None, schedulable=True):
  100. ''' perform oadm manage-node scheduable '''
  101. cmd = ['manage-node']
  102. if node:
  103. cmd.extend(node)
  104. else:
  105. cmd.append('--selector=%s' % selector)
  106. cmd.append('--schedulable=%s' % schedulable)
  107. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') # noqa: E501
  108. def _list_pods(self, node=None, selector=None, pod_selector=None):
  109. ''' perform oadm list pods
  110. node: the node in which to list pods
  111. selector: the label selector filter if provided
  112. pod_selector: the pod selector filter if provided
  113. '''
  114. cmd = ['manage-node']
  115. if node:
  116. cmd.extend(node)
  117. else:
  118. cmd.append('--selector=%s' % selector)
  119. if pod_selector:
  120. cmd.append('--pod-selector=%s' % pod_selector)
  121. cmd.extend(['--list-pods', '-o', 'json'])
  122. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  123. # pylint: disable=too-many-arguments
  124. def _evacuate(self, node=None, selector=None, pod_selector=None, dry_run=False, grace_period=None, force=False):
  125. ''' perform oadm manage-node evacuate '''
  126. cmd = ['manage-node']
  127. if node:
  128. cmd.extend(node)
  129. else:
  130. cmd.append('--selector=%s' % selector)
  131. if dry_run:
  132. cmd.append('--dry-run')
  133. if pod_selector:
  134. cmd.append('--pod-selector=%s' % pod_selector)
  135. if grace_period:
  136. cmd.append('--grace-period=%s' % int(grace_period))
  137. if force:
  138. cmd.append('--force')
  139. cmd.append('--evacuate')
  140. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  141. def _version(self):
  142. ''' return the openshift version'''
  143. return self.openshift_cmd(['version'], output=True, output_type='raw')
  144. def _import_image(self, url=None, name=None, tag=None):
  145. ''' perform image import '''
  146. cmd = ['import-image']
  147. image = '{0}'.format(name)
  148. if tag:
  149. image += ':{0}'.format(tag)
  150. cmd.append(image)
  151. if url:
  152. cmd.append('--from={0}/{1}'.format(url, image))
  153. cmd.append('-n{0}'.format(self.namespace))
  154. cmd.append('--confirm')
  155. return self.openshift_cmd(cmd)
  156. # pylint: disable=too-many-arguments,too-many-branches
  157. def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
  158. '''Base command for oc '''
  159. cmds = []
  160. if oadm:
  161. cmds = ['/usr/bin/oadm']
  162. else:
  163. cmds = ['/usr/bin/oc']
  164. if self.all_namespaces:
  165. cmds.extend(['--all-namespaces'])
  166. elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501
  167. cmds.extend(['-n', self.namespace])
  168. cmds.extend(cmd)
  169. rval = {}
  170. results = ''
  171. err = None
  172. if self.verbose:
  173. print(' '.join(cmds))
  174. proc = subprocess.Popen(cmds,
  175. stdin=subprocess.PIPE,
  176. stdout=subprocess.PIPE,
  177. stderr=subprocess.PIPE,
  178. env={'KUBECONFIG': self.kubeconfig})
  179. stdout, stderr = proc.communicate(input_data)
  180. rval = {"returncode": proc.returncode,
  181. "results": results,
  182. "cmd": ' '.join(cmds)}
  183. if proc.returncode == 0:
  184. if output:
  185. if output_type == 'json':
  186. try:
  187. rval['results'] = json.loads(stdout)
  188. except ValueError as err:
  189. if "No JSON object could be decoded" in err.args:
  190. err = err.args
  191. elif output_type == 'raw':
  192. rval['results'] = stdout
  193. if self.verbose:
  194. print("STDOUT: {0}".format(stdout))
  195. print("STDERR: {0}".format(stderr))
  196. if err:
  197. rval.update({"err": err,
  198. "stderr": stderr,
  199. "stdout": stdout,
  200. "cmd": cmds})
  201. else:
  202. rval.update({"stderr": stderr,
  203. "stdout": stdout,
  204. "results": {}})
  205. return rval
  206. class Utils(object):
  207. ''' utilities for openshiftcli modules '''
  208. @staticmethod
  209. def create_file(rname, data, ftype='yaml'):
  210. ''' create a file in tmp with name and contents'''
  211. path = os.path.join('/tmp', rname)
  212. with open(path, 'w') as fds:
  213. if ftype == 'yaml':
  214. fds.write(yaml.dump(data, Dumper=yaml.RoundTripDumper))
  215. elif ftype == 'json':
  216. fds.write(json.dumps(data))
  217. else:
  218. fds.write(data)
  219. # Register cleanup when module is done
  220. atexit.register(Utils.cleanup, [path])
  221. return path
  222. @staticmethod
  223. def create_files_from_contents(content, content_type=None):
  224. '''Turn an array of dict: filename, content into a files array'''
  225. if not isinstance(content, list):
  226. content = [content]
  227. files = []
  228. for item in content:
  229. path = Utils.create_file(item['path'], item['data'], ftype=content_type)
  230. files.append({'name': os.path.basename(path), 'path': path})
  231. return files
  232. @staticmethod
  233. def cleanup(files):
  234. '''Clean up on exit '''
  235. for sfile in files:
  236. if os.path.exists(sfile):
  237. if os.path.isdir(sfile):
  238. shutil.rmtree(sfile)
  239. elif os.path.isfile(sfile):
  240. os.remove(sfile)
  241. @staticmethod
  242. def exists(results, _name):
  243. ''' Check to see if the results include the name '''
  244. if not results:
  245. return False
  246. if Utils.find_result(results, _name):
  247. return True
  248. return False
  249. @staticmethod
  250. def find_result(results, _name):
  251. ''' Find the specified result by name'''
  252. rval = None
  253. for result in results:
  254. if 'metadata' in result and result['metadata']['name'] == _name:
  255. rval = result
  256. break
  257. return rval
  258. @staticmethod
  259. def get_resource_file(sfile, sfile_type='yaml'):
  260. ''' return the service file '''
  261. contents = None
  262. with open(sfile) as sfd:
  263. contents = sfd.read()
  264. if sfile_type == 'yaml':
  265. contents = yaml.load(contents, yaml.RoundTripLoader)
  266. elif sfile_type == 'json':
  267. contents = json.loads(contents)
  268. return contents
  269. @staticmethod
  270. def filter_versions(stdout):
  271. ''' filter the oc version output '''
  272. version_dict = {}
  273. version_search = ['oc', 'openshift', 'kubernetes']
  274. for line in stdout.strip().split('\n'):
  275. for term in version_search:
  276. if not line:
  277. continue
  278. if line.startswith(term):
  279. version_dict[term] = line.split()[-1]
  280. # horrible hack to get openshift version in Openshift 3.2
  281. # By default "oc version in 3.2 does not return an "openshift" version
  282. if "openshift" not in version_dict:
  283. version_dict["openshift"] = version_dict["oc"]
  284. return version_dict
  285. @staticmethod
  286. def add_custom_versions(versions):
  287. ''' create custom versions strings '''
  288. versions_dict = {}
  289. for tech, version in versions.items():
  290. # clean up "-" from version
  291. if "-" in version:
  292. version = version.split("-")[0]
  293. if version.startswith('v'):
  294. versions_dict[tech + '_numeric'] = version[1:].split('+')[0]
  295. # "v3.3.0.33" is what we have, we want "3.3"
  296. versions_dict[tech + '_short'] = version[1:4]
  297. return versions_dict
  298. @staticmethod
  299. def openshift_installed():
  300. ''' check if openshift is installed '''
  301. import yum
  302. yum_base = yum.YumBase()
  303. if yum_base.rpmdb.searchNevra(name='atomic-openshift'):
  304. return True
  305. return False
  306. # Disabling too-many-branches. This is a yaml dictionary comparison function
  307. # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
  308. @staticmethod
  309. def check_def_equal(user_def, result_def, skip_keys=None, debug=False):
  310. ''' Given a user defined definition, compare it with the results given back by our query. '''
  311. # Currently these values are autogenerated and we do not need to check them
  312. skip = ['metadata', 'status']
  313. if skip_keys:
  314. skip.extend(skip_keys)
  315. for key, value in result_def.items():
  316. if key in skip:
  317. continue
  318. # Both are lists
  319. if isinstance(value, list):
  320. if key not in user_def:
  321. if debug:
  322. print('User data does not have key [%s]' % key)
  323. print('User data: %s' % user_def)
  324. return False
  325. if not isinstance(user_def[key], list):
  326. if debug:
  327. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  328. return False
  329. if len(user_def[key]) != len(value):
  330. if debug:
  331. print("List lengths are not equal.")
  332. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  333. print("user_def: %s" % user_def[key])
  334. print("value: %s" % value)
  335. return False
  336. for values in zip(user_def[key], value):
  337. if isinstance(values[0], dict) and isinstance(values[1], dict):
  338. if debug:
  339. print('sending list - list')
  340. print(type(values[0]))
  341. print(type(values[1]))
  342. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  343. if not result:
  344. print('list compare returned false')
  345. return False
  346. elif value != user_def[key]:
  347. if debug:
  348. print('value should be identical')
  349. print(value)
  350. print(user_def[key])
  351. return False
  352. # recurse on a dictionary
  353. elif isinstance(value, dict):
  354. if key not in user_def:
  355. if debug:
  356. print("user_def does not have key [%s]" % key)
  357. return False
  358. if not isinstance(user_def[key], dict):
  359. if debug:
  360. print("dict returned false: not instance of dict")
  361. return False
  362. # before passing ensure keys match
  363. api_values = set(value.keys()) - set(skip)
  364. user_values = set(user_def[key].keys()) - set(skip)
  365. if api_values != user_values:
  366. if debug:
  367. print("keys are not equal in dict")
  368. print(api_values)
  369. print(user_values)
  370. return False
  371. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  372. if not result:
  373. if debug:
  374. print("dict returned false")
  375. print(result)
  376. return False
  377. # Verify each key, value pair is the same
  378. else:
  379. if key not in user_def or value != user_def[key]:
  380. if debug:
  381. print("value not equal; user_def does not have key")
  382. print(key)
  383. print(value)
  384. if key in user_def:
  385. print(user_def[key])
  386. return False
  387. if debug:
  388. print('returning true')
  389. return True
  390. class OpenShiftCLIConfig(object):
  391. '''Generic Config'''
  392. def __init__(self, rname, namespace, kubeconfig, options):
  393. self.kubeconfig = kubeconfig
  394. self.name = rname
  395. self.namespace = namespace
  396. self._options = options
  397. @property
  398. def config_options(self):
  399. ''' return config options '''
  400. return self._options
  401. def to_option_list(self):
  402. '''return all options as a string'''
  403. return self.stringify()
  404. def stringify(self):
  405. ''' return the options hash as cli params in a string '''
  406. rval = []
  407. for key, data in self.config_options.items():
  408. if data['include'] \
  409. and (data['value'] or isinstance(data['value'], int)):
  410. rval.append('--%s=%s' % (key.replace('_', '-'), data['value']))
  411. return rval