PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/system/osx_defaults.py

https://gitlab.com/0072016/ansible-modules-extras
Python | 354 lines | 336 code | 2 blank | 16 comment | 1 complexity | e48ddef4fed2c52eeb7ffbe69155f282 MD5 | raw file
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # (c) 2014, GeekChimp - Franck Nijhof <franck@geekchimp.com>
  4. #
  5. # Ansible is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # Ansible is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with Ansible. If not, see <http://www.gnu.org/licenses/>.
  17. DOCUMENTATION = '''
  18. ---
  19. module: osx_defaults
  20. author: Franck Nijhof (@frenck)
  21. short_description: osx_defaults allows users to read, write, and delete Mac OS X user defaults from Ansible
  22. description:
  23. - osx_defaults allows users to read, write, and delete Mac OS X user defaults from Ansible scripts.
  24. Mac OS X applications and other programs use the defaults system to record user preferences and other
  25. information that must be maintained when the applications aren't running (such as default font for new
  26. documents, or the position of an Info panel).
  27. version_added: "2.0"
  28. options:
  29. domain:
  30. description:
  31. - The domain is a domain name of the form com.companyname.appname.
  32. required: false
  33. default: NSGlobalDomain
  34. key:
  35. description:
  36. - The key of the user preference
  37. required: true
  38. type:
  39. description:
  40. - The type of value to write.
  41. required: false
  42. default: string
  43. choices: [ "array", "bool", "boolean", "date", "float", "int", "integer", "string" ]
  44. array_add:
  45. description:
  46. - Add new elements to the array for a key which has an array as its value.
  47. required: false
  48. default: false
  49. choices: [ "true", "false" ]
  50. value:
  51. description:
  52. - The value to write. Only required when state = present.
  53. required: false
  54. default: null
  55. state:
  56. description:
  57. - The state of the user defaults
  58. required: false
  59. default: present
  60. choices: [ "present", "absent" ]
  61. notes:
  62. - Apple Mac caches defaults. You may need to logout and login to apply the changes.
  63. '''
  64. EXAMPLES = '''
  65. - osx_defaults: domain=com.apple.Safari key=IncludeInternalDebugMenu type=bool value=true state=present
  66. - osx_defaults: domain=NSGlobalDomain key=AppleMeasurementUnits type=string value=Centimeters state=present
  67. - osx_defaults: key=AppleMeasurementUnits type=string value=Centimeters
  68. - osx_defaults:
  69. key: AppleLanguages
  70. type: array
  71. value: ["en", "nl"]
  72. - osx_defaults: domain=com.geekchimp.macable key=ExampleKeyToRemove state=absent
  73. '''
  74. from datetime import datetime
  75. # exceptions --------------------------------------------------------------- {{{
  76. class OSXDefaultsException(Exception):
  77. pass
  78. # /exceptions -------------------------------------------------------------- }}}
  79. # class MacDefaults -------------------------------------------------------- {{{
  80. class OSXDefaults(object):
  81. """ Class to manage Mac OS user defaults """
  82. # init ---------------------------------------------------------------- {{{
  83. """ Initialize this module. Finds 'defaults' executable and preps the parameters """
  84. def __init__(self, **kwargs):
  85. # Initial var for storing current defaults value
  86. self.current_value = None
  87. # Just set all given parameters
  88. for key, val in kwargs.iteritems():
  89. setattr(self, key, val)
  90. # Try to find the defaults executable
  91. self.executable = self.module.get_bin_path(
  92. 'defaults',
  93. required=False,
  94. opt_dirs=self.path.split(':'),
  95. )
  96. if not self.executable:
  97. raise OSXDefaultsException("Unable to locate defaults executable.")
  98. # When state is present, we require a parameter
  99. if self.state == "present" and self.value is None:
  100. raise OSXDefaultsException("Missing value parameter")
  101. # Ensure the value is the correct type
  102. self.value = self._convert_type(self.type, self.value)
  103. # /init --------------------------------------------------------------- }}}
  104. # tools --------------------------------------------------------------- {{{
  105. """ Converts value to given type """
  106. def _convert_type(self, type, value):
  107. if type == "string":
  108. return str(value)
  109. elif type in ["bool", "boolean"]:
  110. if isinstance(value, basestring):
  111. value = value.lower()
  112. if value in [True, 1, "true", "1", "yes"]:
  113. return True
  114. elif value in [False, 0, "false", "0", "no"]:
  115. return False
  116. raise OSXDefaultsException("Invalid boolean value: {0}".format(repr(value)))
  117. elif type == "date":
  118. try:
  119. return datetime.strptime(value.split("+")[0].strip(), "%Y-%m-%d %H:%M:%S")
  120. except ValueError:
  121. raise OSXDefaultsException(
  122. "Invalid date value: {0}. Required format yyy-mm-dd hh:mm:ss.".format(repr(value))
  123. )
  124. elif type in ["int", "integer"]:
  125. if not str(value).isdigit():
  126. raise OSXDefaultsException("Invalid integer value: {0}".format(repr(value)))
  127. return int(value)
  128. elif type == "float":
  129. try:
  130. value = float(value)
  131. except ValueError:
  132. raise OSXDefaultsException("Invalid float value: {0}".format(repr(value)))
  133. return value
  134. elif type == "array":
  135. if not isinstance(value, list):
  136. raise OSXDefaultsException("Invalid value. Expected value to be an array")
  137. return value
  138. raise OSXDefaultsException('Type is not supported: {0}'.format(type))
  139. """ Converts array output from defaults to an list """
  140. @staticmethod
  141. def _convert_defaults_str_to_list(value):
  142. # Split output of defaults. Every line contains a value
  143. value = value.splitlines()
  144. # Remove first and last item, those are not actual values
  145. value.pop(0)
  146. value.pop(-1)
  147. # Remove extra spaces and comma (,) at the end of values
  148. value = [re.sub(',$', '', x.strip(' ')) for x in value]
  149. return value
  150. # /tools -------------------------------------------------------------- }}}
  151. # commands ------------------------------------------------------------ {{{
  152. """ Reads value of this domain & key from defaults """
  153. def read(self):
  154. # First try to find out the type
  155. rc, out, err = self.module.run_command([self.executable, "read-type", self.domain, self.key])
  156. # If RC is 1, the key does not exists
  157. if rc == 1:
  158. return None
  159. # If the RC is not 0, then terrible happened! Ooooh nooo!
  160. if rc != 0:
  161. raise OSXDefaultsException("An error occurred while reading key type from defaults: " + out)
  162. # Ok, lets parse the type from output
  163. type = out.strip().replace('Type is ', '')
  164. # Now get the current value
  165. rc, out, err = self.module.run_command([self.executable, "read", self.domain, self.key])
  166. # Strip output
  167. out = out.strip()
  168. # An non zero RC at this point is kinda strange...
  169. if rc != 0:
  170. raise OSXDefaultsException("An error occurred while reading key value from defaults: " + out)
  171. # Convert string to list when type is array
  172. if type == "array":
  173. out = self._convert_defaults_str_to_list(out)
  174. # Store the current_value
  175. self.current_value = self._convert_type(type, out)
  176. """ Writes value to this domain & key to defaults """
  177. def write(self):
  178. # We need to convert some values so the defaults commandline understands it
  179. if type(self.value) is bool:
  180. if self.value:
  181. value = "TRUE"
  182. else:
  183. value = "FALSE"
  184. elif type(self.value) is int or type(self.value) is float:
  185. value = str(self.value)
  186. elif self.array_add and self.current_value is not None:
  187. value = list(set(self.value) - set(self.current_value))
  188. elif isinstance(self.value, datetime):
  189. value = self.value.strftime('%Y-%m-%d %H:%M:%S')
  190. else:
  191. value = self.value
  192. # When the type is array and array_add is enabled, morph the type :)
  193. if self.type == "array" and self.array_add:
  194. self.type = "array-add"
  195. # All values should be a list, for easy passing it to the command
  196. if not isinstance(value, list):
  197. value = [value]
  198. rc, out, err = self.module.run_command([self.executable, 'write', self.domain, self.key, '-' + self.type] + value)
  199. if rc != 0:
  200. raise OSXDefaultsException('An error occurred while writing value to defaults: ' + out)
  201. """ Deletes defaults key from domain """
  202. def delete(self):
  203. rc, out, err = self.module.run_command([self.executable, 'delete', self.domain, self.key])
  204. if rc != 0:
  205. raise OSXDefaultsException("An error occurred while deleting key from defaults: " + out)
  206. # /commands ----------------------------------------------------------- }}}
  207. # run ----------------------------------------------------------------- {{{
  208. """ Does the magic! :) """
  209. def run(self):
  210. # Get the current value from defaults
  211. self.read()
  212. # Handle absent state
  213. if self.state == "absent":
  214. print "Absent state detected!"
  215. if self.current_value is None:
  216. return False
  217. self.delete()
  218. return True
  219. # There is a type mismatch! Given type does not match the type in defaults
  220. if self.current_value is not None and type(self.current_value) is not type(self.value):
  221. raise OSXDefaultsException("Type mismatch. Type in defaults: " + type(self.current_value).__name__)
  222. # Current value matches the given value. Nothing need to be done. Arrays need extra care
  223. if self.type == "array" and self.current_value is not None and not self.array_add and \
  224. set(self.current_value) == set(self.value):
  225. return False
  226. elif self.type == "array" and self.current_value is not None and self.array_add and \
  227. len(list(set(self.value) - set(self.current_value))) == 0:
  228. return False
  229. elif self.current_value == self.value:
  230. return False
  231. # Change/Create/Set given key/value for domain in defaults
  232. self.write()
  233. return True
  234. # /run ---------------------------------------------------------------- }}}
  235. # /class MacDefaults ------------------------------------------------------ }}}
  236. # main -------------------------------------------------------------------- {{{
  237. def main():
  238. module = AnsibleModule(
  239. argument_spec=dict(
  240. domain=dict(
  241. default="NSGlobalDomain",
  242. required=False,
  243. ),
  244. key=dict(
  245. default=None,
  246. ),
  247. type=dict(
  248. default="string",
  249. required=False,
  250. choices=[
  251. "array",
  252. "bool",
  253. "boolean",
  254. "date",
  255. "float",
  256. "int",
  257. "integer",
  258. "string",
  259. ],
  260. ),
  261. array_add=dict(
  262. default=False,
  263. required=False,
  264. type='bool',
  265. ),
  266. value=dict(
  267. default=None,
  268. required=False,
  269. ),
  270. state=dict(
  271. default="present",
  272. required=False,
  273. choices=[
  274. "absent", "present"
  275. ],
  276. ),
  277. path=dict(
  278. default="/usr/bin:/usr/local/bin",
  279. required=False,
  280. )
  281. ),
  282. supports_check_mode=True,
  283. )
  284. domain = module.params['domain']
  285. key = module.params['key']
  286. type = module.params['type']
  287. array_add = module.params['array_add']
  288. value = module.params['value']
  289. state = module.params['state']
  290. path = module.params['path']
  291. try:
  292. defaults = OSXDefaults(module=module, domain=domain, key=key, type=type,
  293. array_add=array_add, value=value, state=state, path=path)
  294. changed = defaults.run()
  295. module.exit_json(changed=changed)
  296. except OSXDefaultsException, e:
  297. module.fail_json(msg=e.message)
  298. # /main ------------------------------------------------------------------- }}}
  299. from ansible.module_utils.basic import *
  300. main()