oc_adm_policy_group.py 72 KB

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