oc_adm_policy_user.py 74 KB

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