oc_version.py 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509
  1. #!/usr/bin/env python
  2. # pylint: disable=missing-docstring
  3. # flake8: noqa: T001
  4. # ___ ___ _ _ ___ ___ _ _____ ___ ___
  5. # / __| __| \| | __| _ \ /_\_ _| __| \
  6. # | (_ | _|| .` | _|| / / _ \| | | _|| |) |
  7. # \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____
  8. # | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|
  9. # | |) | (_) | | .` | (_) || | | _|| |) | | | |
  10. # |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|
  11. #
  12. # Copyright 2016 Red Hat, Inc. and/or its affiliates
  13. # and other contributors as indicated by the @author tags.
  14. #
  15. # Licensed under the Apache License, Version 2.0 (the "License");
  16. # you may not use this file except in compliance with the License.
  17. # You may obtain a copy of the License at
  18. #
  19. # http://www.apache.org/licenses/LICENSE-2.0
  20. #
  21. # Unless required by applicable law or agreed to in writing, software
  22. # distributed under the License is distributed on an "AS IS" BASIS,
  23. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24. # See the License for the specific language governing permissions and
  25. # limitations under the License.
  26. #
  27. # -*- -*- -*- Begin included fragment: lib/import.py -*- -*- -*-
  28. '''
  29. OpenShiftCLI class that wraps the oc commands in a subprocess
  30. '''
  31. # pylint: disable=too-many-lines
  32. from __future__ import print_function
  33. import atexit
  34. import copy
  35. import fcntl
  36. import json
  37. import time
  38. import os
  39. import re
  40. import shutil
  41. import subprocess
  42. import tempfile
  43. # pylint: disable=import-error
  44. try:
  45. import ruamel.yaml as yaml
  46. except ImportError:
  47. import yaml
  48. from ansible.module_utils.basic import AnsibleModule
  49. # -*- -*- -*- End included fragment: lib/import.py -*- -*- -*-
  50. # -*- -*- -*- Begin included fragment: doc/version -*- -*- -*-
  51. DOCUMENTATION = '''
  52. ---
  53. module: oc_version
  54. short_description: Return the current openshift version
  55. description:
  56. - Return the openshift installed version. `oc version`
  57. options:
  58. state:
  59. description:
  60. - State controls the action that will be taken with resource
  61. - Currently list is only supported state.
  62. required: true
  63. default: list
  64. choices: ["list"]
  65. aliases: []
  66. kubeconfig:
  67. description:
  68. - The path for the kubeconfig file to use for authentication
  69. required: false
  70. default: /etc/origin/master/admin.kubeconfig
  71. aliases: []
  72. debug:
  73. description:
  74. - Turn on debug output.
  75. required: false
  76. default: False
  77. aliases: []
  78. author:
  79. - "Kenny Woodson <kwoodson@redhat.com>"
  80. extends_documentation_fragment: []
  81. '''
  82. EXAMPLES = '''
  83. oc_version:
  84. - name: get oc version
  85. oc_version:
  86. register: oc_version
  87. '''
  88. # -*- -*- -*- End included fragment: doc/version -*- -*- -*-
  89. # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  90. class YeditException(Exception): # pragma: no cover
  91. ''' Exception class for Yedit '''
  92. pass
  93. # pylint: disable=too-many-public-methods,too-many-instance-attributes
  94. class Yedit(object): # pragma: no cover
  95. ''' Class to modify yaml files '''
  96. re_valid_key = r"(((\[-?\d+\])|([0-9a-zA-Z%s/_-]+)).?)+$"
  97. re_key = r"(?:\[(-?\d+)\])|([0-9a-zA-Z{}/_-]+)"
  98. com_sep = set(['.', '#', '|', ':'])
  99. # pylint: disable=too-many-arguments
  100. def __init__(self,
  101. filename=None,
  102. content=None,
  103. content_type='yaml',
  104. separator='.',
  105. backup_ext=None,
  106. backup=False):
  107. self.content = content
  108. self._separator = separator
  109. self.filename = filename
  110. self.__yaml_dict = content
  111. self.content_type = content_type
  112. self.backup = backup
  113. if backup_ext is None:
  114. self.backup_ext = ".{}".format(time.strftime("%Y%m%dT%H%M%S"))
  115. else:
  116. self.backup_ext = backup_ext
  117. self.load(content_type=self.content_type)
  118. if self.__yaml_dict is None:
  119. self.__yaml_dict = {}
  120. @property
  121. def separator(self):
  122. ''' getter method for separator '''
  123. return self._separator
  124. @separator.setter
  125. def separator(self, inc_sep):
  126. ''' setter method for separator '''
  127. self._separator = inc_sep
  128. @property
  129. def yaml_dict(self):
  130. ''' getter method for yaml_dict '''
  131. return self.__yaml_dict
  132. @yaml_dict.setter
  133. def yaml_dict(self, value):
  134. ''' setter method for yaml_dict '''
  135. self.__yaml_dict = value
  136. @staticmethod
  137. def parse_key(key, sep='.'):
  138. '''parse the key allowing the appropriate separator'''
  139. common_separators = list(Yedit.com_sep - set([sep]))
  140. return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
  141. @staticmethod
  142. def valid_key(key, sep='.'):
  143. '''validate the incoming key'''
  144. common_separators = list(Yedit.com_sep - set([sep]))
  145. if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
  146. return False
  147. return True
  148. # pylint: disable=too-many-return-statements,too-many-branches
  149. @staticmethod
  150. def remove_entry(data, key, index=None, value=None, sep='.'):
  151. ''' remove data at location key '''
  152. if key == '' and isinstance(data, dict):
  153. if value is not None:
  154. data.pop(value)
  155. elif index is not None:
  156. raise YeditException("remove_entry for a dictionary does not have an index {}".format(index))
  157. else:
  158. data.clear()
  159. return True
  160. elif key == '' and isinstance(data, list):
  161. ind = None
  162. if value is not None:
  163. try:
  164. ind = data.index(value)
  165. except ValueError:
  166. return False
  167. elif index is not None:
  168. ind = index
  169. else:
  170. del data[:]
  171. if ind is not None:
  172. data.pop(ind)
  173. return True
  174. if not (key and Yedit.valid_key(key, sep)) and \
  175. isinstance(data, (list, dict)):
  176. return None
  177. key_indexes = Yedit.parse_key(key, sep)
  178. for arr_ind, dict_key in key_indexes[:-1]:
  179. if dict_key and isinstance(data, dict):
  180. data = data.get(dict_key)
  181. elif (arr_ind and isinstance(data, list) and
  182. int(arr_ind) <= len(data) - 1):
  183. data = data[int(arr_ind)]
  184. else:
  185. return None
  186. # process last index for remove
  187. # expected list entry
  188. if key_indexes[-1][0]:
  189. if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  190. del data[int(key_indexes[-1][0])]
  191. return True
  192. # expected dict entry
  193. elif key_indexes[-1][1]:
  194. if isinstance(data, dict):
  195. del data[key_indexes[-1][1]]
  196. return True
  197. @staticmethod
  198. def add_entry(data, key, item=None, sep='.'):
  199. ''' Get an item from a dictionary with key notation a.b.c
  200. d = {'a': {'b': 'c'}}}
  201. key = a#b
  202. return c
  203. '''
  204. if key == '':
  205. pass
  206. elif (not (key and Yedit.valid_key(key, sep)) and
  207. isinstance(data, (list, dict))):
  208. return None
  209. key_indexes = Yedit.parse_key(key, sep)
  210. for arr_ind, dict_key in key_indexes[:-1]:
  211. if dict_key:
  212. if isinstance(data, dict) and dict_key in data and data[dict_key]: # noqa: E501
  213. data = data[dict_key]
  214. continue
  215. elif data and not isinstance(data, dict):
  216. raise YeditException("Unexpected item type found while going through key " +
  217. "path: {} (at key: {})".format(key, dict_key))
  218. data[dict_key] = {}
  219. data = data[dict_key]
  220. elif (arr_ind and isinstance(data, list) and
  221. int(arr_ind) <= len(data) - 1):
  222. data = data[int(arr_ind)]
  223. else:
  224. raise YeditException("Unexpected item type found while going through key path: {}".format(key))
  225. if key == '':
  226. data = item
  227. # process last index for add
  228. # expected list entry
  229. elif key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  230. data[int(key_indexes[-1][0])] = item
  231. # expected dict entry
  232. elif key_indexes[-1][1] and isinstance(data, dict):
  233. data[key_indexes[-1][1]] = item
  234. # didn't add/update to an existing list, nor add/update key to a dict
  235. # so we must have been provided some syntax like a.b.c[<int>] = "data" for a
  236. # non-existent array
  237. else:
  238. raise YeditException("Error adding to object at path: {}".format(key))
  239. return data
  240. @staticmethod
  241. def get_entry(data, key, sep='.'):
  242. ''' Get an item from a dictionary with key notation a.b.c
  243. d = {'a': {'b': 'c'}}}
  244. key = a.b
  245. return c
  246. '''
  247. if key == '':
  248. pass
  249. elif (not (key and Yedit.valid_key(key, sep)) and
  250. isinstance(data, (list, dict))):
  251. return None
  252. key_indexes = Yedit.parse_key(key, sep)
  253. for arr_ind, dict_key in key_indexes:
  254. if dict_key and isinstance(data, dict):
  255. data = data.get(dict_key)
  256. elif (arr_ind and isinstance(data, list) and
  257. int(arr_ind) <= len(data) - 1):
  258. data = data[int(arr_ind)]
  259. else:
  260. return None
  261. return data
  262. @staticmethod
  263. def _write(filename, contents):
  264. ''' Actually write the file contents to disk. This helps with mocking. '''
  265. tmp_filename = filename + '.yedit'
  266. with open(tmp_filename, 'w') as yfd:
  267. fcntl.flock(yfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
  268. yfd.write(contents)
  269. fcntl.flock(yfd, fcntl.LOCK_UN)
  270. os.rename(tmp_filename, filename)
  271. def write(self):
  272. ''' write to file '''
  273. if not self.filename:
  274. raise YeditException('Please specify a filename.')
  275. if self.backup and self.file_exists():
  276. shutil.copy(self.filename, '{}{}'.format(self.filename, self.backup_ext))
  277. # Try to set format attributes if supported
  278. try:
  279. self.yaml_dict.fa.set_block_style()
  280. except AttributeError:
  281. pass
  282. # Try to use RoundTripDumper if supported.
  283. if self.content_type == 'yaml':
  284. try:
  285. Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper))
  286. except AttributeError:
  287. Yedit._write(self.filename, yaml.safe_dump(self.yaml_dict, default_flow_style=False))
  288. elif self.content_type == 'json':
  289. Yedit._write(self.filename, json.dumps(self.yaml_dict, indent=4, sort_keys=True))
  290. else:
  291. raise YeditException('Unsupported content_type: {}.'.format(self.content_type) +
  292. 'Please specify a content_type of yaml or json.')
  293. return (True, self.yaml_dict)
  294. def read(self):
  295. ''' read from file '''
  296. # check if it exists
  297. if self.filename is None or not self.file_exists():
  298. return None
  299. contents = None
  300. with open(self.filename) as yfd:
  301. contents = yfd.read()
  302. return contents
  303. def file_exists(self):
  304. ''' return whether file exists '''
  305. if os.path.exists(self.filename):
  306. return True
  307. return False
  308. def load(self, content_type='yaml'):
  309. ''' return yaml file '''
  310. contents = self.read()
  311. if not contents and not self.content:
  312. return None
  313. if self.content:
  314. if isinstance(self.content, dict):
  315. self.yaml_dict = self.content
  316. return self.yaml_dict
  317. elif isinstance(self.content, str):
  318. contents = self.content
  319. # check if it is yaml
  320. try:
  321. if content_type == 'yaml' and contents:
  322. # Try to set format attributes if supported
  323. try:
  324. self.yaml_dict.fa.set_block_style()
  325. except AttributeError:
  326. pass
  327. # Try to use RoundTripLoader if supported.
  328. try:
  329. self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader)
  330. except AttributeError:
  331. self.yaml_dict = yaml.safe_load(contents)
  332. # Try to set format attributes if supported
  333. try:
  334. self.yaml_dict.fa.set_block_style()
  335. except AttributeError:
  336. pass
  337. elif content_type == 'json' and contents:
  338. self.yaml_dict = json.loads(contents)
  339. except yaml.YAMLError as err:
  340. # Error loading yaml or json
  341. raise YeditException('Problem with loading yaml file. {}'.format(err))
  342. return self.yaml_dict
  343. def get(self, key):
  344. ''' get a specified key'''
  345. try:
  346. entry = Yedit.get_entry(self.yaml_dict, key, self.separator)
  347. except KeyError:
  348. entry = None
  349. return entry
  350. def pop(self, path, key_or_item):
  351. ''' remove a key, value pair from a dict or an item for a list'''
  352. try:
  353. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  354. except KeyError:
  355. entry = None
  356. if entry is None:
  357. return (False, self.yaml_dict)
  358. if isinstance(entry, dict):
  359. # AUDIT:maybe-no-member makes sense due to fuzzy types
  360. # pylint: disable=maybe-no-member
  361. if key_or_item in entry:
  362. entry.pop(key_or_item)
  363. return (True, self.yaml_dict)
  364. return (False, self.yaml_dict)
  365. elif isinstance(entry, list):
  366. # AUDIT:maybe-no-member makes sense due to fuzzy types
  367. # pylint: disable=maybe-no-member
  368. ind = None
  369. try:
  370. ind = entry.index(key_or_item)
  371. except ValueError:
  372. return (False, self.yaml_dict)
  373. entry.pop(ind)
  374. return (True, self.yaml_dict)
  375. return (False, self.yaml_dict)
  376. def delete(self, path, index=None, value=None):
  377. ''' remove path from a dict'''
  378. try:
  379. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  380. except KeyError:
  381. entry = None
  382. if entry is None:
  383. return (False, self.yaml_dict)
  384. result = Yedit.remove_entry(self.yaml_dict, path, index, value, self.separator)
  385. if not result:
  386. return (False, self.yaml_dict)
  387. return (True, self.yaml_dict)
  388. def exists(self, path, value):
  389. ''' check if value exists at path'''
  390. try:
  391. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  392. except KeyError:
  393. entry = None
  394. if isinstance(entry, list):
  395. if value in entry:
  396. return True
  397. return False
  398. elif isinstance(entry, dict):
  399. if isinstance(value, dict):
  400. rval = False
  401. for key, val in value.items():
  402. if entry[key] != val:
  403. rval = False
  404. break
  405. else:
  406. rval = True
  407. return rval
  408. return value in entry
  409. return entry == value
  410. def append(self, path, value):
  411. '''append value to a list'''
  412. try:
  413. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  414. except KeyError:
  415. entry = None
  416. if entry is None:
  417. self.put(path, [])
  418. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  419. if not isinstance(entry, list):
  420. return (False, self.yaml_dict)
  421. # AUDIT:maybe-no-member makes sense due to loading data from
  422. # a serialized format.
  423. # pylint: disable=maybe-no-member
  424. entry.append(value)
  425. return (True, self.yaml_dict)
  426. # pylint: disable=too-many-arguments
  427. def update(self, path, value, index=None, curr_value=None):
  428. ''' put path, value into a dict '''
  429. try:
  430. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  431. except KeyError:
  432. entry = None
  433. if isinstance(entry, dict):
  434. # AUDIT:maybe-no-member makes sense due to fuzzy types
  435. # pylint: disable=maybe-no-member
  436. if not isinstance(value, dict):
  437. raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
  438. 'value=[{}] type=[{}]'.format(value, type(value)))
  439. entry.update(value)
  440. return (True, self.yaml_dict)
  441. elif isinstance(entry, list):
  442. # AUDIT:maybe-no-member makes sense due to fuzzy types
  443. # pylint: disable=maybe-no-member
  444. ind = None
  445. if curr_value:
  446. try:
  447. ind = entry.index(curr_value)
  448. except ValueError:
  449. return (False, self.yaml_dict)
  450. elif index is not None:
  451. ind = index
  452. if ind is not None and entry[ind] != value:
  453. entry[ind] = value
  454. return (True, self.yaml_dict)
  455. # see if it exists in the list
  456. try:
  457. ind = entry.index(value)
  458. except ValueError:
  459. # doesn't exist, append it
  460. entry.append(value)
  461. return (True, self.yaml_dict)
  462. # already exists, return
  463. if ind is not None:
  464. return (False, self.yaml_dict)
  465. return (False, self.yaml_dict)
  466. def put(self, path, value):
  467. ''' put path, value into a dict '''
  468. try:
  469. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  470. except KeyError:
  471. entry = None
  472. if entry == value:
  473. return (False, self.yaml_dict)
  474. # deepcopy didn't work
  475. # Try to use ruamel.yaml and fallback to pyyaml
  476. try:
  477. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  478. default_flow_style=False),
  479. yaml.RoundTripLoader)
  480. except AttributeError:
  481. tmp_copy = copy.deepcopy(self.yaml_dict)
  482. # set the format attributes if available
  483. try:
  484. tmp_copy.fa.set_block_style()
  485. except AttributeError:
  486. pass
  487. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  488. if result is None:
  489. return (False, self.yaml_dict)
  490. # When path equals "" it is a special case.
  491. # "" refers to the root of the document
  492. # Only update the root path (entire document) when its a list or dict
  493. if path == '':
  494. if isinstance(result, list) or isinstance(result, dict):
  495. self.yaml_dict = result
  496. return (True, self.yaml_dict)
  497. return (False, self.yaml_dict)
  498. self.yaml_dict = tmp_copy
  499. return (True, self.yaml_dict)
  500. def create(self, path, value):
  501. ''' create a yaml file '''
  502. if not self.file_exists():
  503. # deepcopy didn't work
  504. # Try to use ruamel.yaml and fallback to pyyaml
  505. try:
  506. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  507. default_flow_style=False),
  508. yaml.RoundTripLoader)
  509. except AttributeError:
  510. tmp_copy = copy.deepcopy(self.yaml_dict)
  511. # set the format attributes if available
  512. try:
  513. tmp_copy.fa.set_block_style()
  514. except AttributeError:
  515. pass
  516. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  517. if result is not None:
  518. self.yaml_dict = tmp_copy
  519. return (True, self.yaml_dict)
  520. return (False, self.yaml_dict)
  521. @staticmethod
  522. def get_curr_value(invalue, val_type):
  523. '''return the current value'''
  524. if invalue is None:
  525. return None
  526. curr_value = invalue
  527. if val_type == 'yaml':
  528. curr_value = yaml.safe_load(str(invalue))
  529. elif val_type == 'json':
  530. curr_value = json.loads(invalue)
  531. return curr_value
  532. @staticmethod
  533. def parse_value(inc_value, vtype=''):
  534. '''determine value type passed'''
  535. true_bools = ['y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE',
  536. 'on', 'On', 'ON', ]
  537. false_bools = ['n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE',
  538. 'off', 'Off', 'OFF']
  539. # It came in as a string but you didn't specify value_type as string
  540. # we will convert to bool if it matches any of the above cases
  541. if isinstance(inc_value, str) and 'bool' in vtype:
  542. if inc_value not in true_bools and inc_value not in false_bools:
  543. raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
  544. elif isinstance(inc_value, bool) and 'str' in vtype:
  545. inc_value = str(inc_value)
  546. # There is a special case where '' will turn into None after yaml loading it so skip
  547. if isinstance(inc_value, str) and inc_value == '':
  548. pass
  549. # If vtype is not str then go ahead and attempt to yaml load it.
  550. elif isinstance(inc_value, str) and 'str' not in vtype:
  551. try:
  552. inc_value = yaml.safe_load(inc_value)
  553. except Exception:
  554. raise YeditException('Could not determine type of incoming value. ' +
  555. 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
  556. return inc_value
  557. @staticmethod
  558. def process_edits(edits, yamlfile):
  559. '''run through a list of edits and process them one-by-one'''
  560. results = []
  561. for edit in edits:
  562. value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
  563. if edit.get('action') == 'update':
  564. # pylint: disable=line-too-long
  565. curr_value = Yedit.get_curr_value(
  566. Yedit.parse_value(edit.get('curr_value')),
  567. edit.get('curr_value_format'))
  568. rval = yamlfile.update(edit['key'],
  569. value,
  570. edit.get('index'),
  571. curr_value)
  572. elif edit.get('action') == 'append':
  573. rval = yamlfile.append(edit['key'], value)
  574. else:
  575. rval = yamlfile.put(edit['key'], value)
  576. if rval[0]:
  577. results.append({'key': edit['key'], 'edit': rval[1]})
  578. return {'changed': len(results) > 0, 'results': results}
  579. # pylint: disable=too-many-return-statements,too-many-branches
  580. @staticmethod
  581. def run_ansible(params):
  582. '''perform the idempotent crud operations'''
  583. yamlfile = Yedit(filename=params['src'],
  584. backup=params['backup'],
  585. content_type=params['content_type'],
  586. backup_ext=params['backup_ext'],
  587. separator=params['separator'])
  588. state = params['state']
  589. if params['src']:
  590. rval = yamlfile.load()
  591. if yamlfile.yaml_dict is None and state != 'present':
  592. return {'failed': True,
  593. 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) +
  594. 'file exists, that it is has correct permissions, and is valid yaml.'}
  595. if state == 'list':
  596. if params['content']:
  597. content = Yedit.parse_value(params['content'], params['content_type'])
  598. yamlfile.yaml_dict = content
  599. if params['key']:
  600. rval = yamlfile.get(params['key'])
  601. return {'changed': False, 'result': rval, 'state': state}
  602. elif state == 'absent':
  603. if params['content']:
  604. content = Yedit.parse_value(params['content'], params['content_type'])
  605. yamlfile.yaml_dict = content
  606. if params['update']:
  607. rval = yamlfile.pop(params['key'], params['value'])
  608. else:
  609. rval = yamlfile.delete(params['key'], params['index'], params['value'])
  610. if rval[0] and params['src']:
  611. yamlfile.write()
  612. return {'changed': rval[0], 'result': rval[1], 'state': state}
  613. elif state == 'present':
  614. # check if content is different than what is in the file
  615. if params['content']:
  616. content = Yedit.parse_value(params['content'], params['content_type'])
  617. # We had no edits to make and the contents are the same
  618. if yamlfile.yaml_dict == content and \
  619. params['value'] is None:
  620. return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
  621. yamlfile.yaml_dict = content
  622. # If we were passed a key, value then
  623. # we enapsulate it in a list and process it
  624. # Key, Value passed to the module : Converted to Edits list #
  625. edits = []
  626. _edit = {}
  627. if params['value'] is not None:
  628. _edit['value'] = params['value']
  629. _edit['value_type'] = params['value_type']
  630. _edit['key'] = params['key']
  631. if params['update']:
  632. _edit['action'] = 'update'
  633. _edit['curr_value'] = params['curr_value']
  634. _edit['curr_value_format'] = params['curr_value_format']
  635. _edit['index'] = params['index']
  636. elif params['append']:
  637. _edit['action'] = 'append'
  638. edits.append(_edit)
  639. elif params['edits'] is not None:
  640. edits = params['edits']
  641. if edits:
  642. results = Yedit.process_edits(edits, yamlfile)
  643. # if there were changes and a src provided to us we need to write
  644. if results['changed'] and params['src']:
  645. yamlfile.write()
  646. return {'changed': results['changed'], 'result': results['results'], 'state': state}
  647. # no edits to make
  648. if params['src']:
  649. # pylint: disable=redefined-variable-type
  650. rval = yamlfile.write()
  651. return {'changed': rval[0],
  652. 'result': rval[1],
  653. 'state': state}
  654. # We were passed content but no src, key or value, or edits. Return contents in memory
  655. return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
  656. return {'failed': True, 'msg': 'Unkown state passed'}
  657. # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  658. # -*- -*- -*- Begin included fragment: lib/base.py -*- -*- -*-
  659. # pylint: disable=too-many-lines
  660. # noqa: E301,E302,E303,T001
  661. class OpenShiftCLIError(Exception):
  662. '''Exception class for openshiftcli'''
  663. pass
  664. ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
  665. def locate_oc_binary():
  666. ''' Find and return oc binary file '''
  667. # https://github.com/openshift/openshift-ansible/issues/3410
  668. # oc can be in /usr/local/bin in some cases, but that may not
  669. # be in $PATH due to ansible/sudo
  670. paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
  671. oc_binary = 'oc'
  672. # Use shutil.which if it is available, otherwise fallback to a naive path search
  673. try:
  674. which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
  675. if which_result is not None:
  676. oc_binary = which_result
  677. except AttributeError:
  678. for path in paths:
  679. if os.path.exists(os.path.join(path, oc_binary)):
  680. oc_binary = os.path.join(path, oc_binary)
  681. break
  682. return oc_binary
  683. # pylint: disable=too-few-public-methods
  684. class OpenShiftCLI(object):
  685. ''' Class to wrap the command line tools '''
  686. def __init__(self,
  687. namespace,
  688. kubeconfig='/etc/origin/master/admin.kubeconfig',
  689. verbose=False,
  690. all_namespaces=False):
  691. ''' Constructor for OpenshiftCLI '''
  692. self.namespace = namespace
  693. self.verbose = verbose
  694. self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
  695. self.all_namespaces = all_namespaces
  696. self.oc_binary = locate_oc_binary()
  697. # Pylint allows only 5 arguments to be passed.
  698. # pylint: disable=too-many-arguments
  699. def _replace_content(self, resource, rname, content, edits=None, force=False, sep='.'):
  700. ''' replace the current object with the content '''
  701. res = self._get(resource, rname)
  702. if not res['results']:
  703. return res
  704. fname = Utils.create_tmpfile(rname + '-')
  705. yed = Yedit(fname, res['results'][0], separator=sep)
  706. updated = False
  707. if content is not None:
  708. changes = []
  709. for key, value in content.items():
  710. changes.append(yed.put(key, value))
  711. if any([change[0] for change in changes]):
  712. updated = True
  713. elif edits is not None:
  714. results = Yedit.process_edits(edits, yed)
  715. if results['changed']:
  716. updated = True
  717. if updated:
  718. yed.write()
  719. atexit.register(Utils.cleanup, [fname])
  720. return self._replace(fname, force)
  721. return {'returncode': 0, 'updated': False}
  722. def _replace(self, fname, force=False):
  723. '''replace the current object with oc replace'''
  724. # We are removing the 'resourceVersion' to handle
  725. # a race condition when modifying oc objects
  726. yed = Yedit(fname)
  727. results = yed.delete('metadata.resourceVersion')
  728. if results[0]:
  729. yed.write()
  730. cmd = ['replace', '-f', fname]
  731. if force:
  732. cmd.append('--force')
  733. return self.openshift_cmd(cmd)
  734. def _create_from_content(self, rname, content):
  735. '''create a temporary file and then call oc create on it'''
  736. fname = Utils.create_tmpfile(rname + '-')
  737. yed = Yedit(fname, content=content)
  738. yed.write()
  739. atexit.register(Utils.cleanup, [fname])
  740. return self._create(fname)
  741. def _create(self, fname):
  742. '''call oc create on a filename'''
  743. return self.openshift_cmd(['create', '-f', fname])
  744. def _delete(self, resource, name=None, selector=None):
  745. '''call oc delete on a resource'''
  746. cmd = ['delete', resource]
  747. if selector is not None:
  748. cmd.append('--selector={}'.format(selector))
  749. elif name is not None:
  750. cmd.append(name)
  751. else:
  752. raise OpenShiftCLIError('Either name or selector is required when calling delete.')
  753. return self.openshift_cmd(cmd)
  754. def _process(self, template_name, create=False, params=None, template_data=None): # noqa: E501
  755. '''process a template
  756. template_name: the name of the template to process
  757. create: whether to send to oc create after processing
  758. params: the parameters for the template
  759. template_data: the incoming template's data; instead of a file
  760. '''
  761. cmd = ['process']
  762. if template_data:
  763. cmd.extend(['-f', '-'])
  764. else:
  765. cmd.append(template_name)
  766. if params:
  767. param_str = ["{}={}".format(key, str(value).replace("'", r'"')) for key, value in params.items()]
  768. cmd.append('-p')
  769. cmd.extend(param_str)
  770. results = self.openshift_cmd(cmd, output=True, input_data=template_data)
  771. if results['returncode'] != 0 or not create:
  772. return results
  773. fname = Utils.create_tmpfile(template_name + '-')
  774. yed = Yedit(fname, results['results'])
  775. yed.write()
  776. atexit.register(Utils.cleanup, [fname])
  777. return self.openshift_cmd(['create', '-f', fname])
  778. def _get(self, resource, name=None, selector=None, field_selector=None):
  779. '''return a resource by name '''
  780. cmd = ['get', resource]
  781. if selector is not None:
  782. cmd.append('--selector={}'.format(selector))
  783. if field_selector is not None:
  784. cmd.append('--field-selector={}'.format(field_selector))
  785. # Name cannot be used with selector or field_selector.
  786. if selector is None and field_selector is None and name is not None:
  787. cmd.append(name)
  788. cmd.extend(['-o', 'json'])
  789. rval = self.openshift_cmd(cmd, output=True)
  790. # Ensure results are retuned in an array
  791. if 'items' in rval:
  792. rval['results'] = rval['items']
  793. elif not isinstance(rval['results'], list):
  794. rval['results'] = [rval['results']]
  795. return rval
  796. def _schedulable(self, node=None, selector=None, schedulable=True):
  797. ''' perform oadm manage-node scheduable '''
  798. cmd = ['manage-node']
  799. if node:
  800. cmd.extend(node)
  801. else:
  802. cmd.append('--selector={}'.format(selector))
  803. cmd.append('--schedulable={}'.format(schedulable))
  804. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') # noqa: E501
  805. def _list_pods(self, node=None, selector=None, pod_selector=None):
  806. ''' perform oadm list pods
  807. node: the node in which to list pods
  808. selector: the label selector filter if provided
  809. pod_selector: the pod selector filter if provided
  810. '''
  811. cmd = ['manage-node']
  812. if node:
  813. cmd.extend(node)
  814. else:
  815. cmd.append('--selector={}'.format(selector))
  816. if pod_selector:
  817. cmd.append('--pod-selector={}'.format(pod_selector))
  818. cmd.extend(['--list-pods', '-o', 'json'])
  819. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  820. # pylint: disable=too-many-arguments
  821. def _evacuate(self, node=None, selector=None, pod_selector=None, dry_run=False, grace_period=None, force=False):
  822. ''' perform oadm manage-node evacuate '''
  823. cmd = ['manage-node']
  824. if node:
  825. cmd.extend(node)
  826. else:
  827. cmd.append('--selector={}'.format(selector))
  828. if dry_run:
  829. cmd.append('--dry-run')
  830. if pod_selector:
  831. cmd.append('--pod-selector={}'.format(pod_selector))
  832. if grace_period:
  833. cmd.append('--grace-period={}'.format(int(grace_period)))
  834. if force:
  835. cmd.append('--force')
  836. cmd.append('--evacuate')
  837. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  838. def _version(self):
  839. ''' return the openshift version'''
  840. return self.openshift_cmd(['version'], output=True, output_type='raw')
  841. def _import_image(self, url=None, name=None, tag=None):
  842. ''' perform image import '''
  843. cmd = ['import-image']
  844. image = '{0}'.format(name)
  845. if tag:
  846. image += ':{0}'.format(tag)
  847. cmd.append(image)
  848. if url:
  849. cmd.append('--from={0}/{1}'.format(url, image))
  850. cmd.append('-n{0}'.format(self.namespace))
  851. cmd.append('--confirm')
  852. return self.openshift_cmd(cmd)
  853. def _run(self, cmds, input_data):
  854. ''' Actually executes the command. This makes mocking easier. '''
  855. curr_env = os.environ.copy()
  856. curr_env.update({'KUBECONFIG': self.kubeconfig})
  857. proc = subprocess.Popen(cmds,
  858. stdin=subprocess.PIPE,
  859. stdout=subprocess.PIPE,
  860. stderr=subprocess.PIPE,
  861. env=curr_env)
  862. stdout, stderr = proc.communicate(input_data)
  863. return proc.returncode, stdout.decode('utf-8'), stderr.decode('utf-8')
  864. # pylint: disable=too-many-arguments,too-many-branches
  865. def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
  866. '''Base command for oc '''
  867. cmds = [self.oc_binary]
  868. if oadm:
  869. cmds.append('adm')
  870. cmds.extend(cmd)
  871. if self.all_namespaces:
  872. cmds.extend(['--all-namespaces'])
  873. elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501
  874. cmds.extend(['-n', self.namespace])
  875. if self.verbose:
  876. print(' '.join(cmds))
  877. try:
  878. returncode, stdout, stderr = self._run(cmds, input_data)
  879. except OSError as ex:
  880. returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
  881. rval = {"returncode": returncode,
  882. "cmd": ' '.join(cmds)}
  883. if output_type == 'json':
  884. rval['results'] = {}
  885. if output and stdout:
  886. try:
  887. rval['results'] = json.loads(stdout)
  888. except ValueError as verr:
  889. if "No JSON object could be decoded" in verr.args:
  890. rval['err'] = verr.args
  891. elif output_type == 'raw':
  892. rval['results'] = stdout if output else ''
  893. if self.verbose:
  894. print("STDOUT: {0}".format(stdout))
  895. print("STDERR: {0}".format(stderr))
  896. if 'err' in rval or returncode != 0:
  897. rval.update({"stderr": stderr,
  898. "stdout": stdout})
  899. return rval
  900. class Utils(object): # pragma: no cover
  901. ''' utilities for openshiftcli modules '''
  902. @staticmethod
  903. def _write(filename, contents):
  904. ''' Actually write the file contents to disk. This helps with mocking. '''
  905. with open(filename, 'w') as sfd:
  906. sfd.write(str(contents))
  907. @staticmethod
  908. def create_tmp_file_from_contents(rname, data, ftype='yaml'):
  909. ''' create a file in tmp with name and contents'''
  910. tmp = Utils.create_tmpfile(prefix=rname)
  911. if ftype == 'yaml':
  912. # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage
  913. # pylint: disable=no-member
  914. if hasattr(yaml, 'RoundTripDumper'):
  915. Utils._write(tmp, yaml.dump(data, Dumper=yaml.RoundTripDumper))
  916. else:
  917. Utils._write(tmp, yaml.safe_dump(data, default_flow_style=False))
  918. elif ftype == 'json':
  919. Utils._write(tmp, json.dumps(data))
  920. else:
  921. Utils._write(tmp, data)
  922. # Register cleanup when module is done
  923. atexit.register(Utils.cleanup, [tmp])
  924. return tmp
  925. @staticmethod
  926. def create_tmpfile_copy(inc_file):
  927. '''create a temporary copy of a file'''
  928. tmpfile = Utils.create_tmpfile('lib_openshift-')
  929. Utils._write(tmpfile, open(inc_file).read())
  930. # Cleanup the tmpfile
  931. atexit.register(Utils.cleanup, [tmpfile])
  932. return tmpfile
  933. @staticmethod
  934. def create_tmpfile(prefix='tmp'):
  935. ''' Generates and returns a temporary file name '''
  936. with tempfile.NamedTemporaryFile(prefix=prefix, delete=False) as tmp:
  937. return tmp.name
  938. @staticmethod
  939. def create_tmp_files_from_contents(content, content_type=None):
  940. '''Turn an array of dict: filename, content into a files array'''
  941. if not isinstance(content, list):
  942. content = [content]
  943. files = []
  944. for item in content:
  945. path = Utils.create_tmp_file_from_contents(item['path'] + '-',
  946. item['data'],
  947. ftype=content_type)
  948. files.append({'name': os.path.basename(item['path']),
  949. 'path': path})
  950. return files
  951. @staticmethod
  952. def cleanup(files):
  953. '''Clean up on exit '''
  954. for sfile in files:
  955. if os.path.exists(sfile):
  956. if os.path.isdir(sfile):
  957. shutil.rmtree(sfile)
  958. elif os.path.isfile(sfile):
  959. os.remove(sfile)
  960. @staticmethod
  961. def exists(results, _name):
  962. ''' Check to see if the results include the name '''
  963. if not results:
  964. return False
  965. if Utils.find_result(results, _name):
  966. return True
  967. return False
  968. @staticmethod
  969. def find_result(results, _name):
  970. ''' Find the specified result by name'''
  971. rval = None
  972. for result in results:
  973. if 'metadata' in result and result['metadata']['name'] == _name:
  974. rval = result
  975. break
  976. return rval
  977. @staticmethod
  978. def get_resource_file(sfile, sfile_type='yaml'):
  979. ''' return the service file '''
  980. contents = None
  981. with open(sfile) as sfd:
  982. contents = sfd.read()
  983. if sfile_type == 'yaml':
  984. # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage
  985. # pylint: disable=no-member
  986. if hasattr(yaml, 'RoundTripLoader'):
  987. contents = yaml.load(contents, yaml.RoundTripLoader)
  988. else:
  989. contents = yaml.safe_load(contents)
  990. elif sfile_type == 'json':
  991. contents = json.loads(contents)
  992. return contents
  993. @staticmethod
  994. def filter_versions(stdout):
  995. ''' filter the oc version output '''
  996. version_dict = {}
  997. version_search = ['oc', 'openshift', 'kubernetes']
  998. for line in stdout.strip().split('\n'):
  999. for term in version_search:
  1000. if not line:
  1001. continue
  1002. if line.startswith(term):
  1003. version_dict[term] = line.split()[-1]
  1004. # horrible hack to get openshift version in Openshift 3.2
  1005. # By default "oc version in 3.2 does not return an "openshift" version
  1006. if "openshift" not in version_dict:
  1007. version_dict["openshift"] = version_dict["oc"]
  1008. return version_dict
  1009. @staticmethod
  1010. def add_custom_versions(versions):
  1011. ''' create custom versions strings '''
  1012. versions_dict = {}
  1013. for tech, version in versions.items():
  1014. # clean up "-" from version
  1015. if "-" in version:
  1016. version = version.split("-")[0]
  1017. if version.startswith('v'):
  1018. version = version[1:] # Remove the 'v' prefix
  1019. versions_dict[tech + '_numeric'] = version.split('+')[0]
  1020. # "3.3.0.33" is what we have, we want "3.3"
  1021. versions_dict[tech + '_short'] = "{}.{}".format(*version.split('.'))
  1022. return versions_dict
  1023. @staticmethod
  1024. def openshift_installed():
  1025. ''' check if openshift is installed '''
  1026. import rpm
  1027. transaction_set = rpm.TransactionSet()
  1028. rpmquery = transaction_set.dbMatch("name", "atomic-openshift")
  1029. return rpmquery.count() > 0
  1030. # Disabling too-many-branches. This is a yaml dictionary comparison function
  1031. # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
  1032. @staticmethod
  1033. def check_def_equal(user_def, result_def, skip_keys=None, debug=False):
  1034. ''' Given a user defined definition, compare it with the results given back by our query. '''
  1035. if user_def is None:
  1036. user_def = {}
  1037. # Currently these values are autogenerated and we do not need to check them
  1038. skip = ['metadata', 'status']
  1039. if skip_keys:
  1040. skip.extend(skip_keys)
  1041. for key, value in result_def.items():
  1042. if key in skip:
  1043. continue
  1044. # Both are lists
  1045. if isinstance(value, list):
  1046. if key not in user_def:
  1047. if debug:
  1048. print('User data does not have key [%s]' % key)
  1049. print('User data: %s' % user_def)
  1050. return False
  1051. if not isinstance(user_def[key], list):
  1052. if debug:
  1053. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  1054. return False
  1055. if len(user_def[key]) != len(value):
  1056. if debug:
  1057. print("List lengths are not equal.")
  1058. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  1059. print("user_def: %s" % user_def[key])
  1060. print("value: %s" % value)
  1061. return False
  1062. for values in zip(user_def[key], value):
  1063. if isinstance(values[0], dict) and isinstance(values[1], dict):
  1064. if debug:
  1065. print('sending list - list')
  1066. print(type(values[0]))
  1067. print(type(values[1]))
  1068. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  1069. if not result:
  1070. print('list compare returned false')
  1071. return False
  1072. elif value != user_def[key]:
  1073. if debug:
  1074. print('value should be identical')
  1075. print(user_def[key])
  1076. print(value)
  1077. return False
  1078. # recurse on a dictionary
  1079. elif isinstance(value, dict):
  1080. if key not in user_def:
  1081. if debug:
  1082. print("user_def does not have key [%s]" % key)
  1083. return False
  1084. if not isinstance(user_def[key], dict):
  1085. if debug:
  1086. print("dict returned false: not instance of dict")
  1087. return False
  1088. # before passing ensure keys match
  1089. api_values = set(value.keys()) - set(skip)
  1090. user_values = set(user_def[key].keys()) - set(skip)
  1091. if api_values != user_values:
  1092. if debug:
  1093. print("keys are not equal in dict")
  1094. print(user_values)
  1095. print(api_values)
  1096. return False
  1097. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  1098. if not result:
  1099. if debug:
  1100. print("dict returned false")
  1101. print(result)
  1102. return False
  1103. # Verify each key, value pair is the same
  1104. else:
  1105. if key not in user_def or value != user_def[key]:
  1106. if debug:
  1107. print("value not equal; user_def does not have key")
  1108. print(key)
  1109. print(value)
  1110. if key in user_def:
  1111. print(user_def[key])
  1112. return False
  1113. if debug:
  1114. print('returning true')
  1115. return True
  1116. class OpenShiftCLIConfig(object):
  1117. '''Generic Config'''
  1118. def __init__(self, rname, namespace, kubeconfig, options):
  1119. self.kubeconfig = kubeconfig
  1120. self.name = rname
  1121. self.namespace = namespace
  1122. self._options = options
  1123. @property
  1124. def config_options(self):
  1125. ''' return config options '''
  1126. return self._options
  1127. def to_option_list(self, ascommalist=''):
  1128. '''return all options as a string
  1129. if ascommalist is set to the name of a key, and
  1130. the value of that key is a dict, format the dict
  1131. as a list of comma delimited key=value pairs'''
  1132. return self.stringify(ascommalist)
  1133. def stringify(self, ascommalist=''):
  1134. ''' return the options hash as cli params in a string
  1135. if ascommalist is set to the name of a key, and
  1136. the value of that key is a dict, format the dict
  1137. as a list of comma delimited key=value pairs '''
  1138. rval = []
  1139. for key in sorted(self.config_options.keys()):
  1140. data = self.config_options[key]
  1141. if data['include'] \
  1142. and (data['value'] is not None or isinstance(data['value'], int)):
  1143. if key == ascommalist:
  1144. val = ','.join(['{}={}'.format(kk, vv) for kk, vv in sorted(data['value'].items())])
  1145. else:
  1146. val = data['value']
  1147. rval.append('--{}={}'.format(key.replace('_', '-'), val))
  1148. return rval
  1149. # -*- -*- -*- End included fragment: lib/base.py -*- -*- -*-
  1150. # -*- -*- -*- Begin included fragment: class/oc_version.py -*- -*- -*-
  1151. # pylint: disable=too-many-instance-attributes
  1152. class OCVersion(OpenShiftCLI):
  1153. ''' Class to wrap the oc command line tools '''
  1154. # pylint allows 5
  1155. # pylint: disable=too-many-arguments
  1156. def __init__(self,
  1157. config,
  1158. debug):
  1159. ''' Constructor for OCVersion '''
  1160. super(OCVersion, self).__init__(None, config)
  1161. self.debug = debug
  1162. def get(self):
  1163. '''get and return version information '''
  1164. results = {}
  1165. version_results = self._version()
  1166. if version_results['returncode'] == 0:
  1167. filtered_vers = Utils.filter_versions(version_results['results'])
  1168. custom_vers = Utils.add_custom_versions(filtered_vers)
  1169. results['returncode'] = version_results['returncode']
  1170. results.update(filtered_vers)
  1171. results.update(custom_vers)
  1172. return results
  1173. raise OpenShiftCLIError('Problem detecting openshift version.')
  1174. @staticmethod
  1175. def run_ansible(params):
  1176. '''run the oc_version module'''
  1177. oc_version = OCVersion(params['kubeconfig'], params['debug'])
  1178. if params['state'] == 'list':
  1179. #pylint: disable=protected-access
  1180. result = oc_version.get()
  1181. return {'state': params['state'],
  1182. 'results': result,
  1183. 'changed': False}
  1184. # -*- -*- -*- End included fragment: class/oc_version.py -*- -*- -*-
  1185. # -*- -*- -*- Begin included fragment: ansible/oc_version.py -*- -*- -*-
  1186. def main():
  1187. ''' ansible oc module for version '''
  1188. module = AnsibleModule(
  1189. argument_spec=dict(
  1190. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  1191. state=dict(default='list', type='str',
  1192. choices=['list']),
  1193. debug=dict(default=False, type='bool'),
  1194. ),
  1195. supports_check_mode=True,
  1196. )
  1197. rval = OCVersion.run_ansible(module.params)
  1198. if 'failed' in rval:
  1199. module.fail_json(**rval)
  1200. module.exit_json(**rval)
  1201. if __name__ == '__main__':
  1202. main()
  1203. # -*- -*- -*- End included fragment: ansible/oc_version.py -*- -*- -*-