oc_serviceaccount.py 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572
  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 = 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(prefix=None):
  805. ''' Generates and returns a temporary file name '''
  806. with tempfile.NamedTemporaryFile(prefix=prefix, delete=False) as tmp:
  807. return tmp.name
  808. @staticmethod
  809. def create_tmp_files_from_contents(content, content_type=None):
  810. '''Turn an array of dict: filename, content into a files array'''
  811. if not isinstance(content, list):
  812. content = [content]
  813. files = []
  814. for item in content:
  815. path = Utils.create_tmp_file_from_contents(item['path'] + '-',
  816. item['data'],
  817. ftype=content_type)
  818. files.append({'name': os.path.basename(item['path']),
  819. 'path': path})
  820. return files
  821. @staticmethod
  822. def cleanup(files):
  823. '''Clean up on exit '''
  824. for sfile in files:
  825. if os.path.exists(sfile):
  826. if os.path.isdir(sfile):
  827. shutil.rmtree(sfile)
  828. elif os.path.isfile(sfile):
  829. os.remove(sfile)
  830. @staticmethod
  831. def exists(results, _name):
  832. ''' Check to see if the results include the name '''
  833. if not results:
  834. return False
  835. if Utils.find_result(results, _name):
  836. return True
  837. return False
  838. @staticmethod
  839. def find_result(results, _name):
  840. ''' Find the specified result by name'''
  841. rval = None
  842. for result in results:
  843. if 'metadata' in result and result['metadata']['name'] == _name:
  844. rval = result
  845. break
  846. return rval
  847. @staticmethod
  848. def get_resource_file(sfile, sfile_type='yaml'):
  849. ''' return the service file '''
  850. contents = None
  851. with open(sfile) as sfd:
  852. contents = sfd.read()
  853. if sfile_type == 'yaml':
  854. contents = yaml.load(contents, yaml.RoundTripLoader)
  855. elif sfile_type == 'json':
  856. contents = json.loads(contents)
  857. return contents
  858. @staticmethod
  859. def filter_versions(stdout):
  860. ''' filter the oc version output '''
  861. version_dict = {}
  862. version_search = ['oc', 'openshift', 'kubernetes']
  863. for line in stdout.strip().split('\n'):
  864. for term in version_search:
  865. if not line:
  866. continue
  867. if line.startswith(term):
  868. version_dict[term] = line.split()[-1]
  869. # horrible hack to get openshift version in Openshift 3.2
  870. # By default "oc version in 3.2 does not return an "openshift" version
  871. if "openshift" not in version_dict:
  872. version_dict["openshift"] = version_dict["oc"]
  873. return version_dict
  874. @staticmethod
  875. def add_custom_versions(versions):
  876. ''' create custom versions strings '''
  877. versions_dict = {}
  878. for tech, version in versions.items():
  879. # clean up "-" from version
  880. if "-" in version:
  881. version = version.split("-")[0]
  882. if version.startswith('v'):
  883. versions_dict[tech + '_numeric'] = version[1:].split('+')[0]
  884. # "v3.3.0.33" is what we have, we want "3.3"
  885. versions_dict[tech + '_short'] = version[1:4]
  886. return versions_dict
  887. @staticmethod
  888. def openshift_installed():
  889. ''' check if openshift is installed '''
  890. import yum
  891. yum_base = yum.YumBase()
  892. if yum_base.rpmdb.searchNevra(name='atomic-openshift'):
  893. return True
  894. return False
  895. # Disabling too-many-branches. This is a yaml dictionary comparison function
  896. # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
  897. @staticmethod
  898. def check_def_equal(user_def, result_def, skip_keys=None, debug=False):
  899. ''' Given a user defined definition, compare it with the results given back by our query. '''
  900. # Currently these values are autogenerated and we do not need to check them
  901. skip = ['metadata', 'status']
  902. if skip_keys:
  903. skip.extend(skip_keys)
  904. for key, value in result_def.items():
  905. if key in skip:
  906. continue
  907. # Both are lists
  908. if isinstance(value, list):
  909. if key not in user_def:
  910. if debug:
  911. print('User data does not have key [%s]' % key)
  912. print('User data: %s' % user_def)
  913. return False
  914. if not isinstance(user_def[key], list):
  915. if debug:
  916. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  917. return False
  918. if len(user_def[key]) != len(value):
  919. if debug:
  920. print("List lengths are not equal.")
  921. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  922. print("user_def: %s" % user_def[key])
  923. print("value: %s" % value)
  924. return False
  925. for values in zip(user_def[key], value):
  926. if isinstance(values[0], dict) and isinstance(values[1], dict):
  927. if debug:
  928. print('sending list - list')
  929. print(type(values[0]))
  930. print(type(values[1]))
  931. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  932. if not result:
  933. print('list compare returned false')
  934. return False
  935. elif value != user_def[key]:
  936. if debug:
  937. print('value should be identical')
  938. print(value)
  939. print(user_def[key])
  940. return False
  941. # recurse on a dictionary
  942. elif isinstance(value, dict):
  943. if key not in user_def:
  944. if debug:
  945. print("user_def does not have key [%s]" % key)
  946. return False
  947. if not isinstance(user_def[key], dict):
  948. if debug:
  949. print("dict returned false: not instance of dict")
  950. return False
  951. # before passing ensure keys match
  952. api_values = set(value.keys()) - set(skip)
  953. user_values = set(user_def[key].keys()) - set(skip)
  954. if api_values != user_values:
  955. if debug:
  956. print("keys are not equal in dict")
  957. print(api_values)
  958. print(user_values)
  959. return False
  960. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  961. if not result:
  962. if debug:
  963. print("dict returned false")
  964. print(result)
  965. return False
  966. # Verify each key, value pair is the same
  967. else:
  968. if key not in user_def or value != user_def[key]:
  969. if debug:
  970. print("value not equal; user_def does not have key")
  971. print(key)
  972. print(value)
  973. if key in user_def:
  974. print(user_def[key])
  975. return False
  976. if debug:
  977. print('returning true')
  978. return True
  979. class OpenShiftCLIConfig(object):
  980. '''Generic Config'''
  981. def __init__(self, rname, namespace, kubeconfig, options):
  982. self.kubeconfig = kubeconfig
  983. self.name = rname
  984. self.namespace = namespace
  985. self._options = options
  986. @property
  987. def config_options(self):
  988. ''' return config options '''
  989. return self._options
  990. def to_option_list(self):
  991. '''return all options as a string'''
  992. return self.stringify()
  993. def stringify(self):
  994. ''' return the options hash as cli params in a string '''
  995. rval = []
  996. for key, data in self.config_options.items():
  997. if data['include'] \
  998. and (data['value'] or isinstance(data['value'], int)):
  999. rval.append('--%s=%s' % (key.replace('_', '-'), data['value']))
  1000. return rval
  1001. # -*- -*- -*- End included fragment: lib/base.py -*- -*- -*-
  1002. # -*- -*- -*- Begin included fragment: lib/serviceaccount.py -*- -*- -*-
  1003. class ServiceAccountConfig(object):
  1004. '''Service account config class
  1005. This class stores the options and returns a default service account
  1006. '''
  1007. # pylint: disable=too-many-arguments
  1008. def __init__(self, sname, namespace, kubeconfig, secrets=None, image_pull_secrets=None):
  1009. self.name = sname
  1010. self.kubeconfig = kubeconfig
  1011. self.namespace = namespace
  1012. self.secrets = secrets or []
  1013. self.image_pull_secrets = image_pull_secrets or []
  1014. self.data = {}
  1015. self.create_dict()
  1016. def create_dict(self):
  1017. ''' return a properly structured volume '''
  1018. self.data['apiVersion'] = 'v1'
  1019. self.data['kind'] = 'ServiceAccount'
  1020. self.data['metadata'] = {}
  1021. self.data['metadata']['name'] = self.name
  1022. self.data['metadata']['namespace'] = self.namespace
  1023. self.data['secrets'] = []
  1024. if self.secrets:
  1025. for sec in self.secrets:
  1026. self.data['secrets'].append({"name": sec})
  1027. self.data['imagePullSecrets'] = []
  1028. if self.image_pull_secrets:
  1029. for sec in self.image_pull_secrets:
  1030. self.data['imagePullSecrets'].append({"name": sec})
  1031. class ServiceAccount(Yedit):
  1032. ''' Class to wrap the oc command line tools '''
  1033. image_pull_secrets_path = "imagePullSecrets"
  1034. secrets_path = "secrets"
  1035. def __init__(self, content):
  1036. '''ServiceAccount constructor'''
  1037. super(ServiceAccount, self).__init__(content=content)
  1038. self._secrets = None
  1039. self._image_pull_secrets = None
  1040. @property
  1041. def image_pull_secrets(self):
  1042. ''' property for image_pull_secrets '''
  1043. if self._image_pull_secrets is None:
  1044. self._image_pull_secrets = self.get(ServiceAccount.image_pull_secrets_path) or []
  1045. return self._image_pull_secrets
  1046. @image_pull_secrets.setter
  1047. def image_pull_secrets(self, secrets):
  1048. ''' property for secrets '''
  1049. self._image_pull_secrets = secrets
  1050. @property
  1051. def secrets(self):
  1052. ''' property for secrets '''
  1053. if not self._secrets:
  1054. self._secrets = self.get(ServiceAccount.secrets_path) or []
  1055. return self._secrets
  1056. @secrets.setter
  1057. def secrets(self, secrets):
  1058. ''' property for secrets '''
  1059. self._secrets = secrets
  1060. def delete_secret(self, inc_secret):
  1061. ''' remove a secret '''
  1062. remove_idx = None
  1063. for idx, sec in enumerate(self.secrets):
  1064. if sec['name'] == inc_secret:
  1065. remove_idx = idx
  1066. break
  1067. if remove_idx:
  1068. del self.secrets[remove_idx]
  1069. return True
  1070. return False
  1071. def delete_image_pull_secret(self, inc_secret):
  1072. ''' remove a image_pull_secret '''
  1073. remove_idx = None
  1074. for idx, sec in enumerate(self.image_pull_secrets):
  1075. if sec['name'] == inc_secret:
  1076. remove_idx = idx
  1077. break
  1078. if remove_idx:
  1079. del self.image_pull_secrets[remove_idx]
  1080. return True
  1081. return False
  1082. def find_secret(self, inc_secret):
  1083. '''find secret'''
  1084. for secret in self.secrets:
  1085. if secret['name'] == inc_secret:
  1086. return secret
  1087. return None
  1088. def find_image_pull_secret(self, inc_secret):
  1089. '''find secret'''
  1090. for secret in self.image_pull_secrets:
  1091. if secret['name'] == inc_secret:
  1092. return secret
  1093. return None
  1094. def add_secret(self, inc_secret):
  1095. '''add secret'''
  1096. if self.secrets:
  1097. self.secrets.append({"name": inc_secret}) # pylint: disable=no-member
  1098. else:
  1099. self.put(ServiceAccount.secrets_path, [{"name": inc_secret}])
  1100. def add_image_pull_secret(self, inc_secret):
  1101. '''add image_pull_secret'''
  1102. if self.image_pull_secrets:
  1103. self.image_pull_secrets.append({"name": inc_secret}) # pylint: disable=no-member
  1104. else:
  1105. self.put(ServiceAccount.image_pull_secrets_path, [{"name": inc_secret}])
  1106. # -*- -*- -*- End included fragment: lib/serviceaccount.py -*- -*- -*-
  1107. # -*- -*- -*- Begin included fragment: class/oc_serviceaccount.py -*- -*- -*-
  1108. # pylint: disable=too-many-instance-attributes
  1109. class OCServiceAccount(OpenShiftCLI):
  1110. ''' Class to wrap the oc command line tools '''
  1111. kind = 'sa'
  1112. # pylint allows 5
  1113. # pylint: disable=too-many-arguments
  1114. def __init__(self,
  1115. config,
  1116. verbose=False):
  1117. ''' Constructor for OCVolume '''
  1118. super(OCServiceAccount, self).__init__(config.namespace, config.kubeconfig)
  1119. self.config = config
  1120. self.namespace = config.namespace
  1121. self.service_account = None
  1122. def exists(self):
  1123. ''' return whether a volume exists '''
  1124. if self.service_account:
  1125. return True
  1126. return False
  1127. def get(self):
  1128. '''return volume information '''
  1129. result = self._get(self.kind, self.config.name)
  1130. if result['returncode'] == 0:
  1131. self.service_account = ServiceAccount(content=result['results'][0])
  1132. elif '\"%s\" not found' % self.config.name in result['stderr']:
  1133. result['returncode'] = 0
  1134. result['results'] = [{}]
  1135. return result
  1136. def delete(self):
  1137. '''delete the object'''
  1138. return self._delete(self.kind, self.config.name)
  1139. def create(self):
  1140. '''create the object'''
  1141. return self._create_from_content(self.config.name, self.config.data)
  1142. def update(self):
  1143. '''update the object'''
  1144. # need to update the tls information and the service name
  1145. for secret in self.config.secrets:
  1146. result = self.service_account.find_secret(secret)
  1147. if not result:
  1148. self.service_account.add_secret(secret)
  1149. for secret in self.config.image_pull_secrets:
  1150. result = self.service_account.find_image_pull_secret(secret)
  1151. if not result:
  1152. self.service_account.add_image_pull_secret(secret)
  1153. return self._replace_content(self.kind, self.config.name, self.config.data)
  1154. def needs_update(self):
  1155. ''' verify an update is needed '''
  1156. # since creating an service account generates secrets and imagepullsecrets
  1157. # check_def_equal will not work
  1158. # Instead, verify all secrets passed are in the list
  1159. for secret in self.config.secrets:
  1160. result = self.service_account.find_secret(secret)
  1161. if not result:
  1162. return True
  1163. for secret in self.config.image_pull_secrets:
  1164. result = self.service_account.find_image_pull_secret(secret)
  1165. if not result:
  1166. return True
  1167. return False
  1168. @staticmethod
  1169. # pylint: disable=too-many-return-statements,too-many-branches
  1170. # TODO: This function should be refactored into its individual parts.
  1171. def run_ansible(params, check_mode):
  1172. '''run the ansible idempotent code'''
  1173. rconfig = ServiceAccountConfig(params['name'],
  1174. params['namespace'],
  1175. params['kubeconfig'],
  1176. params['secrets'],
  1177. params['image_pull_secrets'],
  1178. )
  1179. oc_sa = OCServiceAccount(rconfig,
  1180. verbose=params['debug'])
  1181. state = params['state']
  1182. api_rval = oc_sa.get()
  1183. #####
  1184. # Get
  1185. #####
  1186. if state == 'list':
  1187. return {'changed': False, 'results': api_rval['results'], 'state': 'list'}
  1188. ########
  1189. # Delete
  1190. ########
  1191. if state == 'absent':
  1192. if oc_sa.exists():
  1193. if check_mode:
  1194. return {'changed': True, 'msg': 'Would have performed a delete.'}
  1195. api_rval = oc_sa.delete()
  1196. return {'changed': True, 'results': api_rval, 'state': 'absent'}
  1197. return {'changed': False, 'state': 'absent'}
  1198. if state == 'present':
  1199. ########
  1200. # Create
  1201. ########
  1202. if not oc_sa.exists():
  1203. if check_mode:
  1204. return {'changed': True, 'msg': 'Would have performed a create.'}
  1205. # Create it here
  1206. api_rval = oc_sa.create()
  1207. if api_rval['returncode'] != 0:
  1208. return {'failed': True, 'msg': api_rval}
  1209. # return the created object
  1210. api_rval = oc_sa.get()
  1211. if api_rval['returncode'] != 0:
  1212. return {'failed': True, 'msg': api_rval}
  1213. return {'changed': True, 'results': api_rval, 'state': 'present'}
  1214. ########
  1215. # Update
  1216. ########
  1217. if oc_sa.needs_update():
  1218. api_rval = oc_sa.update()
  1219. if api_rval['returncode'] != 0:
  1220. return {'failed': True, 'msg': api_rval}
  1221. # return the created object
  1222. api_rval = oc_sa.get()
  1223. if api_rval['returncode'] != 0:
  1224. return {'failed': True, 'msg': api_rval}
  1225. return {'changed': True, 'results': api_rval, 'state': 'present'}
  1226. return {'changed': False, 'results': api_rval, 'state': 'present'}
  1227. return {'failed': True,
  1228. 'changed': False,
  1229. 'msg': 'Unknown state passed. %s' % state,
  1230. 'state': 'unknown'}
  1231. # -*- -*- -*- End included fragment: class/oc_serviceaccount.py -*- -*- -*-
  1232. # -*- -*- -*- Begin included fragment: ansible/oc_serviceaccount.py -*- -*- -*-
  1233. def main():
  1234. '''
  1235. ansible oc module for service accounts
  1236. '''
  1237. module = AnsibleModule(
  1238. argument_spec=dict(
  1239. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  1240. state=dict(default='present', type='str',
  1241. choices=['present', 'absent', 'list']),
  1242. debug=dict(default=False, type='bool'),
  1243. name=dict(default=None, required=True, type='str'),
  1244. namespace=dict(default=None, required=True, type='str'),
  1245. secrets=dict(default=None, type='list'),
  1246. image_pull_secrets=dict(default=None, type='list'),
  1247. ),
  1248. supports_check_mode=True,
  1249. )
  1250. rval = OCServiceAccount.run_ansible(module.params, module.check_mode)
  1251. if 'failed' in rval:
  1252. module.fail_json(**rval)
  1253. module.exit_json(**rval)
  1254. if __name__ == '__main__':
  1255. main()
  1256. # -*- -*- -*- End included fragment: ansible/oc_serviceaccount.py -*- -*- -*-