oc_adm_policy_group.py 73 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250
  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. state:
  59. description:
  60. - State controls the action that will be taken with resource
  61. required: true
  62. default: present
  63. choices: ["present", "absent"]
  64. aliases: []
  65. kubeconfig:
  66. description:
  67. - The path for the kubeconfig file to use for authentication
  68. required: false
  69. default: /etc/origin/master/admin.kubeconfig
  70. aliases: []
  71. namespace:
  72. description:
  73. - The namespace scope
  74. required: false
  75. default: None
  76. aliases: []
  77. rolebinding_name:
  78. description:
  79. - The name of the rolebinding object for roles
  80. required: false
  81. default: None
  82. aliases: []
  83. debug:
  84. description:
  85. - Turn on debug output.
  86. required: false
  87. default: False
  88. aliases: []
  89. group:
  90. description:
  91. - The name of the group
  92. required: true
  93. default: None
  94. aliases: []
  95. resource_kind:
  96. description:
  97. - The kind of policy to affect
  98. required: true
  99. default: None
  100. choices: ["role", "cluster-role", "scc"]
  101. aliases: []
  102. resource_name:
  103. description:
  104. - The name of the policy
  105. required: true
  106. default: None
  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. if user_def is None:
  1074. user_def = {}
  1075. # Currently these values are autogenerated and we do not need to check them
  1076. skip = ['metadata', 'status']
  1077. if skip_keys:
  1078. skip.extend(skip_keys)
  1079. for key, value in result_def.items():
  1080. if key in skip:
  1081. continue
  1082. # Both are lists
  1083. if isinstance(value, list):
  1084. if key not in user_def:
  1085. if debug:
  1086. print('User data does not have key [%s]' % key)
  1087. print('User data: %s' % user_def)
  1088. return False
  1089. if not isinstance(user_def[key], list):
  1090. if debug:
  1091. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  1092. return False
  1093. if len(user_def[key]) != len(value):
  1094. if debug:
  1095. print("List lengths are not equal.")
  1096. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  1097. print("user_def: %s" % user_def[key])
  1098. print("value: %s" % value)
  1099. return False
  1100. for values in zip(user_def[key], value):
  1101. if isinstance(values[0], dict) and isinstance(values[1], dict):
  1102. if debug:
  1103. print('sending list - list')
  1104. print(type(values[0]))
  1105. print(type(values[1]))
  1106. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  1107. if not result:
  1108. print('list compare returned false')
  1109. return False
  1110. elif value != user_def[key]:
  1111. if debug:
  1112. print('value should be identical')
  1113. print(user_def[key])
  1114. print(value)
  1115. return False
  1116. # recurse on a dictionary
  1117. elif isinstance(value, dict):
  1118. if key not in user_def:
  1119. if debug:
  1120. print("user_def does not have key [%s]" % key)
  1121. return False
  1122. if not isinstance(user_def[key], dict):
  1123. if debug:
  1124. print("dict returned false: not instance of dict")
  1125. return False
  1126. # before passing ensure keys match
  1127. api_values = set(value.keys()) - set(skip)
  1128. user_values = set(user_def[key].keys()) - set(skip)
  1129. if api_values != user_values:
  1130. if debug:
  1131. print("keys are not equal in dict")
  1132. print(user_values)
  1133. print(api_values)
  1134. return False
  1135. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  1136. if not result:
  1137. if debug:
  1138. print("dict returned false")
  1139. print(result)
  1140. return False
  1141. # Verify each key, value pair is the same
  1142. else:
  1143. if key not in user_def or value != user_def[key]:
  1144. if debug:
  1145. print("value not equal; user_def does not have key")
  1146. print(key)
  1147. print(value)
  1148. if key in user_def:
  1149. print(user_def[key])
  1150. return False
  1151. if debug:
  1152. print('returning true')
  1153. return True
  1154. class OpenShiftCLIConfig(object):
  1155. '''Generic Config'''
  1156. def __init__(self, rname, namespace, kubeconfig, options):
  1157. self.kubeconfig = kubeconfig
  1158. self.name = rname
  1159. self.namespace = namespace
  1160. self._options = options
  1161. @property
  1162. def config_options(self):
  1163. ''' return config options '''
  1164. return self._options
  1165. def to_option_list(self, ascommalist=''):
  1166. '''return all options as a string
  1167. if ascommalist is set to the name of a key, and
  1168. the value of that key is a dict, format the dict
  1169. as a list of comma delimited key=value pairs'''
  1170. return self.stringify(ascommalist)
  1171. def stringify(self, ascommalist=''):
  1172. ''' return the options hash as cli params in a string
  1173. if ascommalist is set to the name of a key, and
  1174. the value of that key is a dict, format the dict
  1175. as a list of comma delimited key=value pairs '''
  1176. rval = []
  1177. for key in sorted(self.config_options.keys()):
  1178. data = self.config_options[key]
  1179. if data['include'] \
  1180. and (data['value'] is not None or isinstance(data['value'], int)):
  1181. if key == ascommalist:
  1182. val = ','.join(['{}={}'.format(kk, vv) for kk, vv in sorted(data['value'].items())])
  1183. else:
  1184. val = data['value']
  1185. rval.append('--{}={}'.format(key.replace('_', '-'), val))
  1186. return rval
  1187. # -*- -*- -*- End included fragment: lib/base.py -*- -*- -*-
  1188. # -*- -*- -*- Begin included fragment: lib/rolebinding.py -*- -*- -*-
  1189. # pylint: disable=too-many-instance-attributes
  1190. class RoleBindingConfig(object):
  1191. ''' Handle rolebinding config '''
  1192. # pylint: disable=too-many-arguments
  1193. def __init__(self,
  1194. name,
  1195. namespace,
  1196. kubeconfig,
  1197. group_names=None,
  1198. role_ref=None,
  1199. subjects=None,
  1200. usernames=None):
  1201. ''' constructor for handling rolebinding options '''
  1202. self.kubeconfig = kubeconfig
  1203. self.name = name
  1204. self.namespace = namespace
  1205. self.group_names = group_names
  1206. self.role_ref = role_ref
  1207. self.subjects = subjects
  1208. self.usernames = usernames
  1209. self.data = {}
  1210. self.create_dict()
  1211. def create_dict(self):
  1212. ''' create a default rolebinding as a dict '''
  1213. self.data['apiVersion'] = 'v1'
  1214. self.data['kind'] = 'RoleBinding'
  1215. self.data['groupNames'] = self.group_names
  1216. self.data['metadata']['name'] = self.name
  1217. self.data['metadata']['namespace'] = self.namespace
  1218. self.data['roleRef'] = self.role_ref
  1219. self.data['subjects'] = self.subjects
  1220. self.data['userNames'] = self.usernames
  1221. # pylint: disable=too-many-instance-attributes,too-many-public-methods
  1222. class RoleBinding(Yedit):
  1223. ''' Class to model a rolebinding openshift object'''
  1224. group_names_path = "groupNames"
  1225. role_ref_path = "roleRef"
  1226. subjects_path = "subjects"
  1227. user_names_path = "userNames"
  1228. kind = 'RoleBinding'
  1229. def __init__(self, content):
  1230. '''RoleBinding constructor'''
  1231. super(RoleBinding, self).__init__(content=content)
  1232. self._subjects = None
  1233. self._role_ref = None
  1234. self._group_names = None
  1235. self._user_names = None
  1236. @property
  1237. def subjects(self):
  1238. ''' subjects property '''
  1239. if self._subjects is None:
  1240. self._subjects = self.get_subjects()
  1241. return self._subjects
  1242. @subjects.setter
  1243. def subjects(self, data):
  1244. ''' subjects property setter'''
  1245. self._subjects = data
  1246. @property
  1247. def role_ref(self):
  1248. ''' role_ref property '''
  1249. if self._role_ref is None:
  1250. self._role_ref = self.get_role_ref()
  1251. return self._role_ref
  1252. @role_ref.setter
  1253. def role_ref(self, data):
  1254. ''' role_ref property setter'''
  1255. self._role_ref = data
  1256. @property
  1257. def group_names(self):
  1258. ''' group_names property '''
  1259. if self._group_names is None:
  1260. self._group_names = self.get_group_names()
  1261. return self._group_names
  1262. @group_names.setter
  1263. def group_names(self, data):
  1264. ''' group_names property setter'''
  1265. self._group_names = data
  1266. @property
  1267. def user_names(self):
  1268. ''' user_names property '''
  1269. if self._user_names is None:
  1270. self._user_names = self.get_user_names()
  1271. return self._user_names
  1272. @user_names.setter
  1273. def user_names(self, data):
  1274. ''' user_names property setter'''
  1275. self._user_names = data
  1276. def get_group_names(self):
  1277. ''' return groupNames '''
  1278. return self.get(RoleBinding.group_names_path) or []
  1279. def get_user_names(self):
  1280. ''' return usernames '''
  1281. return self.get(RoleBinding.user_names_path) or []
  1282. def get_role_ref(self):
  1283. ''' return role_ref '''
  1284. return self.get(RoleBinding.role_ref_path) or {}
  1285. def get_subjects(self):
  1286. ''' return subjects '''
  1287. return self.get(RoleBinding.subjects_path) or []
  1288. #### ADD #####
  1289. def add_subject(self, inc_subject):
  1290. ''' add a subject '''
  1291. if self.subjects:
  1292. # pylint: disable=no-member
  1293. self.subjects.append(inc_subject)
  1294. else:
  1295. self.put(RoleBinding.subjects_path, [inc_subject])
  1296. return True
  1297. def add_role_ref(self, inc_role_ref):
  1298. ''' add a role_ref '''
  1299. if not self.role_ref:
  1300. self.put(RoleBinding.role_ref_path, {"name": inc_role_ref})
  1301. return True
  1302. return False
  1303. def add_group_names(self, inc_group_names):
  1304. ''' add a group_names '''
  1305. if self.group_names:
  1306. # pylint: disable=no-member
  1307. self.group_names.append(inc_group_names)
  1308. else:
  1309. self.put(RoleBinding.group_names_path, [inc_group_names])
  1310. return True
  1311. def add_user_name(self, inc_user_name):
  1312. ''' add a username '''
  1313. if self.user_names:
  1314. # pylint: disable=no-member
  1315. self.user_names.append(inc_user_name)
  1316. else:
  1317. self.put(RoleBinding.user_names_path, [inc_user_name])
  1318. return True
  1319. #### /ADD #####
  1320. #### Remove #####
  1321. def remove_subject(self, inc_subject):
  1322. ''' remove a subject '''
  1323. try:
  1324. # pylint: disable=no-member
  1325. self.subjects.remove(inc_subject)
  1326. except ValueError as _:
  1327. return False
  1328. return True
  1329. def remove_role_ref(self, inc_role_ref):
  1330. ''' remove a role_ref '''
  1331. if self.role_ref and self.role_ref['name'] == inc_role_ref:
  1332. del self.role_ref['name']
  1333. return True
  1334. return False
  1335. def remove_group_name(self, inc_group_name):
  1336. ''' remove a groupname '''
  1337. try:
  1338. # pylint: disable=no-member
  1339. self.group_names.remove(inc_group_name)
  1340. except ValueError as _:
  1341. return False
  1342. return True
  1343. def remove_user_name(self, inc_user_name):
  1344. ''' remove a username '''
  1345. try:
  1346. # pylint: disable=no-member
  1347. self.user_names.remove(inc_user_name)
  1348. except ValueError as _:
  1349. return False
  1350. return True
  1351. #### /REMOVE #####
  1352. #### UPDATE #####
  1353. def update_subject(self, inc_subject):
  1354. ''' update a subject '''
  1355. try:
  1356. # pylint: disable=no-member
  1357. index = self.subjects.index(inc_subject)
  1358. except ValueError as _:
  1359. return self.add_subject(inc_subject)
  1360. self.subjects[index] = inc_subject
  1361. return True
  1362. def update_group_name(self, inc_group_name):
  1363. ''' update a groupname '''
  1364. try:
  1365. # pylint: disable=no-member
  1366. index = self.group_names.index(inc_group_name)
  1367. except ValueError as _:
  1368. return self.add_group_names(inc_group_name)
  1369. self.group_names[index] = inc_group_name
  1370. return True
  1371. def update_user_name(self, inc_user_name):
  1372. ''' update a username '''
  1373. try:
  1374. # pylint: disable=no-member
  1375. index = self.user_names.index(inc_user_name)
  1376. except ValueError as _:
  1377. return self.add_user_name(inc_user_name)
  1378. self.user_names[index] = inc_user_name
  1379. return True
  1380. def update_role_ref(self, inc_role_ref):
  1381. ''' update a role_ref '''
  1382. self.role_ref['name'] = inc_role_ref
  1383. return True
  1384. #### /UPDATE #####
  1385. #### FIND ####
  1386. def find_subject(self, inc_subject):
  1387. ''' find a subject '''
  1388. index = None
  1389. try:
  1390. # pylint: disable=no-member
  1391. index = self.subjects.index(inc_subject)
  1392. except ValueError as _:
  1393. return index
  1394. return index
  1395. def find_group_name(self, inc_group_name):
  1396. ''' find a group_name '''
  1397. index = None
  1398. try:
  1399. # pylint: disable=no-member
  1400. index = self.group_names.index(inc_group_name)
  1401. except ValueError as _:
  1402. return index
  1403. return index
  1404. def find_user_name(self, inc_user_name):
  1405. ''' find a user_name '''
  1406. index = None
  1407. try:
  1408. # pylint: disable=no-member
  1409. index = self.user_names.index(inc_user_name)
  1410. except ValueError as _:
  1411. return index
  1412. return index
  1413. def find_role_ref(self, inc_role_ref):
  1414. ''' find a user_name '''
  1415. if self.role_ref and self.role_ref['name'] == inc_role_ref['name']:
  1416. return self.role_ref
  1417. return None
  1418. # -*- -*- -*- End included fragment: lib/rolebinding.py -*- -*- -*-
  1419. # -*- -*- -*- Begin included fragment: lib/scc.py -*- -*- -*-
  1420. # pylint: disable=too-many-instance-attributes
  1421. class SecurityContextConstraintsConfig(object):
  1422. ''' Handle scc options '''
  1423. # pylint: disable=too-many-arguments
  1424. def __init__(self,
  1425. sname,
  1426. kubeconfig,
  1427. options=None,
  1428. fs_group='MustRunAs',
  1429. default_add_capabilities=None,
  1430. groups=None,
  1431. priority=None,
  1432. required_drop_capabilities=None,
  1433. run_as_user='MustRunAsRange',
  1434. se_linux_context='MustRunAs',
  1435. supplemental_groups='RunAsAny',
  1436. users=None,
  1437. annotations=None):
  1438. ''' constructor for handling scc options '''
  1439. self.kubeconfig = kubeconfig
  1440. self.name = sname
  1441. self.options = options
  1442. self.fs_group = fs_group
  1443. self.default_add_capabilities = default_add_capabilities
  1444. self.groups = groups
  1445. self.priority = priority
  1446. self.required_drop_capabilities = required_drop_capabilities
  1447. self.run_as_user = run_as_user
  1448. self.se_linux_context = se_linux_context
  1449. self.supplemental_groups = supplemental_groups
  1450. self.users = users
  1451. self.annotations = annotations
  1452. self.data = {}
  1453. self.create_dict()
  1454. def create_dict(self):
  1455. ''' assign the correct properties for a scc dict '''
  1456. # allow options
  1457. if self.options:
  1458. for key, value in self.options.items():
  1459. self.data[key] = value
  1460. else:
  1461. self.data['allowHostDirVolumePlugin'] = False
  1462. self.data['allowHostIPC'] = False
  1463. self.data['allowHostNetwork'] = False
  1464. self.data['allowHostPID'] = False
  1465. self.data['allowHostPorts'] = False
  1466. self.data['allowPrivilegedContainer'] = False
  1467. self.data['allowedCapabilities'] = None
  1468. # version
  1469. self.data['apiVersion'] = 'v1'
  1470. # kind
  1471. self.data['kind'] = 'SecurityContextConstraints'
  1472. # defaultAddCapabilities
  1473. self.data['defaultAddCapabilities'] = self.default_add_capabilities
  1474. # fsGroup
  1475. self.data['fsGroup']['type'] = self.fs_group
  1476. # groups
  1477. self.data['groups'] = []
  1478. if self.groups:
  1479. self.data['groups'] = self.groups
  1480. # metadata
  1481. self.data['metadata'] = {}
  1482. self.data['metadata']['name'] = self.name
  1483. if self.annotations:
  1484. for key, value in self.annotations.items():
  1485. self.data['metadata'][key] = value
  1486. # priority
  1487. self.data['priority'] = self.priority
  1488. # requiredDropCapabilities
  1489. self.data['requiredDropCapabilities'] = self.required_drop_capabilities
  1490. # runAsUser
  1491. self.data['runAsUser'] = {'type': self.run_as_user}
  1492. # seLinuxContext
  1493. self.data['seLinuxContext'] = {'type': self.se_linux_context}
  1494. # supplementalGroups
  1495. self.data['supplementalGroups'] = {'type': self.supplemental_groups}
  1496. # users
  1497. self.data['users'] = []
  1498. if self.users:
  1499. self.data['users'] = self.users
  1500. # pylint: disable=too-many-instance-attributes,too-many-public-methods,no-member
  1501. class SecurityContextConstraints(Yedit):
  1502. ''' Class to wrap the oc command line tools '''
  1503. default_add_capabilities_path = "defaultAddCapabilities"
  1504. fs_group_path = "fsGroup"
  1505. groups_path = "groups"
  1506. priority_path = "priority"
  1507. required_drop_capabilities_path = "requiredDropCapabilities"
  1508. run_as_user_path = "runAsUser"
  1509. se_linux_context_path = "seLinuxContext"
  1510. supplemental_groups_path = "supplementalGroups"
  1511. users_path = "users"
  1512. kind = 'SecurityContextConstraints'
  1513. def __init__(self, content):
  1514. '''SecurityContextConstraints constructor'''
  1515. super(SecurityContextConstraints, self).__init__(content=content)
  1516. self._users = None
  1517. self._groups = None
  1518. @property
  1519. def users(self):
  1520. ''' users property getter '''
  1521. if self._users is None:
  1522. self._users = self.get_users()
  1523. return self._users
  1524. @property
  1525. def groups(self):
  1526. ''' groups property getter '''
  1527. if self._groups is None:
  1528. self._groups = self.get_groups()
  1529. return self._groups
  1530. @users.setter
  1531. def users(self, data):
  1532. ''' users property setter'''
  1533. self._users = data
  1534. @groups.setter
  1535. def groups(self, data):
  1536. ''' groups property setter'''
  1537. self._groups = data
  1538. def get_users(self):
  1539. '''get scc users'''
  1540. return self.get(SecurityContextConstraints.users_path) or []
  1541. def get_groups(self):
  1542. '''get scc groups'''
  1543. return self.get(SecurityContextConstraints.groups_path) or []
  1544. def add_user(self, inc_user):
  1545. ''' add a user '''
  1546. if self.users:
  1547. self.users.append(inc_user)
  1548. else:
  1549. self.put(SecurityContextConstraints.users_path, [inc_user])
  1550. return True
  1551. def add_group(self, inc_group):
  1552. ''' add a group '''
  1553. if self.groups:
  1554. self.groups.append(inc_group)
  1555. else:
  1556. self.put(SecurityContextConstraints.groups_path, [inc_group])
  1557. return True
  1558. def remove_user(self, inc_user):
  1559. ''' remove a user '''
  1560. try:
  1561. self.users.remove(inc_user)
  1562. except ValueError as _:
  1563. return False
  1564. return True
  1565. def remove_group(self, inc_group):
  1566. ''' remove a group '''
  1567. try:
  1568. self.groups.remove(inc_group)
  1569. except ValueError as _:
  1570. return False
  1571. return True
  1572. def update_user(self, inc_user):
  1573. ''' update a user '''
  1574. try:
  1575. index = self.users.index(inc_user)
  1576. except ValueError as _:
  1577. return self.add_user(inc_user)
  1578. self.users[index] = inc_user
  1579. return True
  1580. def update_group(self, inc_group):
  1581. ''' update a group '''
  1582. try:
  1583. index = self.groups.index(inc_group)
  1584. except ValueError as _:
  1585. return self.add_group(inc_group)
  1586. self.groups[index] = inc_group
  1587. return True
  1588. def find_user(self, inc_user):
  1589. ''' find a user '''
  1590. index = None
  1591. try:
  1592. index = self.users.index(inc_user)
  1593. except ValueError as _:
  1594. return index
  1595. return index
  1596. def find_group(self, inc_group):
  1597. ''' find a group '''
  1598. index = None
  1599. try:
  1600. index = self.groups.index(inc_group)
  1601. except ValueError as _:
  1602. return index
  1603. return index
  1604. # -*- -*- -*- End included fragment: lib/scc.py -*- -*- -*-
  1605. # -*- -*- -*- Begin included fragment: class/oc_adm_policy_group.py -*- -*- -*-
  1606. class PolicyGroupException(Exception):
  1607. ''' PolicyGroup exception'''
  1608. pass
  1609. class PolicyGroupConfig(OpenShiftCLIConfig):
  1610. ''' PolicyGroupConfig is a DTO for group related policy. '''
  1611. def __init__(self, namespace, kubeconfig, policy_options):
  1612. super(PolicyGroupConfig, self).__init__(policy_options['name']['value'],
  1613. namespace, kubeconfig, policy_options)
  1614. self.kind = self.get_kind()
  1615. self.namespace = namespace
  1616. def get_kind(self):
  1617. ''' return the kind we are working with '''
  1618. if self.config_options['resource_kind']['value'] == 'role':
  1619. return 'rolebinding'
  1620. elif self.config_options['resource_kind']['value'] == 'cluster-role':
  1621. return 'clusterrolebinding'
  1622. elif self.config_options['resource_kind']['value'] == 'scc':
  1623. return 'scc'
  1624. return None
  1625. # pylint: disable=too-many-return-statements
  1626. class PolicyGroup(OpenShiftCLI):
  1627. ''' Class to handle attaching policies to users '''
  1628. def __init__(self,
  1629. config,
  1630. verbose=False):
  1631. ''' Constructor for PolicyGroup '''
  1632. super(PolicyGroup, self).__init__(config.namespace, config.kubeconfig, verbose)
  1633. self.config = config
  1634. self.verbose = verbose
  1635. self._rolebinding = None
  1636. self._scc = None
  1637. self._cluster_role_bindings = None
  1638. self._role_bindings = None
  1639. @property
  1640. def rolebindings(self):
  1641. if self._role_bindings is None:
  1642. results = self._get('rolebindings', None)
  1643. if results['returncode'] != 0:
  1644. raise OpenShiftCLIError('Could not retrieve rolebindings')
  1645. self._role_bindings = results['results'][0]['items']
  1646. return self._role_bindings
  1647. @property
  1648. def clusterrolebindings(self):
  1649. if self._cluster_role_bindings is None:
  1650. results = self._get('clusterrolebindings', None)
  1651. if results['returncode'] != 0:
  1652. raise OpenShiftCLIError('Could not retrieve clusterrolebindings')
  1653. self._cluster_role_bindings = results['results'][0]['items']
  1654. return self._cluster_role_bindings
  1655. @property
  1656. def role_binding(self):
  1657. ''' role_binding getter '''
  1658. return self._rolebinding
  1659. @role_binding.setter
  1660. def role_binding(self, binding):
  1661. ''' role_binding setter '''
  1662. self._rolebinding = binding
  1663. @property
  1664. def security_context_constraint(self):
  1665. ''' security_context_constraint getter '''
  1666. return self._scc
  1667. @security_context_constraint.setter
  1668. def security_context_constraint(self, scc):
  1669. ''' security_context_constraint setter '''
  1670. self._scc = scc
  1671. def get(self):
  1672. '''fetch the desired kind'''
  1673. resource_name = self.config.config_options['name']['value']
  1674. if resource_name == 'cluster-reader':
  1675. resource_name += 's'
  1676. # oc adm policy add-... creates policy bindings with the name
  1677. # "[resource_name]-binding", however some bindings in the system
  1678. # simply use "[resource_name]". So try both.
  1679. results = self._get(self.config.kind, resource_name)
  1680. if results['returncode'] == 0:
  1681. return results
  1682. # Now try -binding naming convention
  1683. return self._get(self.config.kind, resource_name + "-binding")
  1684. def exists_role_binding(self):
  1685. ''' return whether role_binding exists '''
  1686. bindings = None
  1687. if self.config.config_options['resource_kind']['value'] == 'cluster-role':
  1688. bindings = self.clusterrolebindings
  1689. else:
  1690. bindings = self.rolebindings
  1691. if bindings is None:
  1692. return False
  1693. for binding in bindings:
  1694. if self.config.config_options['rolebinding_name']['value'] is not None and \
  1695. binding['metadata']['name'] != self.config.config_options['rolebinding_name']['value']:
  1696. continue
  1697. if binding['roleRef']['name'] == self.config.config_options['name']['value'] and \
  1698. binding['groupNames'] is not None and \
  1699. self.config.config_options['group']['value'] in binding['groupNames']:
  1700. self.role_binding = binding
  1701. return True
  1702. return False
  1703. def exists_scc(self):
  1704. ''' return whether scc exists '''
  1705. results = self.get()
  1706. if results['returncode'] == 0:
  1707. self.security_context_constraint = SecurityContextConstraints(results['results'][0])
  1708. if self.security_context_constraint.find_group(self.config.config_options['group']['value']) != None:
  1709. return True
  1710. return False
  1711. return results
  1712. def exists(self):
  1713. '''does the object exist?'''
  1714. if self.config.config_options['resource_kind']['value'] == 'cluster-role':
  1715. return self.exists_role_binding()
  1716. elif self.config.config_options['resource_kind']['value'] == 'role':
  1717. return self.exists_role_binding()
  1718. elif self.config.config_options['resource_kind']['value'] == 'scc':
  1719. return self.exists_scc()
  1720. return False
  1721. def perform(self):
  1722. '''perform action on resource'''
  1723. cmd = ['policy',
  1724. self.config.config_options['action']['value'],
  1725. self.config.config_options['name']['value'],
  1726. self.config.config_options['group']['value']]
  1727. if self.config.config_options['rolebinding_name']['value'] is not None:
  1728. cmd.extend(['--rolebinding-name', self.config.config_options['rolebinding_name']['value']])
  1729. return self.openshift_cmd(cmd, oadm=True)
  1730. @staticmethod
  1731. def run_ansible(params, check_mode):
  1732. '''run the oc_adm_policy_group module'''
  1733. state = params['state']
  1734. action = None
  1735. if state == 'present':
  1736. action = 'add-' + params['resource_kind'] + '-to-group'
  1737. else:
  1738. action = 'remove-' + params['resource_kind'] + '-from-group'
  1739. nconfig = PolicyGroupConfig(params['namespace'],
  1740. params['kubeconfig'],
  1741. {'action': {'value': action, 'include': False},
  1742. 'group': {'value': params['group'], 'include': False},
  1743. 'resource_kind': {'value': params['resource_kind'], 'include': False},
  1744. 'name': {'value': params['resource_name'], 'include': False},
  1745. 'rolebinding_name': {'value': params['rolebinding_name'], 'include': False},
  1746. })
  1747. policygroup = PolicyGroup(nconfig, params['debug'])
  1748. # Run the oc adm policy group related command
  1749. ########
  1750. # Delete
  1751. ########
  1752. if state == 'absent':
  1753. if not policygroup.exists():
  1754. return {'changed': False, 'state': 'absent'}
  1755. if check_mode:
  1756. return {'changed': False, 'msg': 'CHECK_MODE: would have performed a delete.'}
  1757. api_rval = policygroup.perform()
  1758. if api_rval['returncode'] != 0:
  1759. return {'msg': api_rval}
  1760. return {'changed': True, 'results' : api_rval, state:'absent'}
  1761. if state == 'present':
  1762. ########
  1763. # Create
  1764. ########
  1765. results = policygroup.exists()
  1766. if isinstance(results, dict) and 'returncode' in results and results['returncode'] != 0:
  1767. return {'msg': results}
  1768. if not results:
  1769. if check_mode:
  1770. return {'changed': False, 'msg': 'CHECK_MODE: would have performed a create.'}
  1771. api_rval = policygroup.perform()
  1772. if api_rval['returncode'] != 0:
  1773. return {'msg': api_rval}
  1774. return {'changed': True, 'results': api_rval, state: 'present'}
  1775. return {'changed': False, state: 'present'}
  1776. return {'failed': True, 'changed': False, 'results': 'Unknown state passed. %s' % state, state: 'unknown'}
  1777. # -*- -*- -*- End included fragment: class/oc_adm_policy_group.py -*- -*- -*-
  1778. # -*- -*- -*- Begin included fragment: ansible/oc_adm_policy_group.py -*- -*- -*-
  1779. def main():
  1780. '''
  1781. ansible oc adm module for group policy
  1782. '''
  1783. module = AnsibleModule(
  1784. argument_spec=dict(
  1785. state=dict(default='present', type='str',
  1786. choices=['present', 'absent']),
  1787. debug=dict(default=False, type='bool'),
  1788. resource_name=dict(required=True, type='str'),
  1789. namespace=dict(default='default', type='str'),
  1790. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  1791. group=dict(required=True, type='str'),
  1792. resource_kind=dict(required=True, choices=['role', 'cluster-role', 'scc'], type='str'),
  1793. rolebinding_name=dict(default=None, type='str'),
  1794. ),
  1795. supports_check_mode=True,
  1796. )
  1797. results = PolicyGroup.run_ansible(module.params, module.check_mode)
  1798. if 'failed' in results:
  1799. module.fail_json(**results)
  1800. module.exit_json(**results)
  1801. if __name__ == "__main__":
  1802. main()
  1803. # -*- -*- -*- End included fragment: ansible/oc_adm_policy_group.py -*- -*- -*-