oc_adm_policy_group.py 73 KB

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