yedit.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. #!/usr/bin/env python
  2. # pylint: disable=missing-docstring
  3. # ___ ___ _ _ ___ ___ _ _____ ___ ___
  4. # / __| __| \| | __| _ \ /_\_ _| __| \
  5. # | (_ | _|| .` | _|| / / _ \| | | _|| |) |
  6. # \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____
  7. # | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|
  8. # | |) | (_) | | .` | (_) || | | _|| |) | | | |
  9. # |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|
  10. #
  11. # Copyright 2016 Red Hat, Inc. and/or its affiliates
  12. # and other contributors as indicated by the @author tags.
  13. #
  14. # Licensed under the Apache License, Version 2.0 (the "License");
  15. # you may not use this file except in compliance with the License.
  16. # You may obtain a copy of the License at
  17. #
  18. # http://www.apache.org/licenses/LICENSE-2.0
  19. #
  20. # Unless required by applicable law or agreed to in writing, software
  21. # distributed under the License is distributed on an "AS IS" BASIS,
  22. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  23. # See the License for the specific language governing permissions and
  24. # limitations under the License.
  25. #
  26. # pylint: disable=wrong-import-order
  27. import json
  28. import os
  29. import re
  30. # pylint: disable=import-error
  31. import ruamel.yaml as yaml
  32. import shutil
  33. from ansible.module_utils.basic import AnsibleModule
  34. DOCUMENTATION = '''
  35. ---
  36. module: yedit
  37. short_description: Create, modify, and idempotently manage yaml files.
  38. description:
  39. - Modify yaml files programmatically.
  40. options:
  41. state:
  42. description:
  43. - State represents whether to create, modify, delete, or list yaml
  44. required: true
  45. default: present
  46. choices: ["present", "absent", "list"]
  47. aliases: []
  48. debug:
  49. description:
  50. - Turn on debug information.
  51. required: false
  52. default: false
  53. aliases: []
  54. src:
  55. description:
  56. - The file that is the target of the modifications.
  57. required: false
  58. default: None
  59. aliases: []
  60. content:
  61. description:
  62. - Content represents the yaml content you desire to work with. This
  63. - could be the file contents to write or the inmemory data to modify.
  64. required: false
  65. default: None
  66. aliases: []
  67. content_type:
  68. description:
  69. - The python type of the content parameter.
  70. required: false
  71. default: 'dict'
  72. aliases: []
  73. key:
  74. description:
  75. - The path to the value you wish to modify. Emtpy string means the top of
  76. - the document.
  77. required: false
  78. default: ''
  79. aliases: []
  80. value:
  81. description:
  82. - The incoming value of parameter 'key'.
  83. required: false
  84. default:
  85. aliases: []
  86. value_type:
  87. description:
  88. - The python type of the incoming value.
  89. required: false
  90. default: ''
  91. aliases: []
  92. update:
  93. description:
  94. - Whether the update should be performed on a dict/hash or list/array
  95. - object.
  96. required: false
  97. default: false
  98. aliases: []
  99. append:
  100. description:
  101. - Whether to append to an array/list. When the key does not exist or is
  102. - null, a new array is created. When the key is of a non-list type,
  103. - nothing is done.
  104. required: false
  105. default: false
  106. aliases: []
  107. index:
  108. description:
  109. - Used in conjunction with the update parameter. This will update a
  110. - specific index in an array/list.
  111. required: false
  112. default: false
  113. aliases: []
  114. curr_value:
  115. description:
  116. - Used in conjunction with the update parameter. This is the current
  117. - value of 'key' in the yaml file.
  118. required: false
  119. default: false
  120. aliases: []
  121. curr_value_format:
  122. description:
  123. - Format of the incoming current value.
  124. choices: ["yaml", "json", "str"]
  125. required: false
  126. default: false
  127. aliases: []
  128. backup:
  129. description:
  130. - Whether to make a backup copy of the current file when performing an
  131. - edit.
  132. required: false
  133. default: true
  134. aliases: []
  135. separator:
  136. description:
  137. - The separator being used when parsing strings.
  138. required: false
  139. default: '.'
  140. aliases: []
  141. author:
  142. - "Kenny Woodson <kwoodson@redhat.com>"
  143. extends_documentation_fragment: []
  144. '''
  145. EXAMPLES = '''
  146. # Simple insert of key, value
  147. - name: insert simple key, value
  148. yedit:
  149. src: somefile.yml
  150. key: test
  151. value: somevalue
  152. state: present
  153. # Results:
  154. # test: somevalue
  155. # Multilevel insert of key, value
  156. - name: insert simple key, value
  157. yedit:
  158. src: somefile.yml
  159. key: a#b#c
  160. value: d
  161. state: present
  162. # Results:
  163. # a:
  164. # b:
  165. # c: d
  166. '''
  167. # noqa: E301,E302
  168. class YeditException(Exception):
  169. ''' Exception class for Yedit '''
  170. pass
  171. # pylint: disable=too-many-public-methods
  172. class Yedit(object):
  173. ''' Class to modify yaml files '''
  174. re_valid_key = r"(((\[-?\d+\])|([0-9a-zA-Z%s/_-]+)).?)+$"
  175. re_key = r"(?:\[(-?\d+)\])|([0-9a-zA-Z%s/_-]+)"
  176. com_sep = set(['.', '#', '|', ':'])
  177. # pylint: disable=too-many-arguments
  178. def __init__(self,
  179. filename=None,
  180. content=None,
  181. content_type='yaml',
  182. separator='.',
  183. backup=False):
  184. self.content = content
  185. self._separator = separator
  186. self.filename = filename
  187. self.__yaml_dict = content
  188. self.content_type = content_type
  189. self.backup = backup
  190. self.load(content_type=self.content_type)
  191. if self.__yaml_dict is None:
  192. self.__yaml_dict = {}
  193. @property
  194. def separator(self):
  195. ''' getter method for yaml_dict '''
  196. return self._separator
  197. @separator.setter
  198. def separator(self):
  199. ''' getter method for yaml_dict '''
  200. return self._separator
  201. @property
  202. def yaml_dict(self):
  203. ''' getter method for yaml_dict '''
  204. return self.__yaml_dict
  205. @yaml_dict.setter
  206. def yaml_dict(self, value):
  207. ''' setter method for yaml_dict '''
  208. self.__yaml_dict = value
  209. @staticmethod
  210. def parse_key(key, sep='.'):
  211. '''parse the key allowing the appropriate separator'''
  212. common_separators = list(Yedit.com_sep - set([sep]))
  213. return re.findall(Yedit.re_key % ''.join(common_separators), key)
  214. @staticmethod
  215. def valid_key(key, sep='.'):
  216. '''validate the incoming key'''
  217. common_separators = list(Yedit.com_sep - set([sep]))
  218. if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
  219. return False
  220. return True
  221. @staticmethod
  222. def remove_entry(data, key, sep='.'):
  223. ''' remove data at location key '''
  224. if key == '' and isinstance(data, dict):
  225. data.clear()
  226. return True
  227. elif key == '' and isinstance(data, list):
  228. del data[:]
  229. return True
  230. if not (key and Yedit.valid_key(key, sep)) and \
  231. isinstance(data, (list, dict)):
  232. return None
  233. key_indexes = Yedit.parse_key(key, sep)
  234. for arr_ind, dict_key in key_indexes[:-1]:
  235. if dict_key and isinstance(data, dict):
  236. data = data.get(dict_key, None)
  237. elif (arr_ind and isinstance(data, list) and
  238. int(arr_ind) <= len(data) - 1):
  239. data = data[int(arr_ind)]
  240. else:
  241. return None
  242. # process last index for remove
  243. # expected list entry
  244. if key_indexes[-1][0]:
  245. if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  246. del data[int(key_indexes[-1][0])]
  247. return True
  248. # expected dict entry
  249. elif key_indexes[-1][1]:
  250. if isinstance(data, dict):
  251. del data[key_indexes[-1][1]]
  252. return True
  253. @staticmethod
  254. def add_entry(data, key, item=None, sep='.'):
  255. ''' Get an item from a dictionary with key notation a.b.c
  256. d = {'a': {'b': 'c'}}}
  257. key = a#b
  258. return c
  259. '''
  260. if key == '':
  261. pass
  262. elif (not (key and Yedit.valid_key(key, sep)) and
  263. isinstance(data, (list, dict))):
  264. return None
  265. key_indexes = Yedit.parse_key(key, sep)
  266. for arr_ind, dict_key in key_indexes[:-1]:
  267. if dict_key:
  268. if isinstance(data, dict) and dict_key in data and data[dict_key]: # noqa: E501
  269. data = data[dict_key]
  270. continue
  271. elif data and not isinstance(data, dict):
  272. return None
  273. data[dict_key] = {}
  274. data = data[dict_key]
  275. elif (arr_ind and isinstance(data, list) and
  276. int(arr_ind) <= len(data) - 1):
  277. data = data[int(arr_ind)]
  278. else:
  279. return None
  280. if key == '':
  281. data = item
  282. # process last index for add
  283. # expected list entry
  284. elif key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  285. data[int(key_indexes[-1][0])] = item
  286. # expected dict entry
  287. elif key_indexes[-1][1] and isinstance(data, dict):
  288. data[key_indexes[-1][1]] = item
  289. return data
  290. @staticmethod
  291. def get_entry(data, key, sep='.'):
  292. ''' Get an item from a dictionary with key notation a.b.c
  293. d = {'a': {'b': 'c'}}}
  294. key = a.b
  295. return c
  296. '''
  297. if key == '':
  298. pass
  299. elif (not (key and Yedit.valid_key(key, sep)) and
  300. isinstance(data, (list, dict))):
  301. return None
  302. key_indexes = Yedit.parse_key(key, sep)
  303. for arr_ind, dict_key in key_indexes:
  304. if dict_key and isinstance(data, dict):
  305. data = data.get(dict_key, None)
  306. elif (arr_ind and isinstance(data, list) and
  307. int(arr_ind) <= len(data) - 1):
  308. data = data[int(arr_ind)]
  309. else:
  310. return None
  311. return data
  312. def write(self):
  313. ''' write to file '''
  314. if not self.filename:
  315. raise YeditException('Please specify a filename.')
  316. if self.backup and self.file_exists():
  317. shutil.copy(self.filename, self.filename + '.orig')
  318. tmp_filename = self.filename + '.yedit'
  319. with open(tmp_filename, 'w') as yfd:
  320. # pylint: disable=no-member
  321. if hasattr(self.yaml_dict, 'fa'):
  322. self.yaml_dict.fa.set_block_style()
  323. yfd.write(yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper))
  324. os.rename(tmp_filename, self.filename)
  325. return (True, self.yaml_dict)
  326. def read(self):
  327. ''' read from file '''
  328. # check if it exists
  329. if self.filename is None or not self.file_exists():
  330. return None
  331. contents = None
  332. with open(self.filename) as yfd:
  333. contents = yfd.read()
  334. return contents
  335. def file_exists(self):
  336. ''' return whether file exists '''
  337. if os.path.exists(self.filename):
  338. return True
  339. return False
  340. def load(self, content_type='yaml'):
  341. ''' return yaml file '''
  342. contents = self.read()
  343. if not contents and not self.content:
  344. return None
  345. if self.content:
  346. if isinstance(self.content, dict):
  347. self.yaml_dict = self.content
  348. return self.yaml_dict
  349. elif isinstance(self.content, str):
  350. contents = self.content
  351. # check if it is yaml
  352. try:
  353. if content_type == 'yaml' and contents:
  354. self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader)
  355. # pylint: disable=no-member
  356. if hasattr(self.yaml_dict, 'fa'):
  357. self.yaml_dict.fa.set_block_style()
  358. elif content_type == 'json' and contents:
  359. self.yaml_dict = json.loads(contents)
  360. except yaml.YAMLError as err:
  361. # Error loading yaml or json
  362. raise YeditException('Problem with loading yaml file. %s' % err)
  363. return self.yaml_dict
  364. def get(self, key):
  365. ''' get a specified key'''
  366. try:
  367. entry = Yedit.get_entry(self.yaml_dict, key, self.separator)
  368. except KeyError:
  369. entry = None
  370. return entry
  371. def pop(self, path, key_or_item):
  372. ''' remove a key, value pair from a dict or an item for a list'''
  373. try:
  374. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  375. except KeyError:
  376. entry = None
  377. if entry is None:
  378. return (False, self.yaml_dict)
  379. if isinstance(entry, dict):
  380. # pylint: disable=no-member,maybe-no-member
  381. if key_or_item in entry:
  382. entry.pop(key_or_item)
  383. return (True, self.yaml_dict)
  384. return (False, self.yaml_dict)
  385. elif isinstance(entry, list):
  386. # pylint: disable=no-member,maybe-no-member
  387. ind = None
  388. try:
  389. ind = entry.index(key_or_item)
  390. except ValueError:
  391. return (False, self.yaml_dict)
  392. entry.pop(ind)
  393. return (True, self.yaml_dict)
  394. return (False, self.yaml_dict)
  395. def delete(self, path):
  396. ''' remove path from a dict'''
  397. try:
  398. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  399. except KeyError:
  400. entry = None
  401. if entry is None:
  402. return (False, self.yaml_dict)
  403. result = Yedit.remove_entry(self.yaml_dict, path, self.separator)
  404. if not result:
  405. return (False, self.yaml_dict)
  406. return (True, self.yaml_dict)
  407. def exists(self, path, value):
  408. ''' check if value exists at path'''
  409. try:
  410. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  411. except KeyError:
  412. entry = None
  413. if isinstance(entry, list):
  414. if value in entry:
  415. return True
  416. return False
  417. elif isinstance(entry, dict):
  418. if isinstance(value, dict):
  419. rval = False
  420. for key, val in value.items():
  421. if entry[key] != val:
  422. rval = False
  423. break
  424. else:
  425. rval = True
  426. return rval
  427. return value in entry
  428. return entry == value
  429. def append(self, path, value):
  430. '''append value to a list'''
  431. try:
  432. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  433. except KeyError:
  434. entry = None
  435. if entry is None:
  436. self.put(path, [])
  437. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  438. if not isinstance(entry, list):
  439. return (False, self.yaml_dict)
  440. # pylint: disable=no-member,maybe-no-member
  441. entry.append(value)
  442. return (True, self.yaml_dict)
  443. # pylint: disable=too-many-arguments
  444. def update(self, path, value, index=None, curr_value=None):
  445. ''' put path, value into a dict '''
  446. try:
  447. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  448. except KeyError:
  449. entry = None
  450. if isinstance(entry, dict):
  451. # pylint: disable=no-member,maybe-no-member
  452. if not isinstance(value, dict):
  453. raise YeditException('Cannot replace key, value entry in ' +
  454. 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501
  455. entry.update(value)
  456. return (True, self.yaml_dict)
  457. elif isinstance(entry, list):
  458. # pylint: disable=no-member,maybe-no-member
  459. ind = None
  460. if curr_value:
  461. try:
  462. ind = entry.index(curr_value)
  463. except ValueError:
  464. return (False, self.yaml_dict)
  465. elif index is not None:
  466. ind = index
  467. if ind is not None and entry[ind] != value:
  468. entry[ind] = value
  469. return (True, self.yaml_dict)
  470. # see if it exists in the list
  471. try:
  472. ind = entry.index(value)
  473. except ValueError:
  474. # doesn't exist, append it
  475. entry.append(value)
  476. return (True, self.yaml_dict)
  477. # already exists, return
  478. if ind is not None:
  479. return (False, self.yaml_dict)
  480. return (False, self.yaml_dict)
  481. def put(self, path, value):
  482. ''' put path, value into a dict '''
  483. try:
  484. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  485. except KeyError:
  486. entry = None
  487. if entry == value:
  488. return (False, self.yaml_dict)
  489. # deepcopy didn't work
  490. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  491. default_flow_style=False),
  492. yaml.RoundTripLoader)
  493. # pylint: disable=no-member
  494. if hasattr(self.yaml_dict, 'fa'):
  495. tmp_copy.fa.set_block_style()
  496. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  497. if not result:
  498. return (False, self.yaml_dict)
  499. self.yaml_dict = tmp_copy
  500. return (True, self.yaml_dict)
  501. def create(self, path, value):
  502. ''' create a yaml file '''
  503. if not self.file_exists():
  504. # deepcopy didn't work
  505. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, default_flow_style=False), # noqa: E501
  506. yaml.RoundTripLoader)
  507. # pylint: disable=no-member
  508. if hasattr(self.yaml_dict, 'fa'):
  509. tmp_copy.fa.set_block_style()
  510. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  511. if result:
  512. self.yaml_dict = tmp_copy
  513. return (True, self.yaml_dict)
  514. return (False, self.yaml_dict)
  515. @staticmethod
  516. def get_curr_value(invalue, val_type):
  517. '''return the current value'''
  518. if invalue is None:
  519. return None
  520. curr_value = invalue
  521. if val_type == 'yaml':
  522. curr_value = yaml.load(invalue)
  523. elif val_type == 'json':
  524. curr_value = json.loads(invalue)
  525. return curr_value
  526. @staticmethod
  527. def parse_value(inc_value, vtype=''):
  528. '''determine value type passed'''
  529. true_bools = ['y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE',
  530. 'on', 'On', 'ON', ]
  531. false_bools = ['n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE',
  532. 'off', 'Off', 'OFF']
  533. # It came in as a string but you didn't specify value_type as string
  534. # we will convert to bool if it matches any of the above cases
  535. if isinstance(inc_value, str) and 'bool' in vtype:
  536. if inc_value not in true_bools and inc_value not in false_bools:
  537. raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
  538. % (inc_value, vtype))
  539. elif isinstance(inc_value, bool) and 'str' in vtype:
  540. inc_value = str(inc_value)
  541. # If vtype is not str then go ahead and attempt to yaml load it.
  542. if isinstance(inc_value, str) and 'str' not in vtype:
  543. try:
  544. inc_value = yaml.load(inc_value)
  545. except Exception:
  546. raise YeditException('Could not determine type of incoming ' +
  547. 'value. value=[%s] vtype=[%s]'
  548. % (type(inc_value), vtype))
  549. return inc_value
  550. # pylint: disable=too-many-return-statements,too-many-branches
  551. @staticmethod
  552. def run_ansible(module):
  553. '''perform the idempotent crud operations'''
  554. yamlfile = Yedit(filename=module.params['src'],
  555. backup=module.params['backup'],
  556. separator=module.params['separator'])
  557. if module.params['src']:
  558. rval = yamlfile.load()
  559. if yamlfile.yaml_dict is None and \
  560. module.params['state'] != 'present':
  561. return {'failed': True,
  562. 'msg': 'Error opening file [%s]. Verify that the ' +
  563. 'file exists, that it is has correct' +
  564. ' permissions, and is valid yaml.'}
  565. if module.params['state'] == 'list':
  566. if module.params['content']:
  567. content = Yedit.parse_value(module.params['content'],
  568. module.params['content_type'])
  569. yamlfile.yaml_dict = content
  570. if module.params['key']:
  571. rval = yamlfile.get(module.params['key']) or {}
  572. return {'changed': False, 'result': rval, 'state': "list"}
  573. elif module.params['state'] == 'absent':
  574. if module.params['content']:
  575. content = Yedit.parse_value(module.params['content'],
  576. module.params['content_type'])
  577. yamlfile.yaml_dict = content
  578. if module.params['update']:
  579. rval = yamlfile.pop(module.params['key'],
  580. module.params['value'])
  581. else:
  582. rval = yamlfile.delete(module.params['key'])
  583. if rval[0] and module.params['src']:
  584. yamlfile.write()
  585. return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
  586. elif module.params['state'] == 'present':
  587. # check if content is different than what is in the file
  588. if module.params['content']:
  589. content = Yedit.parse_value(module.params['content'],
  590. module.params['content_type'])
  591. # We had no edits to make and the contents are the same
  592. if yamlfile.yaml_dict == content and \
  593. module.params['value'] is None:
  594. return {'changed': False,
  595. 'result': yamlfile.yaml_dict,
  596. 'state': "present"}
  597. yamlfile.yaml_dict = content
  598. # we were passed a value; parse it
  599. if module.params['value']:
  600. value = Yedit.parse_value(module.params['value'],
  601. module.params['value_type'])
  602. key = module.params['key']
  603. if module.params['update']:
  604. # pylint: disable=line-too-long
  605. curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']), # noqa: E501
  606. module.params['curr_value_format']) # noqa: E501
  607. rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501
  608. elif module.params['append']:
  609. rval = yamlfile.append(key, value)
  610. else:
  611. rval = yamlfile.put(key, value)
  612. if rval[0] and module.params['src']:
  613. yamlfile.write()
  614. return {'changed': rval[0],
  615. 'result': rval[1], 'state': "present"}
  616. # no edits to make
  617. if module.params['src']:
  618. # pylint: disable=redefined-variable-type
  619. rval = yamlfile.write()
  620. return {'changed': rval[0],
  621. 'result': rval[1],
  622. 'state': "present"}
  623. return {'failed': True, 'msg': 'Unkown state passed'}
  624. # pylint: disable=too-many-branches
  625. def main():
  626. ''' ansible oc module for secrets '''
  627. module = AnsibleModule(
  628. argument_spec=dict(
  629. state=dict(default='present', type='str',
  630. choices=['present', 'absent', 'list']),
  631. debug=dict(default=False, type='bool'),
  632. src=dict(default=None, type='str'),
  633. content=dict(default=None),
  634. content_type=dict(default='dict', choices=['dict']),
  635. key=dict(default='', type='str'),
  636. value=dict(),
  637. value_type=dict(default='', type='str'),
  638. update=dict(default=False, type='bool'),
  639. append=dict(default=False, type='bool'),
  640. index=dict(default=None, type='int'),
  641. curr_value=dict(default=None, type='str'),
  642. curr_value_format=dict(default='yaml',
  643. choices=['yaml', 'json', 'str'],
  644. type='str'),
  645. backup=dict(default=True, type='bool'),
  646. separator=dict(default='.', type='str'),
  647. ),
  648. mutually_exclusive=[["curr_value", "index"], ['update', "append"]],
  649. required_one_of=[["content", "src"]],
  650. )
  651. rval = Yedit.run_ansible(module)
  652. if 'failed' in rval and rval['failed']:
  653. module.fail_json(**rval)
  654. module.exit_json(**rval)
  655. if __name__ == '__main__':
  656. main()