oc_adm_policy_group.py 72 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234
  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/policy_group -*- -*- -*-
  51. DOCUMENTATION = '''
  52. ---
  53. module: oc_adm_policy_group
  54. short_description: Module to manage openshift policy for groups
  55. description:
  56. - Manage openshift policy for groups.
  57. options:
  58. kubeconfig:
  59. description:
  60. - The path for the kubeconfig file to use for authentication
  61. required: false
  62. default: /etc/origin/master/admin.kubeconfig
  63. aliases: []
  64. namespace:
  65. description:
  66. - The namespace scope
  67. required: false
  68. default: None
  69. aliases: []
  70. debug:
  71. description:
  72. - Turn on debug output.
  73. required: false
  74. default: False
  75. aliases: []
  76. group:
  77. description:
  78. - The name of the group
  79. required: true
  80. default: None
  81. aliases: []
  82. resource_kind:
  83. description:
  84. - The kind of policy to affect
  85. required: true
  86. default: None
  87. choices: ["role", "cluster-role", "scc"]
  88. aliases: []
  89. resource_name:
  90. description:
  91. - The name of the policy
  92. required: true
  93. default: None
  94. aliases: []
  95. state:
  96. description:
  97. - Desired state of the policy
  98. required: true
  99. default: present
  100. choices: ["present", "absent"]
  101. aliases: []
  102. author:
  103. - "Kenny Woodson <kwoodson@redhat.com>"
  104. extends_documentation_fragment: []
  105. '''
  106. EXAMPLES = '''
  107. - name: oc adm policy remove-scc-from-group an-scc agroup
  108. oc_adm_policy_group:
  109. group: agroup
  110. resource_kind: scc
  111. resource_name: an-scc
  112. state: absent
  113. - name: oc adm policy add-cluster-role-to-group system:build-strategy-docker agroup
  114. oc_adm_policy_group:
  115. group: agroup
  116. resource_kind: cluster-role
  117. resource_name: system:build-strategy-docker
  118. state: present
  119. '''
  120. # -*- -*- -*- End included fragment: doc/policy_group -*- -*- -*-
  121. # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  122. class YeditException(Exception): # pragma: no cover
  123. ''' Exception class for Yedit '''
  124. pass
  125. # pylint: disable=too-many-public-methods,too-many-instance-attributes
  126. class Yedit(object): # pragma: no cover
  127. ''' Class to modify yaml files '''
  128. re_valid_key = r"(((\[-?\d+\])|([0-9a-zA-Z%s/_-]+)).?)+$"
  129. re_key = r"(?:\[(-?\d+)\])|([0-9a-zA-Z{}/_-]+)"
  130. com_sep = set(['.', '#', '|', ':'])
  131. # pylint: disable=too-many-arguments
  132. def __init__(self,
  133. filename=None,
  134. content=None,
  135. content_type='yaml',
  136. separator='.',
  137. backup_ext=None,
  138. backup=False):
  139. self.content = content
  140. self._separator = separator
  141. self.filename = filename
  142. self.__yaml_dict = content
  143. self.content_type = content_type
  144. self.backup = backup
  145. if backup_ext is None:
  146. self.backup_ext = ".{}".format(time.strftime("%Y%m%dT%H%M%S"))
  147. else:
  148. self.backup_ext = backup_ext
  149. self.load(content_type=self.content_type)
  150. if self.__yaml_dict is None:
  151. self.__yaml_dict = {}
  152. @property
  153. def separator(self):
  154. ''' getter method for separator '''
  155. return self._separator
  156. @separator.setter
  157. def separator(self, inc_sep):
  158. ''' setter method for separator '''
  159. self._separator = inc_sep
  160. @property
  161. def yaml_dict(self):
  162. ''' getter method for yaml_dict '''
  163. return self.__yaml_dict
  164. @yaml_dict.setter
  165. def yaml_dict(self, value):
  166. ''' setter method for yaml_dict '''
  167. self.__yaml_dict = value
  168. @staticmethod
  169. def parse_key(key, sep='.'):
  170. '''parse the key allowing the appropriate separator'''
  171. common_separators = list(Yedit.com_sep - set([sep]))
  172. return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
  173. @staticmethod
  174. def valid_key(key, sep='.'):
  175. '''validate the incoming key'''
  176. common_separators = list(Yedit.com_sep - set([sep]))
  177. if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
  178. return False
  179. return True
  180. # pylint: disable=too-many-return-statements,too-many-branches
  181. @staticmethod
  182. def remove_entry(data, key, index=None, value=None, sep='.'):
  183. ''' remove data at location key '''
  184. if key == '' and isinstance(data, dict):
  185. if value is not None:
  186. data.pop(value)
  187. elif index is not None:
  188. raise YeditException("remove_entry for a dictionary does not have an index {}".format(index))
  189. else:
  190. data.clear()
  191. return True
  192. elif key == '' and isinstance(data, list):
  193. ind = None
  194. if value is not None:
  195. try:
  196. ind = data.index(value)
  197. except ValueError:
  198. return False
  199. elif index is not None:
  200. ind = index
  201. else:
  202. del data[:]
  203. if ind is not None:
  204. data.pop(ind)
  205. return True
  206. if 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 and isinstance(data, dict):
  212. data = data.get(dict_key)
  213. elif (arr_ind and isinstance(data, list) and
  214. int(arr_ind) <= len(data) - 1):
  215. data = data[int(arr_ind)]
  216. else:
  217. return None
  218. # process last index for remove
  219. # expected list entry
  220. if key_indexes[-1][0]:
  221. if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  222. del data[int(key_indexes[-1][0])]
  223. return True
  224. # expected dict entry
  225. elif key_indexes[-1][1]:
  226. if isinstance(data, dict):
  227. del data[key_indexes[-1][1]]
  228. return True
  229. @staticmethod
  230. def add_entry(data, key, item=None, sep='.'):
  231. ''' Get an item from a dictionary with key notation a.b.c
  232. d = {'a': {'b': 'c'}}}
  233. key = a#b
  234. return c
  235. '''
  236. if key == '':
  237. pass
  238. elif (not (key and Yedit.valid_key(key, sep)) and
  239. isinstance(data, (list, dict))):
  240. return None
  241. key_indexes = Yedit.parse_key(key, sep)
  242. for arr_ind, dict_key in key_indexes[:-1]:
  243. if dict_key:
  244. if isinstance(data, dict) and dict_key in data and data[dict_key]: # noqa: E501
  245. data = data[dict_key]
  246. continue
  247. elif data and not isinstance(data, dict):
  248. raise YeditException("Unexpected item type found while going through key " +
  249. "path: {} (at key: {})".format(key, dict_key))
  250. data[dict_key] = {}
  251. data = data[dict_key]
  252. elif (arr_ind and isinstance(data, list) and
  253. int(arr_ind) <= len(data) - 1):
  254. data = data[int(arr_ind)]
  255. else:
  256. raise YeditException("Unexpected item type found while going through key path: {}".format(key))
  257. if key == '':
  258. data = item
  259. # process last index for add
  260. # expected list entry
  261. elif key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  262. data[int(key_indexes[-1][0])] = item
  263. # expected dict entry
  264. elif key_indexes[-1][1] and isinstance(data, dict):
  265. data[key_indexes[-1][1]] = item
  266. # didn't add/update to an existing list, nor add/update key to a dict
  267. # so we must have been provided some syntax like a.b.c[<int>] = "data" for a
  268. # non-existent array
  269. else:
  270. raise YeditException("Error adding to object at path: {}".format(key))
  271. return data
  272. @staticmethod
  273. def get_entry(data, key, sep='.'):
  274. ''' Get an item from a dictionary with key notation a.b.c
  275. d = {'a': {'b': 'c'}}}
  276. key = a.b
  277. return c
  278. '''
  279. if key == '':
  280. pass
  281. elif (not (key and Yedit.valid_key(key, sep)) and
  282. isinstance(data, (list, dict))):
  283. return None
  284. key_indexes = Yedit.parse_key(key, sep)
  285. for arr_ind, dict_key in key_indexes:
  286. if dict_key and isinstance(data, dict):
  287. data = data.get(dict_key)
  288. elif (arr_ind and isinstance(data, list) and
  289. int(arr_ind) <= len(data) - 1):
  290. data = data[int(arr_ind)]
  291. else:
  292. return None
  293. return data
  294. @staticmethod
  295. def _write(filename, contents):
  296. ''' Actually write the file contents to disk. This helps with mocking. '''
  297. tmp_filename = filename + '.yedit'
  298. with open(tmp_filename, 'w') as yfd:
  299. fcntl.flock(yfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
  300. yfd.write(contents)
  301. fcntl.flock(yfd, fcntl.LOCK_UN)
  302. os.rename(tmp_filename, filename)
  303. def write(self):
  304. ''' write to file '''
  305. if not self.filename:
  306. raise YeditException('Please specify a filename.')
  307. if self.backup and self.file_exists():
  308. shutil.copy(self.filename, '{}{}'.format(self.filename, self.backup_ext))
  309. # Try to set format attributes if supported
  310. try:
  311. self.yaml_dict.fa.set_block_style()
  312. except AttributeError:
  313. pass
  314. # Try to use RoundTripDumper if supported.
  315. if self.content_type == 'yaml':
  316. try:
  317. Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper))
  318. except AttributeError:
  319. Yedit._write(self.filename, yaml.safe_dump(self.yaml_dict, default_flow_style=False))
  320. elif self.content_type == 'json':
  321. Yedit._write(self.filename, json.dumps(self.yaml_dict, indent=4, sort_keys=True))
  322. else:
  323. raise YeditException('Unsupported content_type: {}.'.format(self.content_type) +
  324. 'Please specify a content_type of yaml or json.')
  325. return (True, self.yaml_dict)
  326. def read(self):
  327. ''' read from file '''
  328. # check if it exists
  329. if self.filename is None or not self.file_exists():
  330. return None
  331. contents = None
  332. with open(self.filename) as yfd:
  333. contents = yfd.read()
  334. return contents
  335. def file_exists(self):
  336. ''' return whether file exists '''
  337. if os.path.exists(self.filename):
  338. return True
  339. return False
  340. def load(self, content_type='yaml'):
  341. ''' return yaml file '''
  342. contents = self.read()
  343. if not contents and not self.content:
  344. return None
  345. if self.content:
  346. if isinstance(self.content, dict):
  347. self.yaml_dict = self.content
  348. return self.yaml_dict
  349. elif isinstance(self.content, str):
  350. contents = self.content
  351. # check if it is yaml
  352. try:
  353. if content_type == 'yaml' and contents:
  354. # Try to set format attributes if supported
  355. try:
  356. self.yaml_dict.fa.set_block_style()
  357. except AttributeError:
  358. pass
  359. # Try to use RoundTripLoader if supported.
  360. try:
  361. self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader)
  362. except AttributeError:
  363. self.yaml_dict = yaml.safe_load(contents)
  364. # Try to set format attributes if supported
  365. try:
  366. self.yaml_dict.fa.set_block_style()
  367. except AttributeError:
  368. pass
  369. elif content_type == 'json' and contents:
  370. self.yaml_dict = json.loads(contents)
  371. except yaml.YAMLError as err:
  372. # Error loading yaml or json
  373. raise YeditException('Problem with loading yaml file. {}'.format(err))
  374. return self.yaml_dict
  375. def get(self, key):
  376. ''' get a specified key'''
  377. try:
  378. entry = Yedit.get_entry(self.yaml_dict, key, self.separator)
  379. except KeyError:
  380. entry = None
  381. return entry
  382. def pop(self, path, key_or_item):
  383. ''' remove a key, value pair from a dict or an item for a list'''
  384. try:
  385. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  386. except KeyError:
  387. entry = None
  388. if entry is None:
  389. return (False, self.yaml_dict)
  390. if isinstance(entry, dict):
  391. # AUDIT:maybe-no-member makes sense due to fuzzy types
  392. # pylint: disable=maybe-no-member
  393. if key_or_item in entry:
  394. entry.pop(key_or_item)
  395. return (True, self.yaml_dict)
  396. return (False, self.yaml_dict)
  397. elif isinstance(entry, list):
  398. # AUDIT:maybe-no-member makes sense due to fuzzy types
  399. # pylint: disable=maybe-no-member
  400. ind = None
  401. try:
  402. ind = entry.index(key_or_item)
  403. except ValueError:
  404. return (False, self.yaml_dict)
  405. entry.pop(ind)
  406. return (True, self.yaml_dict)
  407. return (False, self.yaml_dict)
  408. def delete(self, path, index=None, value=None):
  409. ''' remove path from a dict'''
  410. try:
  411. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  412. except KeyError:
  413. entry = None
  414. if entry is None:
  415. return (False, self.yaml_dict)
  416. result = Yedit.remove_entry(self.yaml_dict, path, index, value, self.separator)
  417. if not result:
  418. return (False, self.yaml_dict)
  419. return (True, self.yaml_dict)
  420. def exists(self, path, value):
  421. ''' check if value exists at path'''
  422. try:
  423. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  424. except KeyError:
  425. entry = None
  426. if isinstance(entry, list):
  427. if value in entry:
  428. return True
  429. return False
  430. elif isinstance(entry, dict):
  431. if isinstance(value, dict):
  432. rval = False
  433. for key, val in value.items():
  434. if entry[key] != val:
  435. rval = False
  436. break
  437. else:
  438. rval = True
  439. return rval
  440. return value in entry
  441. return entry == value
  442. def append(self, path, value):
  443. '''append value to a list'''
  444. try:
  445. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  446. except KeyError:
  447. entry = None
  448. if entry is None:
  449. self.put(path, [])
  450. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  451. if not isinstance(entry, list):
  452. return (False, self.yaml_dict)
  453. # AUDIT:maybe-no-member makes sense due to loading data from
  454. # a serialized format.
  455. # pylint: disable=maybe-no-member
  456. entry.append(value)
  457. return (True, self.yaml_dict)
  458. # pylint: disable=too-many-arguments
  459. def update(self, path, value, index=None, curr_value=None):
  460. ''' put path, value into a dict '''
  461. try:
  462. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  463. except KeyError:
  464. entry = None
  465. if isinstance(entry, dict):
  466. # AUDIT:maybe-no-member makes sense due to fuzzy types
  467. # pylint: disable=maybe-no-member
  468. if not isinstance(value, dict):
  469. raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
  470. 'value=[{}] type=[{}]'.format(value, type(value)))
  471. entry.update(value)
  472. return (True, self.yaml_dict)
  473. elif isinstance(entry, list):
  474. # AUDIT:maybe-no-member makes sense due to fuzzy types
  475. # pylint: disable=maybe-no-member
  476. ind = None
  477. if curr_value:
  478. try:
  479. ind = entry.index(curr_value)
  480. except ValueError:
  481. return (False, self.yaml_dict)
  482. elif index is not None:
  483. ind = index
  484. if ind is not None and entry[ind] != value:
  485. entry[ind] = value
  486. return (True, self.yaml_dict)
  487. # see if it exists in the list
  488. try:
  489. ind = entry.index(value)
  490. except ValueError:
  491. # doesn't exist, append it
  492. entry.append(value)
  493. return (True, self.yaml_dict)
  494. # already exists, return
  495. if ind is not None:
  496. return (False, self.yaml_dict)
  497. return (False, self.yaml_dict)
  498. def put(self, path, value):
  499. ''' put path, value into a dict '''
  500. try:
  501. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  502. except KeyError:
  503. entry = None
  504. if entry == value:
  505. return (False, self.yaml_dict)
  506. # deepcopy didn't work
  507. # Try to use ruamel.yaml and fallback to pyyaml
  508. try:
  509. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  510. default_flow_style=False),
  511. yaml.RoundTripLoader)
  512. except AttributeError:
  513. tmp_copy = copy.deepcopy(self.yaml_dict)
  514. # set the format attributes if available
  515. try:
  516. tmp_copy.fa.set_block_style()
  517. except AttributeError:
  518. pass
  519. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  520. if result is None:
  521. return (False, self.yaml_dict)
  522. # When path equals "" it is a special case.
  523. # "" refers to the root of the document
  524. # Only update the root path (entire document) when its a list or dict
  525. if path == '':
  526. if isinstance(result, list) or isinstance(result, dict):
  527. self.yaml_dict = result
  528. return (True, self.yaml_dict)
  529. return (False, self.yaml_dict)
  530. self.yaml_dict = tmp_copy
  531. return (True, self.yaml_dict)
  532. def create(self, path, value):
  533. ''' create a yaml file '''
  534. if not self.file_exists():
  535. # deepcopy didn't work
  536. # Try to use ruamel.yaml and fallback to pyyaml
  537. try:
  538. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  539. default_flow_style=False),
  540. yaml.RoundTripLoader)
  541. except AttributeError:
  542. tmp_copy = copy.deepcopy(self.yaml_dict)
  543. # set the format attributes if available
  544. try:
  545. tmp_copy.fa.set_block_style()
  546. except AttributeError:
  547. pass
  548. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  549. if result is not None:
  550. self.yaml_dict = tmp_copy
  551. return (True, self.yaml_dict)
  552. return (False, self.yaml_dict)
  553. @staticmethod
  554. def get_curr_value(invalue, val_type):
  555. '''return the current value'''
  556. if invalue is None:
  557. return None
  558. curr_value = invalue
  559. if val_type == 'yaml':
  560. curr_value = yaml.safe_load(str(invalue))
  561. elif val_type == 'json':
  562. curr_value = json.loads(invalue)
  563. return curr_value
  564. @staticmethod
  565. def parse_value(inc_value, vtype=''):
  566. '''determine value type passed'''
  567. true_bools = ['y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE',
  568. 'on', 'On', 'ON', ]
  569. false_bools = ['n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE',
  570. 'off', 'Off', 'OFF']
  571. # It came in as a string but you didn't specify value_type as string
  572. # we will convert to bool if it matches any of the above cases
  573. if isinstance(inc_value, str) and 'bool' in vtype:
  574. if inc_value not in true_bools and inc_value not in false_bools:
  575. raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
  576. elif isinstance(inc_value, bool) and 'str' in vtype:
  577. inc_value = str(inc_value)
  578. # There is a special case where '' will turn into None after yaml loading it so skip
  579. if isinstance(inc_value, str) and inc_value == '':
  580. pass
  581. # If vtype is not str then go ahead and attempt to yaml load it.
  582. elif isinstance(inc_value, str) and 'str' not in vtype:
  583. try:
  584. inc_value = yaml.safe_load(inc_value)
  585. except Exception:
  586. raise YeditException('Could not determine type of incoming value. ' +
  587. 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
  588. return inc_value
  589. @staticmethod
  590. def process_edits(edits, yamlfile):
  591. '''run through a list of edits and process them one-by-one'''
  592. results = []
  593. for edit in edits:
  594. value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
  595. if edit.get('action') == 'update':
  596. # pylint: disable=line-too-long
  597. curr_value = Yedit.get_curr_value(
  598. Yedit.parse_value(edit.get('curr_value')),
  599. edit.get('curr_value_format'))
  600. rval = yamlfile.update(edit['key'],
  601. value,
  602. edit.get('index'),
  603. curr_value)
  604. elif edit.get('action') == 'append':
  605. rval = yamlfile.append(edit['key'], value)
  606. else:
  607. rval = yamlfile.put(edit['key'], value)
  608. if rval[0]:
  609. results.append({'key': edit['key'], 'edit': rval[1]})
  610. return {'changed': len(results) > 0, 'results': results}
  611. # pylint: disable=too-many-return-statements,too-many-branches
  612. @staticmethod
  613. def run_ansible(params):
  614. '''perform the idempotent crud operations'''
  615. yamlfile = Yedit(filename=params['src'],
  616. backup=params['backup'],
  617. content_type=params['content_type'],
  618. backup_ext=params['backup_ext'],
  619. separator=params['separator'])
  620. state = params['state']
  621. if params['src']:
  622. rval = yamlfile.load()
  623. if yamlfile.yaml_dict is None and state != 'present':
  624. return {'failed': True,
  625. 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) +
  626. 'file exists, that it is has correct permissions, and is valid yaml.'}
  627. if state == 'list':
  628. if params['content']:
  629. content = Yedit.parse_value(params['content'], params['content_type'])
  630. yamlfile.yaml_dict = content
  631. if params['key']:
  632. rval = yamlfile.get(params['key'])
  633. return {'changed': False, 'result': rval, 'state': state}
  634. elif state == 'absent':
  635. if params['content']:
  636. content = Yedit.parse_value(params['content'], params['content_type'])
  637. yamlfile.yaml_dict = content
  638. if params['update']:
  639. rval = yamlfile.pop(params['key'], params['value'])
  640. else:
  641. rval = yamlfile.delete(params['key'], params['index'], params['value'])
  642. if rval[0] and params['src']:
  643. yamlfile.write()
  644. return {'changed': rval[0], 'result': rval[1], 'state': state}
  645. elif state == 'present':
  646. # check if content is different than what is in the file
  647. if params['content']:
  648. content = Yedit.parse_value(params['content'], params['content_type'])
  649. # We had no edits to make and the contents are the same
  650. if yamlfile.yaml_dict == content and \
  651. params['value'] is None:
  652. return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
  653. yamlfile.yaml_dict = content
  654. # If we were passed a key, value then
  655. # we enapsulate it in a list and process it
  656. # Key, Value passed to the module : Converted to Edits list #
  657. edits = []
  658. _edit = {}
  659. if params['value'] is not None:
  660. _edit['value'] = params['value']
  661. _edit['value_type'] = params['value_type']
  662. _edit['key'] = params['key']
  663. if params['update']:
  664. _edit['action'] = 'update'
  665. _edit['curr_value'] = params['curr_value']
  666. _edit['curr_value_format'] = params['curr_value_format']
  667. _edit['index'] = params['index']
  668. elif params['append']:
  669. _edit['action'] = 'append'
  670. edits.append(_edit)
  671. elif params['edits'] is not None:
  672. edits = params['edits']
  673. if edits:
  674. results = Yedit.process_edits(edits, yamlfile)
  675. # if there were changes and a src provided to us we need to write
  676. if results['changed'] and params['src']:
  677. yamlfile.write()
  678. return {'changed': results['changed'], 'result': results['results'], 'state': state}
  679. # no edits to make
  680. if params['src']:
  681. # pylint: disable=redefined-variable-type
  682. rval = yamlfile.write()
  683. return {'changed': rval[0],
  684. 'result': rval[1],
  685. 'state': state}
  686. # We were passed content but no src, key or value, or edits. Return contents in memory
  687. return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
  688. return {'failed': True, 'msg': 'Unkown state passed'}
  689. # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  690. # -*- -*- -*- Begin included fragment: lib/base.py -*- -*- -*-
  691. # pylint: disable=too-many-lines
  692. # noqa: E301,E302,E303,T001
  693. class OpenShiftCLIError(Exception):
  694. '''Exception class for openshiftcli'''
  695. pass
  696. ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
  697. def locate_oc_binary():
  698. ''' Find and return oc binary file '''
  699. # https://github.com/openshift/openshift-ansible/issues/3410
  700. # oc can be in /usr/local/bin in some cases, but that may not
  701. # be in $PATH due to ansible/sudo
  702. paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
  703. oc_binary = 'oc'
  704. # Use shutil.which if it is available, otherwise fallback to a naive path search
  705. try:
  706. which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
  707. if which_result is not None:
  708. oc_binary = which_result
  709. except AttributeError:
  710. for path in paths:
  711. if os.path.exists(os.path.join(path, oc_binary)):
  712. oc_binary = os.path.join(path, oc_binary)
  713. break
  714. return oc_binary
  715. # pylint: disable=too-few-public-methods
  716. class OpenShiftCLI(object):
  717. ''' Class to wrap the command line tools '''
  718. def __init__(self,
  719. namespace,
  720. kubeconfig='/etc/origin/master/admin.kubeconfig',
  721. verbose=False,
  722. all_namespaces=False):
  723. ''' Constructor for OpenshiftCLI '''
  724. self.namespace = namespace
  725. self.verbose = verbose
  726. self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
  727. self.all_namespaces = all_namespaces
  728. self.oc_binary = locate_oc_binary()
  729. # Pylint allows only 5 arguments to be passed.
  730. # pylint: disable=too-many-arguments
  731. def _replace_content(self, resource, rname, content, edits=None, force=False, sep='.'):
  732. ''' replace the current object with the content '''
  733. res = self._get(resource, rname)
  734. if not res['results']:
  735. return res
  736. fname = Utils.create_tmpfile(rname + '-')
  737. yed = Yedit(fname, res['results'][0], separator=sep)
  738. updated = False
  739. if content is not None:
  740. changes = []
  741. for key, value in content.items():
  742. changes.append(yed.put(key, value))
  743. if any([change[0] for change in changes]):
  744. updated = True
  745. elif edits is not None:
  746. results = Yedit.process_edits(edits, yed)
  747. if results['changed']:
  748. updated = True
  749. if updated:
  750. yed.write()
  751. atexit.register(Utils.cleanup, [fname])
  752. return self._replace(fname, force)
  753. return {'returncode': 0, 'updated': False}
  754. def _replace(self, fname, force=False):
  755. '''replace the current object with oc replace'''
  756. # We are removing the 'resourceVersion' to handle
  757. # a race condition when modifying oc objects
  758. yed = Yedit(fname)
  759. results = yed.delete('metadata.resourceVersion')
  760. if results[0]:
  761. yed.write()
  762. cmd = ['replace', '-f', fname]
  763. if force:
  764. cmd.append('--force')
  765. return self.openshift_cmd(cmd)
  766. def _create_from_content(self, rname, content):
  767. '''create a temporary file and then call oc create on it'''
  768. fname = Utils.create_tmpfile(rname + '-')
  769. yed = Yedit(fname, content=content)
  770. yed.write()
  771. atexit.register(Utils.cleanup, [fname])
  772. return self._create(fname)
  773. def _create(self, fname):
  774. '''call oc create on a filename'''
  775. return self.openshift_cmd(['create', '-f', fname])
  776. def _delete(self, resource, name=None, selector=None):
  777. '''call oc delete on a resource'''
  778. cmd = ['delete', resource]
  779. if selector is not None:
  780. cmd.append('--selector={}'.format(selector))
  781. elif name is not None:
  782. cmd.append(name)
  783. else:
  784. raise OpenShiftCLIError('Either name or selector is required when calling delete.')
  785. return self.openshift_cmd(cmd)
  786. def _process(self, template_name, create=False, params=None, template_data=None): # noqa: E501
  787. '''process a template
  788. template_name: the name of the template to process
  789. create: whether to send to oc create after processing
  790. params: the parameters for the template
  791. template_data: the incoming template's data; instead of a file
  792. '''
  793. cmd = ['process']
  794. if template_data:
  795. cmd.extend(['-f', '-'])
  796. else:
  797. cmd.append(template_name)
  798. if params:
  799. param_str = ["{}={}".format(key, str(value).replace("'", r'"')) for key, value in params.items()]
  800. cmd.append('-p')
  801. cmd.extend(param_str)
  802. results = self.openshift_cmd(cmd, output=True, input_data=template_data)
  803. if results['returncode'] != 0 or not create:
  804. return results
  805. fname = Utils.create_tmpfile(template_name + '-')
  806. yed = Yedit(fname, results['results'])
  807. yed.write()
  808. atexit.register(Utils.cleanup, [fname])
  809. return self.openshift_cmd(['create', '-f', fname])
  810. def _get(self, resource, name=None, selector=None, field_selector=None):
  811. '''return a resource by name '''
  812. cmd = ['get', resource]
  813. if selector is not None:
  814. cmd.append('--selector={}'.format(selector))
  815. if field_selector is not None:
  816. cmd.append('--field-selector={}'.format(field_selector))
  817. # Name cannot be used with selector or field_selector.
  818. if selector is None and field_selector is None and name is not None:
  819. cmd.append(name)
  820. cmd.extend(['-o', 'json'])
  821. rval = self.openshift_cmd(cmd, output=True)
  822. # Ensure results are retuned in an array
  823. if 'items' in rval:
  824. rval['results'] = rval['items']
  825. elif not isinstance(rval['results'], list):
  826. rval['results'] = [rval['results']]
  827. return rval
  828. def _schedulable(self, node=None, selector=None, schedulable=True):
  829. ''' perform oadm manage-node scheduable '''
  830. cmd = ['manage-node']
  831. if node:
  832. cmd.extend(node)
  833. else:
  834. cmd.append('--selector={}'.format(selector))
  835. cmd.append('--schedulable={}'.format(schedulable))
  836. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') # noqa: E501
  837. def _list_pods(self, node=None, selector=None, pod_selector=None):
  838. ''' perform oadm list pods
  839. node: the node in which to list pods
  840. selector: the label selector filter if provided
  841. pod_selector: the pod selector filter if provided
  842. '''
  843. cmd = ['manage-node']
  844. if node:
  845. cmd.extend(node)
  846. else:
  847. cmd.append('--selector={}'.format(selector))
  848. if pod_selector:
  849. cmd.append('--pod-selector={}'.format(pod_selector))
  850. cmd.extend(['--list-pods', '-o', 'json'])
  851. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  852. # pylint: disable=too-many-arguments
  853. def _evacuate(self, node=None, selector=None, pod_selector=None, dry_run=False, grace_period=None, force=False):
  854. ''' perform oadm manage-node evacuate '''
  855. cmd = ['manage-node']
  856. if node:
  857. cmd.extend(node)
  858. else:
  859. cmd.append('--selector={}'.format(selector))
  860. if dry_run:
  861. cmd.append('--dry-run')
  862. if pod_selector:
  863. cmd.append('--pod-selector={}'.format(pod_selector))
  864. if grace_period:
  865. cmd.append('--grace-period={}'.format(int(grace_period)))
  866. if force:
  867. cmd.append('--force')
  868. cmd.append('--evacuate')
  869. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  870. def _version(self):
  871. ''' return the openshift version'''
  872. return self.openshift_cmd(['version'], output=True, output_type='raw')
  873. def _import_image(self, url=None, name=None, tag=None):
  874. ''' perform image import '''
  875. cmd = ['import-image']
  876. image = '{0}'.format(name)
  877. if tag:
  878. image += ':{0}'.format(tag)
  879. cmd.append(image)
  880. if url:
  881. cmd.append('--from={0}/{1}'.format(url, image))
  882. cmd.append('-n{0}'.format(self.namespace))
  883. cmd.append('--confirm')
  884. return self.openshift_cmd(cmd)
  885. def _run(self, cmds, input_data):
  886. ''' Actually executes the command. This makes mocking easier. '''
  887. curr_env = os.environ.copy()
  888. curr_env.update({'KUBECONFIG': self.kubeconfig})
  889. proc = subprocess.Popen(cmds,
  890. stdin=subprocess.PIPE,
  891. stdout=subprocess.PIPE,
  892. stderr=subprocess.PIPE,
  893. env=curr_env)
  894. stdout, stderr = proc.communicate(input_data)
  895. return proc.returncode, stdout.decode('utf-8'), stderr.decode('utf-8')
  896. # pylint: disable=too-many-arguments,too-many-branches
  897. def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
  898. '''Base command for oc '''
  899. cmds = [self.oc_binary]
  900. if oadm:
  901. cmds.append('adm')
  902. cmds.extend(cmd)
  903. if self.all_namespaces:
  904. cmds.extend(['--all-namespaces'])
  905. elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501
  906. cmds.extend(['-n', self.namespace])
  907. if self.verbose:
  908. print(' '.join(cmds))
  909. try:
  910. returncode, stdout, stderr = self._run(cmds, input_data)
  911. except OSError as ex:
  912. returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
  913. rval = {"returncode": returncode,
  914. "cmd": ' '.join(cmds)}
  915. if output_type == 'json':
  916. rval['results'] = {}
  917. if output and stdout:
  918. try:
  919. rval['results'] = json.loads(stdout)
  920. except ValueError as verr:
  921. if "No JSON object could be decoded" in verr.args:
  922. rval['err'] = verr.args
  923. elif output_type == 'raw':
  924. rval['results'] = stdout if output else ''
  925. if self.verbose:
  926. print("STDOUT: {0}".format(stdout))
  927. print("STDERR: {0}".format(stderr))
  928. if 'err' in rval or returncode != 0:
  929. rval.update({"stderr": stderr,
  930. "stdout": stdout})
  931. return rval
  932. class Utils(object): # pragma: no cover
  933. ''' utilities for openshiftcli modules '''
  934. @staticmethod
  935. def _write(filename, contents):
  936. ''' Actually write the file contents to disk. This helps with mocking. '''
  937. with open(filename, 'w') as sfd:
  938. sfd.write(str(contents))
  939. @staticmethod
  940. def create_tmp_file_from_contents(rname, data, ftype='yaml'):
  941. ''' create a file in tmp with name and contents'''
  942. tmp = Utils.create_tmpfile(prefix=rname)
  943. if ftype == 'yaml':
  944. # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage
  945. # pylint: disable=no-member
  946. if hasattr(yaml, 'RoundTripDumper'):
  947. Utils._write(tmp, yaml.dump(data, Dumper=yaml.RoundTripDumper))
  948. else:
  949. Utils._write(tmp, yaml.safe_dump(data, default_flow_style=False))
  950. elif ftype == 'json':
  951. Utils._write(tmp, json.dumps(data))
  952. else:
  953. Utils._write(tmp, data)
  954. # Register cleanup when module is done
  955. atexit.register(Utils.cleanup, [tmp])
  956. return tmp
  957. @staticmethod
  958. def create_tmpfile_copy(inc_file):
  959. '''create a temporary copy of a file'''
  960. tmpfile = Utils.create_tmpfile('lib_openshift-')
  961. Utils._write(tmpfile, open(inc_file).read())
  962. # Cleanup the tmpfile
  963. atexit.register(Utils.cleanup, [tmpfile])
  964. return tmpfile
  965. @staticmethod
  966. def create_tmpfile(prefix='tmp'):
  967. ''' Generates and returns a temporary file name '''
  968. with tempfile.NamedTemporaryFile(prefix=prefix, delete=False) as tmp:
  969. return tmp.name
  970. @staticmethod
  971. def create_tmp_files_from_contents(content, content_type=None):
  972. '''Turn an array of dict: filename, content into a files array'''
  973. if not isinstance(content, list):
  974. content = [content]
  975. files = []
  976. for item in content:
  977. path = Utils.create_tmp_file_from_contents(item['path'] + '-',
  978. item['data'],
  979. ftype=content_type)
  980. files.append({'name': os.path.basename(item['path']),
  981. 'path': path})
  982. return files
  983. @staticmethod
  984. def cleanup(files):
  985. '''Clean up on exit '''
  986. for sfile in files:
  987. if os.path.exists(sfile):
  988. if os.path.isdir(sfile):
  989. shutil.rmtree(sfile)
  990. elif os.path.isfile(sfile):
  991. os.remove(sfile)
  992. @staticmethod
  993. def exists(results, _name):
  994. ''' Check to see if the results include the name '''
  995. if not results:
  996. return False
  997. if Utils.find_result(results, _name):
  998. return True
  999. return False
  1000. @staticmethod
  1001. def find_result(results, _name):
  1002. ''' Find the specified result by name'''
  1003. rval = None
  1004. for result in results:
  1005. if 'metadata' in result and result['metadata']['name'] == _name:
  1006. rval = result
  1007. break
  1008. return rval
  1009. @staticmethod
  1010. def get_resource_file(sfile, sfile_type='yaml'):
  1011. ''' return the service file '''
  1012. contents = None
  1013. with open(sfile) as sfd:
  1014. contents = sfd.read()
  1015. if sfile_type == 'yaml':
  1016. # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage
  1017. # pylint: disable=no-member
  1018. if hasattr(yaml, 'RoundTripLoader'):
  1019. contents = yaml.load(contents, yaml.RoundTripLoader)
  1020. else:
  1021. contents = yaml.safe_load(contents)
  1022. elif sfile_type == 'json':
  1023. contents = json.loads(contents)
  1024. return contents
  1025. @staticmethod
  1026. def filter_versions(stdout):
  1027. ''' filter the oc version output '''
  1028. version_dict = {}
  1029. version_search = ['oc', 'openshift', 'kubernetes']
  1030. for line in stdout.strip().split('\n'):
  1031. for term in version_search:
  1032. if not line:
  1033. continue
  1034. if line.startswith(term):
  1035. version_dict[term] = line.split()[-1]
  1036. # horrible hack to get openshift version in Openshift 3.2
  1037. # By default "oc version in 3.2 does not return an "openshift" version
  1038. if "openshift" not in version_dict:
  1039. version_dict["openshift"] = version_dict["oc"]
  1040. return version_dict
  1041. @staticmethod
  1042. def add_custom_versions(versions):
  1043. ''' create custom versions strings '''
  1044. versions_dict = {}
  1045. for tech, version in versions.items():
  1046. # clean up "-" from version
  1047. if "-" in version:
  1048. version = version.split("-")[0]
  1049. if version.startswith('v'):
  1050. version = version[1:] # Remove the 'v' prefix
  1051. versions_dict[tech + '_numeric'] = version.split('+')[0]
  1052. # "3.3.0.33" is what we have, we want "3.3"
  1053. versions_dict[tech + '_short'] = "{}.{}".format(*version.split('.'))
  1054. return versions_dict
  1055. @staticmethod
  1056. def openshift_installed():
  1057. ''' check if openshift is installed '''
  1058. import rpm
  1059. transaction_set = rpm.TransactionSet()
  1060. rpmquery = transaction_set.dbMatch("name", "atomic-openshift")
  1061. return rpmquery.count() > 0
  1062. # Disabling too-many-branches. This is a yaml dictionary comparison function
  1063. # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
  1064. @staticmethod
  1065. def check_def_equal(user_def, result_def, skip_keys=None, debug=False):
  1066. ''' Given a user defined definition, compare it with the results given back by our query. '''
  1067. # Currently these values are autogenerated and we do not need to check them
  1068. skip = ['metadata', 'status']
  1069. if skip_keys:
  1070. skip.extend(skip_keys)
  1071. for key, value in result_def.items():
  1072. if key in skip:
  1073. continue
  1074. # Both are lists
  1075. if isinstance(value, list):
  1076. if key not in user_def:
  1077. if debug:
  1078. print('User data does not have key [%s]' % key)
  1079. print('User data: %s' % user_def)
  1080. return False
  1081. if not isinstance(user_def[key], list):
  1082. if debug:
  1083. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  1084. return False
  1085. if len(user_def[key]) != len(value):
  1086. if debug:
  1087. print("List lengths are not equal.")
  1088. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  1089. print("user_def: %s" % user_def[key])
  1090. print("value: %s" % value)
  1091. return False
  1092. for values in zip(user_def[key], value):
  1093. if isinstance(values[0], dict) and isinstance(values[1], dict):
  1094. if debug:
  1095. print('sending list - list')
  1096. print(type(values[0]))
  1097. print(type(values[1]))
  1098. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  1099. if not result:
  1100. print('list compare returned false')
  1101. return False
  1102. elif value != user_def[key]:
  1103. if debug:
  1104. print('value should be identical')
  1105. print(user_def[key])
  1106. print(value)
  1107. return False
  1108. # recurse on a dictionary
  1109. elif isinstance(value, dict):
  1110. if key not in user_def:
  1111. if debug:
  1112. print("user_def does not have key [%s]" % key)
  1113. return False
  1114. if not isinstance(user_def[key], dict):
  1115. if debug:
  1116. print("dict returned false: not instance of dict")
  1117. return False
  1118. # before passing ensure keys match
  1119. api_values = set(value.keys()) - set(skip)
  1120. user_values = set(user_def[key].keys()) - set(skip)
  1121. if api_values != user_values:
  1122. if debug:
  1123. print("keys are not equal in dict")
  1124. print(user_values)
  1125. print(api_values)
  1126. return False
  1127. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  1128. if not result:
  1129. if debug:
  1130. print("dict returned false")
  1131. print(result)
  1132. return False
  1133. # Verify each key, value pair is the same
  1134. else:
  1135. if key not in user_def or value != user_def[key]:
  1136. if debug:
  1137. print("value not equal; user_def does not have key")
  1138. print(key)
  1139. print(value)
  1140. if key in user_def:
  1141. print(user_def[key])
  1142. return False
  1143. if debug:
  1144. print('returning true')
  1145. return True
  1146. class OpenShiftCLIConfig(object):
  1147. '''Generic Config'''
  1148. def __init__(self, rname, namespace, kubeconfig, options):
  1149. self.kubeconfig = kubeconfig
  1150. self.name = rname
  1151. self.namespace = namespace
  1152. self._options = options
  1153. @property
  1154. def config_options(self):
  1155. ''' return config options '''
  1156. return self._options
  1157. def to_option_list(self, ascommalist=''):
  1158. '''return all options as a string
  1159. if ascommalist is set to the name of a key, and
  1160. the value of that key is a dict, format the dict
  1161. as a list of comma delimited key=value pairs'''
  1162. return self.stringify(ascommalist)
  1163. def stringify(self, ascommalist=''):
  1164. ''' return the options hash as cli params in a string
  1165. if ascommalist is set to the name of a key, and
  1166. the value of that key is a dict, format the dict
  1167. as a list of comma delimited key=value pairs '''
  1168. rval = []
  1169. for key in sorted(self.config_options.keys()):
  1170. data = self.config_options[key]
  1171. if data['include'] \
  1172. and (data['value'] is not None or isinstance(data['value'], int)):
  1173. if key == ascommalist:
  1174. val = ','.join(['{}={}'.format(kk, vv) for kk, vv in sorted(data['value'].items())])
  1175. else:
  1176. val = data['value']
  1177. rval.append('--{}={}'.format(key.replace('_', '-'), val))
  1178. return rval
  1179. # -*- -*- -*- End included fragment: lib/base.py -*- -*- -*-
  1180. # -*- -*- -*- Begin included fragment: lib/rolebinding.py -*- -*- -*-
  1181. # pylint: disable=too-many-instance-attributes
  1182. class RoleBindingConfig(object):
  1183. ''' Handle rolebinding config '''
  1184. # pylint: disable=too-many-arguments
  1185. def __init__(self,
  1186. name,
  1187. namespace,
  1188. kubeconfig,
  1189. group_names=None,
  1190. role_ref=None,
  1191. subjects=None,
  1192. usernames=None):
  1193. ''' constructor for handling rolebinding options '''
  1194. self.kubeconfig = kubeconfig
  1195. self.name = name
  1196. self.namespace = namespace
  1197. self.group_names = group_names
  1198. self.role_ref = role_ref
  1199. self.subjects = subjects
  1200. self.usernames = usernames
  1201. self.data = {}
  1202. self.create_dict()
  1203. def create_dict(self):
  1204. ''' create a default rolebinding as a dict '''
  1205. self.data['apiVersion'] = 'v1'
  1206. self.data['kind'] = 'RoleBinding'
  1207. self.data['groupNames'] = self.group_names
  1208. self.data['metadata']['name'] = self.name
  1209. self.data['metadata']['namespace'] = self.namespace
  1210. self.data['roleRef'] = self.role_ref
  1211. self.data['subjects'] = self.subjects
  1212. self.data['userNames'] = self.usernames
  1213. # pylint: disable=too-many-instance-attributes,too-many-public-methods
  1214. class RoleBinding(Yedit):
  1215. ''' Class to model a rolebinding openshift object'''
  1216. group_names_path = "groupNames"
  1217. role_ref_path = "roleRef"
  1218. subjects_path = "subjects"
  1219. user_names_path = "userNames"
  1220. kind = 'RoleBinding'
  1221. def __init__(self, content):
  1222. '''RoleBinding constructor'''
  1223. super(RoleBinding, self).__init__(content=content)
  1224. self._subjects = None
  1225. self._role_ref = None
  1226. self._group_names = None
  1227. self._user_names = None
  1228. @property
  1229. def subjects(self):
  1230. ''' subjects property '''
  1231. if self._subjects is None:
  1232. self._subjects = self.get_subjects()
  1233. return self._subjects
  1234. @subjects.setter
  1235. def subjects(self, data):
  1236. ''' subjects property setter'''
  1237. self._subjects = data
  1238. @property
  1239. def role_ref(self):
  1240. ''' role_ref property '''
  1241. if self._role_ref is None:
  1242. self._role_ref = self.get_role_ref()
  1243. return self._role_ref
  1244. @role_ref.setter
  1245. def role_ref(self, data):
  1246. ''' role_ref property setter'''
  1247. self._role_ref = data
  1248. @property
  1249. def group_names(self):
  1250. ''' group_names property '''
  1251. if self._group_names is None:
  1252. self._group_names = self.get_group_names()
  1253. return self._group_names
  1254. @group_names.setter
  1255. def group_names(self, data):
  1256. ''' group_names property setter'''
  1257. self._group_names = data
  1258. @property
  1259. def user_names(self):
  1260. ''' user_names property '''
  1261. if self._user_names is None:
  1262. self._user_names = self.get_user_names()
  1263. return self._user_names
  1264. @user_names.setter
  1265. def user_names(self, data):
  1266. ''' user_names property setter'''
  1267. self._user_names = data
  1268. def get_group_names(self):
  1269. ''' return groupNames '''
  1270. return self.get(RoleBinding.group_names_path) or []
  1271. def get_user_names(self):
  1272. ''' return usernames '''
  1273. return self.get(RoleBinding.user_names_path) or []
  1274. def get_role_ref(self):
  1275. ''' return role_ref '''
  1276. return self.get(RoleBinding.role_ref_path) or {}
  1277. def get_subjects(self):
  1278. ''' return subjects '''
  1279. return self.get(RoleBinding.subjects_path) or []
  1280. #### ADD #####
  1281. def add_subject(self, inc_subject):
  1282. ''' add a subject '''
  1283. if self.subjects:
  1284. # pylint: disable=no-member
  1285. self.subjects.append(inc_subject)
  1286. else:
  1287. self.put(RoleBinding.subjects_path, [inc_subject])
  1288. return True
  1289. def add_role_ref(self, inc_role_ref):
  1290. ''' add a role_ref '''
  1291. if not self.role_ref:
  1292. self.put(RoleBinding.role_ref_path, {"name": inc_role_ref})
  1293. return True
  1294. return False
  1295. def add_group_names(self, inc_group_names):
  1296. ''' add a group_names '''
  1297. if self.group_names:
  1298. # pylint: disable=no-member
  1299. self.group_names.append(inc_group_names)
  1300. else:
  1301. self.put(RoleBinding.group_names_path, [inc_group_names])
  1302. return True
  1303. def add_user_name(self, inc_user_name):
  1304. ''' add a username '''
  1305. if self.user_names:
  1306. # pylint: disable=no-member
  1307. self.user_names.append(inc_user_name)
  1308. else:
  1309. self.put(RoleBinding.user_names_path, [inc_user_name])
  1310. return True
  1311. #### /ADD #####
  1312. #### Remove #####
  1313. def remove_subject(self, inc_subject):
  1314. ''' remove a subject '''
  1315. try:
  1316. # pylint: disable=no-member
  1317. self.subjects.remove(inc_subject)
  1318. except ValueError as _:
  1319. return False
  1320. return True
  1321. def remove_role_ref(self, inc_role_ref):
  1322. ''' remove a role_ref '''
  1323. if self.role_ref and self.role_ref['name'] == inc_role_ref:
  1324. del self.role_ref['name']
  1325. return True
  1326. return False
  1327. def remove_group_name(self, inc_group_name):
  1328. ''' remove a groupname '''
  1329. try:
  1330. # pylint: disable=no-member
  1331. self.group_names.remove(inc_group_name)
  1332. except ValueError as _:
  1333. return False
  1334. return True
  1335. def remove_user_name(self, inc_user_name):
  1336. ''' remove a username '''
  1337. try:
  1338. # pylint: disable=no-member
  1339. self.user_names.remove(inc_user_name)
  1340. except ValueError as _:
  1341. return False
  1342. return True
  1343. #### /REMOVE #####
  1344. #### UPDATE #####
  1345. def update_subject(self, inc_subject):
  1346. ''' update a subject '''
  1347. try:
  1348. # pylint: disable=no-member
  1349. index = self.subjects.index(inc_subject)
  1350. except ValueError as _:
  1351. return self.add_subject(inc_subject)
  1352. self.subjects[index] = inc_subject
  1353. return True
  1354. def update_group_name(self, inc_group_name):
  1355. ''' update a groupname '''
  1356. try:
  1357. # pylint: disable=no-member
  1358. index = self.group_names.index(inc_group_name)
  1359. except ValueError as _:
  1360. return self.add_group_names(inc_group_name)
  1361. self.group_names[index] = inc_group_name
  1362. return True
  1363. def update_user_name(self, inc_user_name):
  1364. ''' update a username '''
  1365. try:
  1366. # pylint: disable=no-member
  1367. index = self.user_names.index(inc_user_name)
  1368. except ValueError as _:
  1369. return self.add_user_name(inc_user_name)
  1370. self.user_names[index] = inc_user_name
  1371. return True
  1372. def update_role_ref(self, inc_role_ref):
  1373. ''' update a role_ref '''
  1374. self.role_ref['name'] = inc_role_ref
  1375. return True
  1376. #### /UPDATE #####
  1377. #### FIND ####
  1378. def find_subject(self, inc_subject):
  1379. ''' find a subject '''
  1380. index = None
  1381. try:
  1382. # pylint: disable=no-member
  1383. index = self.subjects.index(inc_subject)
  1384. except ValueError as _:
  1385. return index
  1386. return index
  1387. def find_group_name(self, inc_group_name):
  1388. ''' find a group_name '''
  1389. index = None
  1390. try:
  1391. # pylint: disable=no-member
  1392. index = self.group_names.index(inc_group_name)
  1393. except ValueError as _:
  1394. return index
  1395. return index
  1396. def find_user_name(self, inc_user_name):
  1397. ''' find a user_name '''
  1398. index = None
  1399. try:
  1400. # pylint: disable=no-member
  1401. index = self.user_names.index(inc_user_name)
  1402. except ValueError as _:
  1403. return index
  1404. return index
  1405. def find_role_ref(self, inc_role_ref):
  1406. ''' find a user_name '''
  1407. if self.role_ref and self.role_ref['name'] == inc_role_ref['name']:
  1408. return self.role_ref
  1409. return None
  1410. # -*- -*- -*- End included fragment: lib/rolebinding.py -*- -*- -*-
  1411. # -*- -*- -*- Begin included fragment: lib/scc.py -*- -*- -*-
  1412. # pylint: disable=too-many-instance-attributes
  1413. class SecurityContextConstraintsConfig(object):
  1414. ''' Handle scc options '''
  1415. # pylint: disable=too-many-arguments
  1416. def __init__(self,
  1417. sname,
  1418. kubeconfig,
  1419. options=None,
  1420. fs_group='MustRunAs',
  1421. default_add_capabilities=None,
  1422. groups=None,
  1423. priority=None,
  1424. required_drop_capabilities=None,
  1425. run_as_user='MustRunAsRange',
  1426. se_linux_context='MustRunAs',
  1427. supplemental_groups='RunAsAny',
  1428. users=None,
  1429. annotations=None):
  1430. ''' constructor for handling scc options '''
  1431. self.kubeconfig = kubeconfig
  1432. self.name = sname
  1433. self.options = options
  1434. self.fs_group = fs_group
  1435. self.default_add_capabilities = default_add_capabilities
  1436. self.groups = groups
  1437. self.priority = priority
  1438. self.required_drop_capabilities = required_drop_capabilities
  1439. self.run_as_user = run_as_user
  1440. self.se_linux_context = se_linux_context
  1441. self.supplemental_groups = supplemental_groups
  1442. self.users = users
  1443. self.annotations = annotations
  1444. self.data = {}
  1445. self.create_dict()
  1446. def create_dict(self):
  1447. ''' assign the correct properties for a scc dict '''
  1448. # allow options
  1449. if self.options:
  1450. for key, value in self.options.items():
  1451. self.data[key] = value
  1452. else:
  1453. self.data['allowHostDirVolumePlugin'] = False
  1454. self.data['allowHostIPC'] = False
  1455. self.data['allowHostNetwork'] = False
  1456. self.data['allowHostPID'] = False
  1457. self.data['allowHostPorts'] = False
  1458. self.data['allowPrivilegedContainer'] = False
  1459. self.data['allowedCapabilities'] = None
  1460. # version
  1461. self.data['apiVersion'] = 'v1'
  1462. # kind
  1463. self.data['kind'] = 'SecurityContextConstraints'
  1464. # defaultAddCapabilities
  1465. self.data['defaultAddCapabilities'] = self.default_add_capabilities
  1466. # fsGroup
  1467. self.data['fsGroup']['type'] = self.fs_group
  1468. # groups
  1469. self.data['groups'] = []
  1470. if self.groups:
  1471. self.data['groups'] = self.groups
  1472. # metadata
  1473. self.data['metadata'] = {}
  1474. self.data['metadata']['name'] = self.name
  1475. if self.annotations:
  1476. for key, value in self.annotations.items():
  1477. self.data['metadata'][key] = value
  1478. # priority
  1479. self.data['priority'] = self.priority
  1480. # requiredDropCapabilities
  1481. self.data['requiredDropCapabilities'] = self.required_drop_capabilities
  1482. # runAsUser
  1483. self.data['runAsUser'] = {'type': self.run_as_user}
  1484. # seLinuxContext
  1485. self.data['seLinuxContext'] = {'type': self.se_linux_context}
  1486. # supplementalGroups
  1487. self.data['supplementalGroups'] = {'type': self.supplemental_groups}
  1488. # users
  1489. self.data['users'] = []
  1490. if self.users:
  1491. self.data['users'] = self.users
  1492. # pylint: disable=too-many-instance-attributes,too-many-public-methods,no-member
  1493. class SecurityContextConstraints(Yedit):
  1494. ''' Class to wrap the oc command line tools '''
  1495. default_add_capabilities_path = "defaultAddCapabilities"
  1496. fs_group_path = "fsGroup"
  1497. groups_path = "groups"
  1498. priority_path = "priority"
  1499. required_drop_capabilities_path = "requiredDropCapabilities"
  1500. run_as_user_path = "runAsUser"
  1501. se_linux_context_path = "seLinuxContext"
  1502. supplemental_groups_path = "supplementalGroups"
  1503. users_path = "users"
  1504. kind = 'SecurityContextConstraints'
  1505. def __init__(self, content):
  1506. '''SecurityContextConstraints constructor'''
  1507. super(SecurityContextConstraints, self).__init__(content=content)
  1508. self._users = None
  1509. self._groups = None
  1510. @property
  1511. def users(self):
  1512. ''' users property getter '''
  1513. if self._users is None:
  1514. self._users = self.get_users()
  1515. return self._users
  1516. @property
  1517. def groups(self):
  1518. ''' groups property getter '''
  1519. if self._groups is None:
  1520. self._groups = self.get_groups()
  1521. return self._groups
  1522. @users.setter
  1523. def users(self, data):
  1524. ''' users property setter'''
  1525. self._users = data
  1526. @groups.setter
  1527. def groups(self, data):
  1528. ''' groups property setter'''
  1529. self._groups = data
  1530. def get_users(self):
  1531. '''get scc users'''
  1532. return self.get(SecurityContextConstraints.users_path) or []
  1533. def get_groups(self):
  1534. '''get scc groups'''
  1535. return self.get(SecurityContextConstraints.groups_path) or []
  1536. def add_user(self, inc_user):
  1537. ''' add a user '''
  1538. if self.users:
  1539. self.users.append(inc_user)
  1540. else:
  1541. self.put(SecurityContextConstraints.users_path, [inc_user])
  1542. return True
  1543. def add_group(self, inc_group):
  1544. ''' add a group '''
  1545. if self.groups:
  1546. self.groups.append(inc_group)
  1547. else:
  1548. self.put(SecurityContextConstraints.groups_path, [inc_group])
  1549. return True
  1550. def remove_user(self, inc_user):
  1551. ''' remove a user '''
  1552. try:
  1553. self.users.remove(inc_user)
  1554. except ValueError as _:
  1555. return False
  1556. return True
  1557. def remove_group(self, inc_group):
  1558. ''' remove a group '''
  1559. try:
  1560. self.groups.remove(inc_group)
  1561. except ValueError as _:
  1562. return False
  1563. return True
  1564. def update_user(self, inc_user):
  1565. ''' update a user '''
  1566. try:
  1567. index = self.users.index(inc_user)
  1568. except ValueError as _:
  1569. return self.add_user(inc_user)
  1570. self.users[index] = inc_user
  1571. return True
  1572. def update_group(self, inc_group):
  1573. ''' update a group '''
  1574. try:
  1575. index = self.groups.index(inc_group)
  1576. except ValueError as _:
  1577. return self.add_group(inc_group)
  1578. self.groups[index] = inc_group
  1579. return True
  1580. def find_user(self, inc_user):
  1581. ''' find a user '''
  1582. index = None
  1583. try:
  1584. index = self.users.index(inc_user)
  1585. except ValueError as _:
  1586. return index
  1587. return index
  1588. def find_group(self, inc_group):
  1589. ''' find a group '''
  1590. index = None
  1591. try:
  1592. index = self.groups.index(inc_group)
  1593. except ValueError as _:
  1594. return index
  1595. return index
  1596. # -*- -*- -*- End included fragment: lib/scc.py -*- -*- -*-
  1597. # -*- -*- -*- Begin included fragment: class/oc_adm_policy_group.py -*- -*- -*-
  1598. class PolicyGroupException(Exception):
  1599. ''' PolicyGroup exception'''
  1600. pass
  1601. class PolicyGroupConfig(OpenShiftCLIConfig):
  1602. ''' PolicyGroupConfig is a DTO for group related policy. '''
  1603. def __init__(self, namespace, kubeconfig, policy_options):
  1604. super(PolicyGroupConfig, self).__init__(policy_options['name']['value'],
  1605. namespace, kubeconfig, policy_options)
  1606. self.kind = self.get_kind()
  1607. self.namespace = namespace
  1608. def get_kind(self):
  1609. ''' return the kind we are working with '''
  1610. if self.config_options['resource_kind']['value'] == 'role':
  1611. return 'rolebinding'
  1612. elif self.config_options['resource_kind']['value'] == 'cluster-role':
  1613. return 'clusterrolebinding'
  1614. elif self.config_options['resource_kind']['value'] == 'scc':
  1615. return 'scc'
  1616. return None
  1617. # pylint: disable=too-many-return-statements
  1618. class PolicyGroup(OpenShiftCLI):
  1619. ''' Class to handle attaching policies to users '''
  1620. def __init__(self,
  1621. config,
  1622. verbose=False):
  1623. ''' Constructor for PolicyGroup '''
  1624. super(PolicyGroup, self).__init__(config.namespace, config.kubeconfig, verbose)
  1625. self.config = config
  1626. self.verbose = verbose
  1627. self._rolebinding = None
  1628. self._scc = None
  1629. self._cluster_role_bindings = None
  1630. self._role_bindings = None
  1631. @property
  1632. def rolebindings(self):
  1633. if self._role_bindings is None:
  1634. results = self._get('rolebindings', None)
  1635. if results['returncode'] != 0:
  1636. raise OpenShiftCLIError('Could not retrieve rolebindings')
  1637. self._role_bindings = results['results'][0]['items']
  1638. return self._role_bindings
  1639. @property
  1640. def clusterrolebindings(self):
  1641. if self._cluster_role_bindings is None:
  1642. results = self._get('clusterrolebindings', None)
  1643. if results['returncode'] != 0:
  1644. raise OpenShiftCLIError('Could not retrieve clusterrolebindings')
  1645. self._cluster_role_bindings = results['results'][0]['items']
  1646. return self._cluster_role_bindings
  1647. @property
  1648. def role_binding(self):
  1649. ''' role_binding getter '''
  1650. return self._rolebinding
  1651. @role_binding.setter
  1652. def role_binding(self, binding):
  1653. ''' role_binding setter '''
  1654. self._rolebinding = binding
  1655. @property
  1656. def security_context_constraint(self):
  1657. ''' security_context_constraint getter '''
  1658. return self._scc
  1659. @security_context_constraint.setter
  1660. def security_context_constraint(self, scc):
  1661. ''' security_context_constraint setter '''
  1662. self._scc = scc
  1663. def get(self):
  1664. '''fetch the desired kind'''
  1665. resource_name = self.config.config_options['name']['value']
  1666. if resource_name == 'cluster-reader':
  1667. resource_name += 's'
  1668. # oc adm policy add-... creates policy bindings with the name
  1669. # "[resource_name]-binding", however some bindings in the system
  1670. # simply use "[resource_name]". So try both.
  1671. results = self._get(self.config.kind, resource_name)
  1672. if results['returncode'] == 0:
  1673. return results
  1674. # Now try -binding naming convention
  1675. return self._get(self.config.kind, resource_name + "-binding")
  1676. def exists_role_binding(self):
  1677. ''' return whether role_binding exists '''
  1678. bindings = None
  1679. if self.config.config_options['resource_kind']['value'] == 'cluster-role':
  1680. bindings = self.clusterrolebindings
  1681. else:
  1682. bindings = self.rolebindings
  1683. if bindings is None:
  1684. return False
  1685. for binding in bindings:
  1686. if binding['roleRef']['name'] == self.config.config_options['name']['value'] and \
  1687. binding['groupNames'] is not None and \
  1688. self.config.config_options['group']['value'] in binding['groupNames']:
  1689. self.role_binding = binding
  1690. return True
  1691. return False
  1692. def exists_scc(self):
  1693. ''' return whether scc exists '''
  1694. results = self.get()
  1695. if results['returncode'] == 0:
  1696. self.security_context_constraint = SecurityContextConstraints(results['results'][0])
  1697. if self.security_context_constraint.find_group(self.config.config_options['group']['value']) != None:
  1698. return True
  1699. return False
  1700. return results
  1701. def exists(self):
  1702. '''does the object exist?'''
  1703. if self.config.config_options['resource_kind']['value'] == 'cluster-role':
  1704. return self.exists_role_binding()
  1705. elif self.config.config_options['resource_kind']['value'] == 'role':
  1706. return self.exists_role_binding()
  1707. elif self.config.config_options['resource_kind']['value'] == 'scc':
  1708. return self.exists_scc()
  1709. return False
  1710. def perform(self):
  1711. '''perform action on resource'''
  1712. cmd = ['policy',
  1713. self.config.config_options['action']['value'],
  1714. self.config.config_options['name']['value'],
  1715. self.config.config_options['group']['value']]
  1716. return self.openshift_cmd(cmd, oadm=True)
  1717. @staticmethod
  1718. def run_ansible(params, check_mode):
  1719. '''run the oc_adm_policy_group module'''
  1720. state = params['state']
  1721. action = None
  1722. if state == 'present':
  1723. action = 'add-' + params['resource_kind'] + '-to-group'
  1724. else:
  1725. action = 'remove-' + params['resource_kind'] + '-from-group'
  1726. nconfig = PolicyGroupConfig(params['namespace'],
  1727. params['kubeconfig'],
  1728. {'action': {'value': action, 'include': False},
  1729. 'group': {'value': params['group'], 'include': False},
  1730. 'resource_kind': {'value': params['resource_kind'], 'include': False},
  1731. 'name': {'value': params['resource_name'], 'include': False},
  1732. })
  1733. policygroup = PolicyGroup(nconfig, params['debug'])
  1734. # Run the oc adm policy group related command
  1735. ########
  1736. # Delete
  1737. ########
  1738. if state == 'absent':
  1739. if not policygroup.exists():
  1740. return {'changed': False, 'state': 'absent'}
  1741. if check_mode:
  1742. return {'changed': False, 'msg': 'CHECK_MODE: would have performed a delete.'}
  1743. api_rval = policygroup.perform()
  1744. if api_rval['returncode'] != 0:
  1745. return {'msg': api_rval}
  1746. return {'changed': True, 'results' : api_rval, state:'absent'}
  1747. if state == 'present':
  1748. ########
  1749. # Create
  1750. ########
  1751. results = policygroup.exists()
  1752. if isinstance(results, dict) and 'returncode' in results and results['returncode'] != 0:
  1753. return {'msg': results}
  1754. if not results:
  1755. if check_mode:
  1756. return {'changed': False, 'msg': 'CHECK_MODE: would have performed a create.'}
  1757. api_rval = policygroup.perform()
  1758. if api_rval['returncode'] != 0:
  1759. return {'msg': api_rval}
  1760. return {'changed': True, 'results': api_rval, state: 'present'}
  1761. return {'changed': False, state: 'present'}
  1762. return {'failed': True, 'changed': False, 'results': 'Unknown state passed. %s' % state, state: 'unknown'}
  1763. # -*- -*- -*- End included fragment: class/oc_adm_policy_group.py -*- -*- -*-
  1764. # -*- -*- -*- Begin included fragment: ansible/oc_adm_policy_group.py -*- -*- -*-
  1765. def main():
  1766. '''
  1767. ansible oc adm module for group policy
  1768. '''
  1769. module = AnsibleModule(
  1770. argument_spec=dict(
  1771. state=dict(default='present', type='str',
  1772. choices=['present', 'absent']),
  1773. debug=dict(default=False, type='bool'),
  1774. resource_name=dict(required=True, type='str'),
  1775. namespace=dict(default='default', type='str'),
  1776. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  1777. group=dict(required=True, type='str'),
  1778. resource_kind=dict(required=True, choices=['role', 'cluster-role', 'scc'], type='str'),
  1779. ),
  1780. supports_check_mode=True,
  1781. )
  1782. results = PolicyGroup.run_ansible(module.params, module.check_mode)
  1783. if 'failed' in results:
  1784. module.fail_json(**results)
  1785. module.exit_json(**results)
  1786. if __name__ == "__main__":
  1787. main()
  1788. # -*- -*- -*- End included fragment: ansible/oc_adm_policy_group.py -*- -*- -*-