oc_adm_policy_group.py 72 KB

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