oc_serviceaccount.py 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583
  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 json
  35. import os
  36. import re
  37. import shutil
  38. import subprocess
  39. import tempfile
  40. # pylint: disable=import-error
  41. import ruamel.yaml as yaml
  42. from ansible.module_utils.basic import AnsibleModule
  43. # -*- -*- -*- End included fragment: lib/import.py -*- -*- -*-
  44. # -*- -*- -*- Begin included fragment: doc/serviceaccount -*- -*- -*-
  45. DOCUMENTATION = '''
  46. ---
  47. module: oc_serviceaccount
  48. short_description: Module to manage openshift service accounts
  49. description:
  50. - Manage openshift service accounts programmatically.
  51. options:
  52. state:
  53. description:
  54. - If present, the service account will be created if it doesn't exist or updated if different. If absent, the service account will be removed if present. If list, information about the service account will be gathered and returned as part of the Ansible call results.
  55. required: false
  56. default: present
  57. choices: ["present", "absent", "list"]
  58. aliases: []
  59. kubeconfig:
  60. description:
  61. - The path for the kubeconfig file to use for authentication
  62. required: false
  63. default: /etc/origin/master/admin.kubeconfig
  64. aliases: []
  65. debug:
  66. description:
  67. - Turn on debug output.
  68. required: false
  69. default: false
  70. aliases: []
  71. name:
  72. description:
  73. - Name of the service account.
  74. required: true
  75. default: None
  76. aliases: []
  77. namespace:
  78. description:
  79. - Namespace of the service account.
  80. required: true
  81. default: default
  82. aliases: []
  83. secrets:
  84. description:
  85. - A list of secrets that are associated with the service account.
  86. required: false
  87. default: None
  88. aliases: []
  89. image_pull_secrets:
  90. description:
  91. - A list of the image pull secrets that are associated with the service account.
  92. required: false
  93. default: None
  94. aliases: []
  95. author:
  96. - "Kenny Woodson <kwoodson@redhat.com>"
  97. extends_documentation_fragment: []
  98. '''
  99. EXAMPLES = '''
  100. - name: create registry serviceaccount
  101. oc_serviceaccount:
  102. name: registry
  103. namespace: default
  104. secrets:
  105. - docker-registry-config
  106. - registry-secret
  107. register: sa_out
  108. '''
  109. # -*- -*- -*- End included fragment: doc/serviceaccount -*- -*- -*-
  110. # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  111. # noqa: E301,E302
  112. class YeditException(Exception):
  113. ''' Exception class for Yedit '''
  114. pass
  115. # pylint: disable=too-many-public-methods
  116. class Yedit(object):
  117. ''' Class to modify yaml files '''
  118. re_valid_key = r"(((\[-?\d+\])|([0-9a-zA-Z%s/_-]+)).?)+$"
  119. re_key = r"(?:\[(-?\d+)\])|([0-9a-zA-Z%s/_-]+)"
  120. com_sep = set(['.', '#', '|', ':'])
  121. # pylint: disable=too-many-arguments
  122. def __init__(self,
  123. filename=None,
  124. content=None,
  125. content_type='yaml',
  126. separator='.',
  127. backup=False):
  128. self.content = content
  129. self._separator = separator
  130. self.filename = filename
  131. self.__yaml_dict = content
  132. self.content_type = content_type
  133. self.backup = backup
  134. self.load(content_type=self.content_type)
  135. if self.__yaml_dict is None:
  136. self.__yaml_dict = {}
  137. @property
  138. def separator(self):
  139. ''' getter method for yaml_dict '''
  140. return self._separator
  141. @separator.setter
  142. def separator(self):
  143. ''' getter method for yaml_dict '''
  144. return self._separator
  145. @property
  146. def yaml_dict(self):
  147. ''' getter method for yaml_dict '''
  148. return self.__yaml_dict
  149. @yaml_dict.setter
  150. def yaml_dict(self, value):
  151. ''' setter method for yaml_dict '''
  152. self.__yaml_dict = value
  153. @staticmethod
  154. def parse_key(key, sep='.'):
  155. '''parse the key allowing the appropriate separator'''
  156. common_separators = list(Yedit.com_sep - set([sep]))
  157. return re.findall(Yedit.re_key % ''.join(common_separators), key)
  158. @staticmethod
  159. def valid_key(key, sep='.'):
  160. '''validate the incoming key'''
  161. common_separators = list(Yedit.com_sep - set([sep]))
  162. if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
  163. return False
  164. return True
  165. @staticmethod
  166. def remove_entry(data, key, sep='.'):
  167. ''' remove data at location key '''
  168. if key == '' and isinstance(data, dict):
  169. data.clear()
  170. return True
  171. elif key == '' and isinstance(data, list):
  172. del data[:]
  173. return True
  174. if not (key and Yedit.valid_key(key, sep)) and \
  175. isinstance(data, (list, dict)):
  176. return None
  177. key_indexes = Yedit.parse_key(key, sep)
  178. for arr_ind, dict_key in key_indexes[:-1]:
  179. if dict_key and isinstance(data, dict):
  180. data = data.get(dict_key, None)
  181. elif (arr_ind and isinstance(data, list) and
  182. int(arr_ind) <= len(data) - 1):
  183. data = data[int(arr_ind)]
  184. else:
  185. return None
  186. # process last index for remove
  187. # expected list entry
  188. if key_indexes[-1][0]:
  189. if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  190. del data[int(key_indexes[-1][0])]
  191. return True
  192. # expected dict entry
  193. elif key_indexes[-1][1]:
  194. if isinstance(data, dict):
  195. del data[key_indexes[-1][1]]
  196. return True
  197. @staticmethod
  198. def add_entry(data, key, item=None, sep='.'):
  199. ''' Get an item from a dictionary with key notation a.b.c
  200. d = {'a': {'b': 'c'}}}
  201. key = a#b
  202. return c
  203. '''
  204. if key == '':
  205. pass
  206. elif (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:
  212. if isinstance(data, dict) and dict_key in data and data[dict_key]: # noqa: E501
  213. data = data[dict_key]
  214. continue
  215. elif data and not isinstance(data, dict):
  216. return None
  217. data[dict_key] = {}
  218. data = data[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. if key == '':
  225. data = item
  226. # process last index for add
  227. # expected list entry
  228. elif key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  229. data[int(key_indexes[-1][0])] = item
  230. # expected dict entry
  231. elif key_indexes[-1][1] and isinstance(data, dict):
  232. data[key_indexes[-1][1]] = item
  233. return data
  234. @staticmethod
  235. def get_entry(data, key, sep='.'):
  236. ''' Get an item from a dictionary with key notation a.b.c
  237. d = {'a': {'b': 'c'}}}
  238. key = a.b
  239. return c
  240. '''
  241. if key == '':
  242. pass
  243. elif (not (key and Yedit.valid_key(key, sep)) and
  244. isinstance(data, (list, dict))):
  245. return None
  246. key_indexes = Yedit.parse_key(key, sep)
  247. for arr_ind, dict_key in key_indexes:
  248. if dict_key and isinstance(data, dict):
  249. data = data.get(dict_key, None)
  250. elif (arr_ind and isinstance(data, list) and
  251. int(arr_ind) <= len(data) - 1):
  252. data = data[int(arr_ind)]
  253. else:
  254. return None
  255. return data
  256. @staticmethod
  257. def _write(filename, contents):
  258. ''' Actually write the file contents to disk. This helps with mocking. '''
  259. tmp_filename = filename + '.yedit'
  260. with open(tmp_filename, 'w') as yfd:
  261. yfd.write(contents)
  262. os.rename(tmp_filename, filename)
  263. def write(self):
  264. ''' write to file '''
  265. if not self.filename:
  266. raise YeditException('Please specify a filename.')
  267. if self.backup and self.file_exists():
  268. shutil.copy(self.filename, self.filename + '.orig')
  269. # pylint: disable=no-member
  270. if hasattr(self.yaml_dict, 'fa'):
  271. self.yaml_dict.fa.set_block_style()
  272. Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper))
  273. return (True, self.yaml_dict)
  274. def read(self):
  275. ''' read from file '''
  276. # check if it exists
  277. if self.filename is None or not self.file_exists():
  278. return None
  279. contents = None
  280. with open(self.filename) as yfd:
  281. contents = yfd.read()
  282. return contents
  283. def file_exists(self):
  284. ''' return whether file exists '''
  285. if os.path.exists(self.filename):
  286. return True
  287. return False
  288. def load(self, content_type='yaml'):
  289. ''' return yaml file '''
  290. contents = self.read()
  291. if not contents and not self.content:
  292. return None
  293. if self.content:
  294. if isinstance(self.content, dict):
  295. self.yaml_dict = self.content
  296. return self.yaml_dict
  297. elif isinstance(self.content, str):
  298. contents = self.content
  299. # check if it is yaml
  300. try:
  301. if content_type == 'yaml' and contents:
  302. self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader)
  303. # pylint: disable=no-member
  304. if hasattr(self.yaml_dict, 'fa'):
  305. self.yaml_dict.fa.set_block_style()
  306. elif content_type == 'json' and contents:
  307. self.yaml_dict = json.loads(contents)
  308. except yaml.YAMLError as err:
  309. # Error loading yaml or json
  310. raise YeditException('Problem with loading yaml file. %s' % err)
  311. return self.yaml_dict
  312. def get(self, key):
  313. ''' get a specified key'''
  314. try:
  315. entry = Yedit.get_entry(self.yaml_dict, key, self.separator)
  316. except KeyError:
  317. entry = None
  318. return entry
  319. def pop(self, path, key_or_item):
  320. ''' remove a key, value pair from a dict or an item for a list'''
  321. try:
  322. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  323. except KeyError:
  324. entry = None
  325. if entry is None:
  326. return (False, self.yaml_dict)
  327. if isinstance(entry, dict):
  328. # pylint: disable=no-member,maybe-no-member
  329. if key_or_item in entry:
  330. entry.pop(key_or_item)
  331. return (True, self.yaml_dict)
  332. return (False, self.yaml_dict)
  333. elif isinstance(entry, list):
  334. # pylint: disable=no-member,maybe-no-member
  335. ind = None
  336. try:
  337. ind = entry.index(key_or_item)
  338. except ValueError:
  339. return (False, self.yaml_dict)
  340. entry.pop(ind)
  341. return (True, self.yaml_dict)
  342. return (False, self.yaml_dict)
  343. def delete(self, path):
  344. ''' remove path from a dict'''
  345. try:
  346. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  347. except KeyError:
  348. entry = None
  349. if entry is None:
  350. return (False, self.yaml_dict)
  351. result = Yedit.remove_entry(self.yaml_dict, path, self.separator)
  352. if not result:
  353. return (False, self.yaml_dict)
  354. return (True, self.yaml_dict)
  355. def exists(self, path, value):
  356. ''' check if value exists at path'''
  357. try:
  358. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  359. except KeyError:
  360. entry = None
  361. if isinstance(entry, list):
  362. if value in entry:
  363. return True
  364. return False
  365. elif isinstance(entry, dict):
  366. if isinstance(value, dict):
  367. rval = False
  368. for key, val in value.items():
  369. if entry[key] != val:
  370. rval = False
  371. break
  372. else:
  373. rval = True
  374. return rval
  375. return value in entry
  376. return entry == value
  377. def append(self, path, value):
  378. '''append value to a list'''
  379. try:
  380. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  381. except KeyError:
  382. entry = None
  383. if entry is None:
  384. self.put(path, [])
  385. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  386. if not isinstance(entry, list):
  387. return (False, self.yaml_dict)
  388. # pylint: disable=no-member,maybe-no-member
  389. entry.append(value)
  390. return (True, self.yaml_dict)
  391. # pylint: disable=too-many-arguments
  392. def update(self, path, value, index=None, curr_value=None):
  393. ''' put path, value into a dict '''
  394. try:
  395. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  396. except KeyError:
  397. entry = None
  398. if isinstance(entry, dict):
  399. # pylint: disable=no-member,maybe-no-member
  400. if not isinstance(value, dict):
  401. raise YeditException('Cannot replace key, value entry in ' +
  402. 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501
  403. entry.update(value)
  404. return (True, self.yaml_dict)
  405. elif isinstance(entry, list):
  406. # pylint: disable=no-member,maybe-no-member
  407. ind = None
  408. if curr_value:
  409. try:
  410. ind = entry.index(curr_value)
  411. except ValueError:
  412. return (False, self.yaml_dict)
  413. elif index is not None:
  414. ind = index
  415. if ind is not None and entry[ind] != value:
  416. entry[ind] = value
  417. return (True, self.yaml_dict)
  418. # see if it exists in the list
  419. try:
  420. ind = entry.index(value)
  421. except ValueError:
  422. # doesn't exist, append it
  423. entry.append(value)
  424. return (True, self.yaml_dict)
  425. # already exists, return
  426. if ind is not None:
  427. return (False, self.yaml_dict)
  428. return (False, self.yaml_dict)
  429. def put(self, path, value):
  430. ''' put path, value into a dict '''
  431. try:
  432. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  433. except KeyError:
  434. entry = None
  435. if entry == value:
  436. return (False, self.yaml_dict)
  437. # deepcopy didn't work
  438. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  439. default_flow_style=False),
  440. yaml.RoundTripLoader)
  441. # pylint: disable=no-member
  442. if hasattr(self.yaml_dict, 'fa'):
  443. tmp_copy.fa.set_block_style()
  444. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  445. if not result:
  446. return (False, self.yaml_dict)
  447. self.yaml_dict = tmp_copy
  448. return (True, self.yaml_dict)
  449. def create(self, path, value):
  450. ''' create a yaml file '''
  451. if not self.file_exists():
  452. # deepcopy didn't work
  453. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, default_flow_style=False), # noqa: E501
  454. yaml.RoundTripLoader)
  455. # pylint: disable=no-member
  456. if hasattr(self.yaml_dict, 'fa'):
  457. tmp_copy.fa.set_block_style()
  458. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  459. if result:
  460. self.yaml_dict = tmp_copy
  461. return (True, self.yaml_dict)
  462. return (False, self.yaml_dict)
  463. @staticmethod
  464. def get_curr_value(invalue, val_type):
  465. '''return the current value'''
  466. if invalue is None:
  467. return None
  468. curr_value = invalue
  469. if val_type == 'yaml':
  470. curr_value = yaml.load(invalue)
  471. elif val_type == 'json':
  472. curr_value = json.loads(invalue)
  473. return curr_value
  474. @staticmethod
  475. def parse_value(inc_value, vtype=''):
  476. '''determine value type passed'''
  477. true_bools = ['y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE',
  478. 'on', 'On', 'ON', ]
  479. false_bools = ['n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE',
  480. 'off', 'Off', 'OFF']
  481. # It came in as a string but you didn't specify value_type as string
  482. # we will convert to bool if it matches any of the above cases
  483. if isinstance(inc_value, str) and 'bool' in vtype:
  484. if inc_value not in true_bools and inc_value not in false_bools:
  485. raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
  486. % (inc_value, vtype))
  487. elif isinstance(inc_value, bool) and 'str' in vtype:
  488. inc_value = str(inc_value)
  489. # If vtype is not str then go ahead and attempt to yaml load it.
  490. if isinstance(inc_value, str) and 'str' not in vtype:
  491. try:
  492. inc_value = yaml.load(inc_value)
  493. except Exception:
  494. raise YeditException('Could not determine type of incoming ' +
  495. 'value. value=[%s] vtype=[%s]'
  496. % (type(inc_value), vtype))
  497. return inc_value
  498. # pylint: disable=too-many-return-statements,too-many-branches
  499. @staticmethod
  500. def run_ansible(module):
  501. '''perform the idempotent crud operations'''
  502. yamlfile = Yedit(filename=module.params['src'],
  503. backup=module.params['backup'],
  504. separator=module.params['separator'])
  505. if module.params['src']:
  506. rval = yamlfile.load()
  507. if yamlfile.yaml_dict is None and \
  508. module.params['state'] != 'present':
  509. return {'failed': True,
  510. 'msg': 'Error opening file [%s]. Verify that the ' +
  511. 'file exists, that it is has correct' +
  512. ' permissions, and is valid yaml.'}
  513. if module.params['state'] == 'list':
  514. if module.params['content']:
  515. content = Yedit.parse_value(module.params['content'],
  516. module.params['content_type'])
  517. yamlfile.yaml_dict = content
  518. if module.params['key']:
  519. rval = yamlfile.get(module.params['key']) or {}
  520. return {'changed': False, 'result': rval, 'state': "list"}
  521. elif module.params['state'] == 'absent':
  522. if module.params['content']:
  523. content = Yedit.parse_value(module.params['content'],
  524. module.params['content_type'])
  525. yamlfile.yaml_dict = content
  526. if module.params['update']:
  527. rval = yamlfile.pop(module.params['key'],
  528. module.params['value'])
  529. else:
  530. rval = yamlfile.delete(module.params['key'])
  531. if rval[0] and module.params['src']:
  532. yamlfile.write()
  533. return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
  534. elif module.params['state'] == 'present':
  535. # check if content is different than what is in the file
  536. if module.params['content']:
  537. content = Yedit.parse_value(module.params['content'],
  538. module.params['content_type'])
  539. # We had no edits to make and the contents are the same
  540. if yamlfile.yaml_dict == content and \
  541. module.params['value'] is None:
  542. return {'changed': False,
  543. 'result': yamlfile.yaml_dict,
  544. 'state': "present"}
  545. yamlfile.yaml_dict = content
  546. # we were passed a value; parse it
  547. if module.params['value']:
  548. value = Yedit.parse_value(module.params['value'],
  549. module.params['value_type'])
  550. key = module.params['key']
  551. if module.params['update']:
  552. # pylint: disable=line-too-long
  553. curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']), # noqa: E501
  554. module.params['curr_value_format']) # noqa: E501
  555. rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501
  556. elif module.params['append']:
  557. rval = yamlfile.append(key, value)
  558. else:
  559. rval = yamlfile.put(key, value)
  560. if rval[0] and module.params['src']:
  561. yamlfile.write()
  562. return {'changed': rval[0],
  563. 'result': rval[1], 'state': "present"}
  564. # no edits to make
  565. if module.params['src']:
  566. # pylint: disable=redefined-variable-type
  567. rval = yamlfile.write()
  568. return {'changed': rval[0],
  569. 'result': rval[1],
  570. 'state': "present"}
  571. return {'failed': True, 'msg': 'Unkown state passed'}
  572. # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  573. # -*- -*- -*- Begin included fragment: lib/base.py -*- -*- -*-
  574. # pylint: disable=too-many-lines
  575. # noqa: E301,E302,E303,T001
  576. class OpenShiftCLIError(Exception):
  577. '''Exception class for openshiftcli'''
  578. pass
  579. # pylint: disable=too-few-public-methods
  580. class OpenShiftCLI(object):
  581. ''' Class to wrap the command line tools '''
  582. def __init__(self,
  583. namespace,
  584. kubeconfig='/etc/origin/master/admin.kubeconfig',
  585. verbose=False,
  586. all_namespaces=False):
  587. ''' Constructor for OpenshiftCLI '''
  588. self.namespace = namespace
  589. self.verbose = verbose
  590. self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
  591. self.all_namespaces = all_namespaces
  592. # Pylint allows only 5 arguments to be passed.
  593. # pylint: disable=too-many-arguments
  594. def _replace_content(self, resource, rname, content, force=False, sep='.'):
  595. ''' replace the current object with the content '''
  596. res = self._get(resource, rname)
  597. if not res['results']:
  598. return res
  599. fname = Utils.create_tmpfile(rname + '-')
  600. yed = Yedit(fname, res['results'][0], separator=sep)
  601. changes = []
  602. for key, value in content.items():
  603. changes.append(yed.put(key, value))
  604. if any([change[0] for change in changes]):
  605. yed.write()
  606. atexit.register(Utils.cleanup, [fname])
  607. return self._replace(fname, force)
  608. return {'returncode': 0, 'updated': False}
  609. def _replace(self, fname, force=False):
  610. '''replace the current object with oc replace'''
  611. cmd = ['replace', '-f', fname]
  612. if force:
  613. cmd.append('--force')
  614. return self.openshift_cmd(cmd)
  615. def _create_from_content(self, rname, content):
  616. '''create a temporary file and then call oc create on it'''
  617. fname = Utils.create_tmpfile(rname + '-')
  618. yed = Yedit(fname, content=content)
  619. yed.write()
  620. atexit.register(Utils.cleanup, [fname])
  621. return self._create(fname)
  622. def _create(self, fname):
  623. '''call oc create on a filename'''
  624. return self.openshift_cmd(['create', '-f', fname])
  625. def _delete(self, resource, rname, selector=None):
  626. '''call oc delete on a resource'''
  627. cmd = ['delete', resource, rname]
  628. if selector:
  629. cmd.append('--selector=%s' % selector)
  630. return self.openshift_cmd(cmd)
  631. def _process(self, template_name, create=False, params=None, template_data=None): # noqa: E501
  632. '''process a template
  633. template_name: the name of the template to process
  634. create: whether to send to oc create after processing
  635. params: the parameters for the template
  636. template_data: the incoming template's data; instead of a file
  637. '''
  638. cmd = ['process']
  639. if template_data:
  640. cmd.extend(['-f', '-'])
  641. else:
  642. cmd.append(template_name)
  643. if params:
  644. param_str = ["%s=%s" % (key, value) for key, value in params.items()]
  645. cmd.append('-v')
  646. cmd.extend(param_str)
  647. results = self.openshift_cmd(cmd, output=True, input_data=template_data)
  648. if results['returncode'] != 0 or not create:
  649. return results
  650. fname = Utils.create_tmpfile(template_name + '-')
  651. yed = Yedit(fname, results['results'])
  652. yed.write()
  653. atexit.register(Utils.cleanup, [fname])
  654. return self.openshift_cmd(['create', '-f', fname])
  655. def _get(self, resource, rname=None, selector=None):
  656. '''return a resource by name '''
  657. cmd = ['get', resource]
  658. if selector:
  659. cmd.append('--selector=%s' % selector)
  660. elif rname:
  661. cmd.append(rname)
  662. cmd.extend(['-o', 'json'])
  663. rval = self.openshift_cmd(cmd, output=True)
  664. # Ensure results are retuned in an array
  665. if 'items' in rval:
  666. rval['results'] = rval['items']
  667. elif not isinstance(rval['results'], list):
  668. rval['results'] = [rval['results']]
  669. return rval
  670. def _schedulable(self, node=None, selector=None, schedulable=True):
  671. ''' perform oadm manage-node scheduable '''
  672. cmd = ['manage-node']
  673. if node:
  674. cmd.extend(node)
  675. else:
  676. cmd.append('--selector=%s' % selector)
  677. cmd.append('--schedulable=%s' % schedulable)
  678. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') # noqa: E501
  679. def _list_pods(self, node=None, selector=None, pod_selector=None):
  680. ''' perform oadm list pods
  681. node: the node in which to list pods
  682. selector: the label selector filter if provided
  683. pod_selector: the pod selector filter if provided
  684. '''
  685. cmd = ['manage-node']
  686. if node:
  687. cmd.extend(node)
  688. else:
  689. cmd.append('--selector=%s' % selector)
  690. if pod_selector:
  691. cmd.append('--pod-selector=%s' % pod_selector)
  692. cmd.extend(['--list-pods', '-o', 'json'])
  693. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  694. # pylint: disable=too-many-arguments
  695. def _evacuate(self, node=None, selector=None, pod_selector=None, dry_run=False, grace_period=None, force=False):
  696. ''' perform oadm manage-node evacuate '''
  697. cmd = ['manage-node']
  698. if node:
  699. cmd.extend(node)
  700. else:
  701. cmd.append('--selector=%s' % selector)
  702. if dry_run:
  703. cmd.append('--dry-run')
  704. if pod_selector:
  705. cmd.append('--pod-selector=%s' % pod_selector)
  706. if grace_period:
  707. cmd.append('--grace-period=%s' % int(grace_period))
  708. if force:
  709. cmd.append('--force')
  710. cmd.append('--evacuate')
  711. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  712. def _version(self):
  713. ''' return the openshift version'''
  714. return self.openshift_cmd(['version'], output=True, output_type='raw')
  715. def _import_image(self, url=None, name=None, tag=None):
  716. ''' perform image import '''
  717. cmd = ['import-image']
  718. image = '{0}'.format(name)
  719. if tag:
  720. image += ':{0}'.format(tag)
  721. cmd.append(image)
  722. if url:
  723. cmd.append('--from={0}/{1}'.format(url, image))
  724. cmd.append('-n{0}'.format(self.namespace))
  725. cmd.append('--confirm')
  726. return self.openshift_cmd(cmd)
  727. def _run(self, cmds, input_data):
  728. ''' Actually executes the command. This makes mocking easier. '''
  729. curr_env = os.environ.copy()
  730. curr_env.update({'KUBECONFIG': self.kubeconfig})
  731. proc = subprocess.Popen(cmds,
  732. stdin=subprocess.PIPE,
  733. stdout=subprocess.PIPE,
  734. stderr=subprocess.PIPE,
  735. env=curr_env)
  736. stdout, stderr = proc.communicate(input_data)
  737. return proc.returncode, stdout, stderr
  738. # pylint: disable=too-many-arguments,too-many-branches
  739. def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
  740. '''Base command for oc '''
  741. cmds = []
  742. if oadm:
  743. cmds = ['oadm']
  744. else:
  745. cmds = ['oc']
  746. if self.all_namespaces:
  747. cmds.extend(['--all-namespaces'])
  748. elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501
  749. cmds.extend(['-n', self.namespace])
  750. cmds.extend(cmd)
  751. rval = {}
  752. results = ''
  753. err = None
  754. if self.verbose:
  755. print(' '.join(cmds))
  756. returncode, stdout, stderr = self._run(cmds, input_data)
  757. rval = {"returncode": returncode,
  758. "results": results,
  759. "cmd": ' '.join(cmds)}
  760. if returncode == 0:
  761. if output:
  762. if output_type == 'json':
  763. try:
  764. rval['results'] = json.loads(stdout)
  765. except ValueError as err:
  766. if "No JSON object could be decoded" in err.args:
  767. err = err.args
  768. elif output_type == 'raw':
  769. rval['results'] = stdout
  770. if self.verbose:
  771. print("STDOUT: {0}".format(stdout))
  772. print("STDERR: {0}".format(stderr))
  773. if err:
  774. rval.update({"err": err,
  775. "stderr": stderr,
  776. "stdout": stdout,
  777. "cmd": cmds})
  778. else:
  779. rval.update({"stderr": stderr,
  780. "stdout": stdout,
  781. "results": {}})
  782. return rval
  783. class Utils(object):
  784. ''' utilities for openshiftcli modules '''
  785. @staticmethod
  786. def _write(filename, contents):
  787. ''' Actually write the file contents to disk. This helps with mocking. '''
  788. with open(filename, 'w') as sfd:
  789. sfd.write(contents)
  790. @staticmethod
  791. def create_tmp_file_from_contents(rname, data, ftype='yaml'):
  792. ''' create a file in tmp with name and contents'''
  793. tmp = Utils.create_tmpfile(prefix=rname)
  794. if ftype == 'yaml':
  795. Utils._write(tmp, yaml.dump(data, Dumper=yaml.RoundTripDumper))
  796. elif ftype == 'json':
  797. Utils._write(tmp, json.dumps(data))
  798. else:
  799. Utils._write(tmp, data)
  800. # Register cleanup when module is done
  801. atexit.register(Utils.cleanup, [tmp])
  802. return tmp
  803. @staticmethod
  804. def create_tmpfile_copy(inc_file):
  805. '''create a temporary copy of a file'''
  806. tmpfile = Utils.create_tmpfile('lib_openshift-')
  807. Utils._write(tmpfile, open(inc_file).read())
  808. # Cleanup the tmpfile
  809. atexit.register(Utils.cleanup, [tmpfile])
  810. return tmpfile
  811. @staticmethod
  812. def create_tmpfile(prefix='tmp'):
  813. ''' Generates and returns a temporary file name '''
  814. with tempfile.NamedTemporaryFile(prefix=prefix, delete=False) as tmp:
  815. return tmp.name
  816. @staticmethod
  817. def create_tmp_files_from_contents(content, content_type=None):
  818. '''Turn an array of dict: filename, content into a files array'''
  819. if not isinstance(content, list):
  820. content = [content]
  821. files = []
  822. for item in content:
  823. path = Utils.create_tmp_file_from_contents(item['path'] + '-',
  824. item['data'],
  825. ftype=content_type)
  826. files.append({'name': os.path.basename(item['path']),
  827. 'path': path})
  828. return files
  829. @staticmethod
  830. def cleanup(files):
  831. '''Clean up on exit '''
  832. for sfile in files:
  833. if os.path.exists(sfile):
  834. if os.path.isdir(sfile):
  835. shutil.rmtree(sfile)
  836. elif os.path.isfile(sfile):
  837. os.remove(sfile)
  838. @staticmethod
  839. def exists(results, _name):
  840. ''' Check to see if the results include the name '''
  841. if not results:
  842. return False
  843. if Utils.find_result(results, _name):
  844. return True
  845. return False
  846. @staticmethod
  847. def find_result(results, _name):
  848. ''' Find the specified result by name'''
  849. rval = None
  850. for result in results:
  851. if 'metadata' in result and result['metadata']['name'] == _name:
  852. rval = result
  853. break
  854. return rval
  855. @staticmethod
  856. def get_resource_file(sfile, sfile_type='yaml'):
  857. ''' return the service file '''
  858. contents = None
  859. with open(sfile) as sfd:
  860. contents = sfd.read()
  861. if sfile_type == 'yaml':
  862. contents = yaml.load(contents, yaml.RoundTripLoader)
  863. elif sfile_type == 'json':
  864. contents = json.loads(contents)
  865. return contents
  866. @staticmethod
  867. def filter_versions(stdout):
  868. ''' filter the oc version output '''
  869. version_dict = {}
  870. version_search = ['oc', 'openshift', 'kubernetes']
  871. for line in stdout.strip().split('\n'):
  872. for term in version_search:
  873. if not line:
  874. continue
  875. if line.startswith(term):
  876. version_dict[term] = line.split()[-1]
  877. # horrible hack to get openshift version in Openshift 3.2
  878. # By default "oc version in 3.2 does not return an "openshift" version
  879. if "openshift" not in version_dict:
  880. version_dict["openshift"] = version_dict["oc"]
  881. return version_dict
  882. @staticmethod
  883. def add_custom_versions(versions):
  884. ''' create custom versions strings '''
  885. versions_dict = {}
  886. for tech, version in versions.items():
  887. # clean up "-" from version
  888. if "-" in version:
  889. version = version.split("-")[0]
  890. if version.startswith('v'):
  891. versions_dict[tech + '_numeric'] = version[1:].split('+')[0]
  892. # "v3.3.0.33" is what we have, we want "3.3"
  893. versions_dict[tech + '_short'] = version[1:4]
  894. return versions_dict
  895. @staticmethod
  896. def openshift_installed():
  897. ''' check if openshift is installed '''
  898. import yum
  899. yum_base = yum.YumBase()
  900. if yum_base.rpmdb.searchNevra(name='atomic-openshift'):
  901. return True
  902. return False
  903. # Disabling too-many-branches. This is a yaml dictionary comparison function
  904. # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
  905. @staticmethod
  906. def check_def_equal(user_def, result_def, skip_keys=None, debug=False):
  907. ''' Given a user defined definition, compare it with the results given back by our query. '''
  908. # Currently these values are autogenerated and we do not need to check them
  909. skip = ['metadata', 'status']
  910. if skip_keys:
  911. skip.extend(skip_keys)
  912. for key, value in result_def.items():
  913. if key in skip:
  914. continue
  915. # Both are lists
  916. if isinstance(value, list):
  917. if key not in user_def:
  918. if debug:
  919. print('User data does not have key [%s]' % key)
  920. print('User data: %s' % user_def)
  921. return False
  922. if not isinstance(user_def[key], list):
  923. if debug:
  924. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  925. return False
  926. if len(user_def[key]) != len(value):
  927. if debug:
  928. print("List lengths are not equal.")
  929. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  930. print("user_def: %s" % user_def[key])
  931. print("value: %s" % value)
  932. return False
  933. for values in zip(user_def[key], value):
  934. if isinstance(values[0], dict) and isinstance(values[1], dict):
  935. if debug:
  936. print('sending list - list')
  937. print(type(values[0]))
  938. print(type(values[1]))
  939. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  940. if not result:
  941. print('list compare returned false')
  942. return False
  943. elif value != user_def[key]:
  944. if debug:
  945. print('value should be identical')
  946. print(value)
  947. print(user_def[key])
  948. return False
  949. # recurse on a dictionary
  950. elif isinstance(value, dict):
  951. if key not in user_def:
  952. if debug:
  953. print("user_def does not have key [%s]" % key)
  954. return False
  955. if not isinstance(user_def[key], dict):
  956. if debug:
  957. print("dict returned false: not instance of dict")
  958. return False
  959. # before passing ensure keys match
  960. api_values = set(value.keys()) - set(skip)
  961. user_values = set(user_def[key].keys()) - set(skip)
  962. if api_values != user_values:
  963. if debug:
  964. print("keys are not equal in dict")
  965. print(api_values)
  966. print(user_values)
  967. return False
  968. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  969. if not result:
  970. if debug:
  971. print("dict returned false")
  972. print(result)
  973. return False
  974. # Verify each key, value pair is the same
  975. else:
  976. if key not in user_def or value != user_def[key]:
  977. if debug:
  978. print("value not equal; user_def does not have key")
  979. print(key)
  980. print(value)
  981. if key in user_def:
  982. print(user_def[key])
  983. return False
  984. if debug:
  985. print('returning true')
  986. return True
  987. class OpenShiftCLIConfig(object):
  988. '''Generic Config'''
  989. def __init__(self, rname, namespace, kubeconfig, options):
  990. self.kubeconfig = kubeconfig
  991. self.name = rname
  992. self.namespace = namespace
  993. self._options = options
  994. @property
  995. def config_options(self):
  996. ''' return config options '''
  997. return self._options
  998. def to_option_list(self):
  999. '''return all options as a string'''
  1000. return self.stringify()
  1001. def stringify(self):
  1002. ''' return the options hash as cli params in a string '''
  1003. rval = []
  1004. for key, data in self.config_options.items():
  1005. if data['include'] \
  1006. and (data['value'] or isinstance(data['value'], int)):
  1007. rval.append('--%s=%s' % (key.replace('_', '-'), data['value']))
  1008. return rval
  1009. # -*- -*- -*- End included fragment: lib/base.py -*- -*- -*-
  1010. # -*- -*- -*- Begin included fragment: lib/serviceaccount.py -*- -*- -*-
  1011. class ServiceAccountConfig(object):
  1012. '''Service account config class
  1013. This class stores the options and returns a default service account
  1014. '''
  1015. # pylint: disable=too-many-arguments
  1016. def __init__(self, sname, namespace, kubeconfig, secrets=None, image_pull_secrets=None):
  1017. self.name = sname
  1018. self.kubeconfig = kubeconfig
  1019. self.namespace = namespace
  1020. self.secrets = secrets or []
  1021. self.image_pull_secrets = image_pull_secrets or []
  1022. self.data = {}
  1023. self.create_dict()
  1024. def create_dict(self):
  1025. ''' return a properly structured volume '''
  1026. self.data['apiVersion'] = 'v1'
  1027. self.data['kind'] = 'ServiceAccount'
  1028. self.data['metadata'] = {}
  1029. self.data['metadata']['name'] = self.name
  1030. self.data['metadata']['namespace'] = self.namespace
  1031. self.data['secrets'] = []
  1032. if self.secrets:
  1033. for sec in self.secrets:
  1034. self.data['secrets'].append({"name": sec})
  1035. self.data['imagePullSecrets'] = []
  1036. if self.image_pull_secrets:
  1037. for sec in self.image_pull_secrets:
  1038. self.data['imagePullSecrets'].append({"name": sec})
  1039. class ServiceAccount(Yedit):
  1040. ''' Class to wrap the oc command line tools '''
  1041. image_pull_secrets_path = "imagePullSecrets"
  1042. secrets_path = "secrets"
  1043. def __init__(self, content):
  1044. '''ServiceAccount constructor'''
  1045. super(ServiceAccount, self).__init__(content=content)
  1046. self._secrets = None
  1047. self._image_pull_secrets = None
  1048. @property
  1049. def image_pull_secrets(self):
  1050. ''' property for image_pull_secrets '''
  1051. if self._image_pull_secrets is None:
  1052. self._image_pull_secrets = self.get(ServiceAccount.image_pull_secrets_path) or []
  1053. return self._image_pull_secrets
  1054. @image_pull_secrets.setter
  1055. def image_pull_secrets(self, secrets):
  1056. ''' property for secrets '''
  1057. self._image_pull_secrets = secrets
  1058. @property
  1059. def secrets(self):
  1060. ''' property for secrets '''
  1061. if not self._secrets:
  1062. self._secrets = self.get(ServiceAccount.secrets_path) or []
  1063. return self._secrets
  1064. @secrets.setter
  1065. def secrets(self, secrets):
  1066. ''' property for secrets '''
  1067. self._secrets = secrets
  1068. def delete_secret(self, inc_secret):
  1069. ''' remove a secret '''
  1070. remove_idx = None
  1071. for idx, sec in enumerate(self.secrets):
  1072. if sec['name'] == inc_secret:
  1073. remove_idx = idx
  1074. break
  1075. if remove_idx:
  1076. del self.secrets[remove_idx]
  1077. return True
  1078. return False
  1079. def delete_image_pull_secret(self, inc_secret):
  1080. ''' remove a image_pull_secret '''
  1081. remove_idx = None
  1082. for idx, sec in enumerate(self.image_pull_secrets):
  1083. if sec['name'] == inc_secret:
  1084. remove_idx = idx
  1085. break
  1086. if remove_idx:
  1087. del self.image_pull_secrets[remove_idx]
  1088. return True
  1089. return False
  1090. def find_secret(self, inc_secret):
  1091. '''find secret'''
  1092. for secret in self.secrets:
  1093. if secret['name'] == inc_secret:
  1094. return secret
  1095. return None
  1096. def find_image_pull_secret(self, inc_secret):
  1097. '''find secret'''
  1098. for secret in self.image_pull_secrets:
  1099. if secret['name'] == inc_secret:
  1100. return secret
  1101. return None
  1102. def add_secret(self, inc_secret):
  1103. '''add secret'''
  1104. if self.secrets:
  1105. self.secrets.append({"name": inc_secret}) # pylint: disable=no-member
  1106. else:
  1107. self.put(ServiceAccount.secrets_path, [{"name": inc_secret}])
  1108. def add_image_pull_secret(self, inc_secret):
  1109. '''add image_pull_secret'''
  1110. if self.image_pull_secrets:
  1111. self.image_pull_secrets.append({"name": inc_secret}) # pylint: disable=no-member
  1112. else:
  1113. self.put(ServiceAccount.image_pull_secrets_path, [{"name": inc_secret}])
  1114. # -*- -*- -*- End included fragment: lib/serviceaccount.py -*- -*- -*-
  1115. # -*- -*- -*- Begin included fragment: class/oc_serviceaccount.py -*- -*- -*-
  1116. # pylint: disable=too-many-instance-attributes
  1117. class OCServiceAccount(OpenShiftCLI):
  1118. ''' Class to wrap the oc command line tools '''
  1119. kind = 'sa'
  1120. # pylint allows 5
  1121. # pylint: disable=too-many-arguments
  1122. def __init__(self,
  1123. config,
  1124. verbose=False):
  1125. ''' Constructor for OCVolume '''
  1126. super(OCServiceAccount, self).__init__(config.namespace, config.kubeconfig)
  1127. self.config = config
  1128. self.namespace = config.namespace
  1129. self.service_account = None
  1130. def exists(self):
  1131. ''' return whether a volume exists '''
  1132. if self.service_account:
  1133. return True
  1134. return False
  1135. def get(self):
  1136. '''return volume information '''
  1137. result = self._get(self.kind, self.config.name)
  1138. if result['returncode'] == 0:
  1139. self.service_account = ServiceAccount(content=result['results'][0])
  1140. elif '\"%s\" not found' % self.config.name in result['stderr']:
  1141. result['returncode'] = 0
  1142. result['results'] = [{}]
  1143. return result
  1144. def delete(self):
  1145. '''delete the object'''
  1146. return self._delete(self.kind, self.config.name)
  1147. def create(self):
  1148. '''create the object'''
  1149. return self._create_from_content(self.config.name, self.config.data)
  1150. def update(self):
  1151. '''update the object'''
  1152. # need to update the tls information and the service name
  1153. for secret in self.config.secrets:
  1154. result = self.service_account.find_secret(secret)
  1155. if not result:
  1156. self.service_account.add_secret(secret)
  1157. for secret in self.config.image_pull_secrets:
  1158. result = self.service_account.find_image_pull_secret(secret)
  1159. if not result:
  1160. self.service_account.add_image_pull_secret(secret)
  1161. return self._replace_content(self.kind, self.config.name, self.config.data)
  1162. def needs_update(self):
  1163. ''' verify an update is needed '''
  1164. # since creating an service account generates secrets and imagepullsecrets
  1165. # check_def_equal will not work
  1166. # Instead, verify all secrets passed are in the list
  1167. for secret in self.config.secrets:
  1168. result = self.service_account.find_secret(secret)
  1169. if not result:
  1170. return True
  1171. for secret in self.config.image_pull_secrets:
  1172. result = self.service_account.find_image_pull_secret(secret)
  1173. if not result:
  1174. return True
  1175. return False
  1176. @staticmethod
  1177. # pylint: disable=too-many-return-statements,too-many-branches
  1178. # TODO: This function should be refactored into its individual parts.
  1179. def run_ansible(params, check_mode):
  1180. '''run the ansible idempotent code'''
  1181. rconfig = ServiceAccountConfig(params['name'],
  1182. params['namespace'],
  1183. params['kubeconfig'],
  1184. params['secrets'],
  1185. params['image_pull_secrets'],
  1186. )
  1187. oc_sa = OCServiceAccount(rconfig,
  1188. verbose=params['debug'])
  1189. state = params['state']
  1190. api_rval = oc_sa.get()
  1191. #####
  1192. # Get
  1193. #####
  1194. if state == 'list':
  1195. return {'changed': False, 'results': api_rval['results'], 'state': 'list'}
  1196. ########
  1197. # Delete
  1198. ########
  1199. if state == 'absent':
  1200. if oc_sa.exists():
  1201. if check_mode:
  1202. return {'changed': True, 'msg': 'Would have performed a delete.'}
  1203. api_rval = oc_sa.delete()
  1204. return {'changed': True, 'results': api_rval, 'state': 'absent'}
  1205. return {'changed': False, 'state': 'absent'}
  1206. if state == 'present':
  1207. ########
  1208. # Create
  1209. ########
  1210. if not oc_sa.exists():
  1211. if check_mode:
  1212. return {'changed': True, 'msg': 'Would have performed a create.'}
  1213. # Create it here
  1214. api_rval = oc_sa.create()
  1215. if api_rval['returncode'] != 0:
  1216. return {'failed': True, 'msg': api_rval}
  1217. # return the created object
  1218. api_rval = oc_sa.get()
  1219. if api_rval['returncode'] != 0:
  1220. return {'failed': True, 'msg': api_rval}
  1221. return {'changed': True, 'results': api_rval, 'state': 'present'}
  1222. ########
  1223. # Update
  1224. ########
  1225. if oc_sa.needs_update():
  1226. api_rval = oc_sa.update()
  1227. if api_rval['returncode'] != 0:
  1228. return {'failed': True, 'msg': api_rval}
  1229. # return the created object
  1230. api_rval = oc_sa.get()
  1231. if api_rval['returncode'] != 0:
  1232. return {'failed': True, 'msg': api_rval}
  1233. return {'changed': True, 'results': api_rval, 'state': 'present'}
  1234. return {'changed': False, 'results': api_rval, 'state': 'present'}
  1235. return {'failed': True,
  1236. 'changed': False,
  1237. 'msg': 'Unknown state passed. %s' % state,
  1238. 'state': 'unknown'}
  1239. # -*- -*- -*- End included fragment: class/oc_serviceaccount.py -*- -*- -*-
  1240. # -*- -*- -*- Begin included fragment: ansible/oc_serviceaccount.py -*- -*- -*-
  1241. def main():
  1242. '''
  1243. ansible oc module for service accounts
  1244. '''
  1245. module = AnsibleModule(
  1246. argument_spec=dict(
  1247. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  1248. state=dict(default='present', type='str',
  1249. choices=['present', 'absent', 'list']),
  1250. debug=dict(default=False, type='bool'),
  1251. name=dict(default=None, required=True, type='str'),
  1252. namespace=dict(default=None, required=True, type='str'),
  1253. secrets=dict(default=None, type='list'),
  1254. image_pull_secrets=dict(default=None, type='list'),
  1255. ),
  1256. supports_check_mode=True,
  1257. )
  1258. rval = OCServiceAccount.run_ansible(module.params, module.check_mode)
  1259. if 'failed' in rval:
  1260. module.fail_json(**rval)
  1261. module.exit_json(**rval)
  1262. if __name__ == '__main__':
  1263. main()
  1264. # -*- -*- -*- End included fragment: ansible/oc_serviceaccount.py -*- -*- -*-