PageRenderTime 34ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/packaging/os/pkgng.py

https://gitlab.com/0072016/ansible-modules-extras
Python | 316 lines | 293 code | 3 blank | 20 comment | 0 complexity | 6bd5fd29ff650c05cddc41f0c4ef7093 MD5 | raw file
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # (c) 2013, bleader
  4. # Written by bleader <bleader@ratonland.org>
  5. # Based on pkgin module written by Shaun Zinck <shaun.zinck at gmail.com>
  6. # that was based on pacman module written by Afterburn <http://github.com/afterburn>
  7. # that was based on apt module written by Matthew Williams <matthew@flowroute.com>
  8. #
  9. # This module is free software: you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation, either version 3 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This software is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with this software. If not, see <http://www.gnu.org/licenses/>.
  21. DOCUMENTATION = '''
  22. ---
  23. module: pkgng
  24. short_description: Package manager for FreeBSD >= 9.0
  25. description:
  26. - Manage binary packages for FreeBSD using 'pkgng' which
  27. is available in versions after 9.0.
  28. version_added: "1.2"
  29. options:
  30. name:
  31. description:
  32. - name of package to install/remove
  33. required: true
  34. state:
  35. description:
  36. - state of the package
  37. choices: [ 'present', 'absent' ]
  38. required: false
  39. default: present
  40. cached:
  41. description:
  42. - use local package base or try to fetch an updated one
  43. choices: [ 'yes', 'no' ]
  44. required: false
  45. default: no
  46. annotation:
  47. description:
  48. - a comma-separated list of keyvalue-pairs of the form
  49. <+/-/:><key>[=<value>]. A '+' denotes adding an annotation, a
  50. '-' denotes removing an annotation, and ':' denotes modifying an
  51. annotation.
  52. If setting or modifying annotations, a value must be provided.
  53. required: false
  54. version_added: "1.6"
  55. pkgsite:
  56. description:
  57. - for pkgng versions before 1.1.4, specify packagesite to use
  58. for downloading packages, if not specified, use settings from
  59. /usr/local/etc/pkg.conf
  60. for newer pkgng versions, specify a the name of a repository
  61. configured in /usr/local/etc/pkg/repos
  62. required: false
  63. rootdir:
  64. description:
  65. - for pkgng versions 1.5 and later, pkg will install all packages
  66. within the specified root directory
  67. required: false
  68. author: "bleader (@bleader)"
  69. notes:
  70. - When using pkgsite, be careful that already in cache packages won't be downloaded again.
  71. '''
  72. EXAMPLES = '''
  73. # Install package foo
  74. - pkgng: name=foo state=present
  75. # Annotate package foo and bar
  76. - pkgng: name=foo,bar annotation=+test1=baz,-test2,:test3=foobar
  77. # Remove packages foo and bar
  78. - pkgng: name=foo,bar state=absent
  79. '''
  80. import shlex
  81. import os
  82. import re
  83. import sys
  84. def query_package(module, pkgng_path, name, rootdir_arg):
  85. rc, out, err = module.run_command("%s %s info -g -e %s" % (pkgng_path, rootdir_arg, name))
  86. if rc == 0:
  87. return True
  88. return False
  89. def pkgng_older_than(module, pkgng_path, compare_version):
  90. rc, out, err = module.run_command("%s -v" % pkgng_path)
  91. version = map(lambda x: int(x), re.split(r'[\._]', out))
  92. i = 0
  93. new_pkgng = True
  94. while compare_version[i] == version[i]:
  95. i += 1
  96. if i == min(len(compare_version), len(version)):
  97. break
  98. else:
  99. if compare_version[i] > version[i]:
  100. new_pkgng = False
  101. return not new_pkgng
  102. def remove_packages(module, pkgng_path, packages, rootdir_arg):
  103. remove_c = 0
  104. # Using a for loop incase of error, we can report the package that failed
  105. for package in packages:
  106. # Query the package first, to see if we even need to remove
  107. if not query_package(module, pkgng_path, package, rootdir_arg):
  108. continue
  109. if not module.check_mode:
  110. rc, out, err = module.run_command("%s %s delete -y %s" % (pkgng_path, rootdir_arg, package))
  111. if not module.check_mode and query_package(module, pkgng_path, package, rootdir_arg):
  112. module.fail_json(msg="failed to remove %s: %s" % (package, out))
  113. remove_c += 1
  114. if remove_c > 0:
  115. return (True, "removed %s package(s)" % remove_c)
  116. return (False, "package(s) already absent")
  117. def install_packages(module, pkgng_path, packages, cached, pkgsite, rootdir_arg):
  118. install_c = 0
  119. # as of pkg-1.1.4, PACKAGESITE is deprecated in favor of repository definitions
  120. # in /usr/local/etc/pkg/repos
  121. old_pkgng = pkgng_older_than(module, pkgng_path, [1, 1, 4])
  122. if pkgsite != "":
  123. if old_pkgng:
  124. pkgsite = "PACKAGESITE=%s" % (pkgsite)
  125. else:
  126. pkgsite = "-r %s" % (pkgsite)
  127. batch_var = 'env BATCH=yes' # This environment variable skips mid-install prompts,
  128. # setting them to their default values.
  129. if not module.check_mode and not cached:
  130. if old_pkgng:
  131. rc, out, err = module.run_command("%s %s update" % (pkgsite, pkgng_path))
  132. else:
  133. rc, out, err = module.run_command("%s update" % (pkgng_path))
  134. if rc != 0:
  135. module.fail_json(msg="Could not update catalogue")
  136. for package in packages:
  137. if query_package(module, pkgng_path, package, rootdir_arg):
  138. continue
  139. if not module.check_mode:
  140. if old_pkgng:
  141. rc, out, err = module.run_command("%s %s %s install -g -U -y %s" % (batch_var, pkgsite, pkgng_path, package))
  142. else:
  143. rc, out, err = module.run_command("%s %s %s install %s -g -U -y %s" % (batch_var, pkgng_path, rootdir_arg, pkgsite, package))
  144. if not module.check_mode and not query_package(module, pkgng_path, package, rootdir_arg):
  145. module.fail_json(msg="failed to install %s: %s" % (package, out), stderr=err)
  146. install_c += 1
  147. if install_c > 0:
  148. return (True, "added %s package(s)" % (install_c))
  149. return (False, "package(s) already present")
  150. def annotation_query(module, pkgng_path, package, tag, rootdir_arg):
  151. rc, out, err = module.run_command("%s %s info -g -A %s" % (pkgng_path, rootdir_arg, package))
  152. match = re.search(r'^\s*(?P<tag>%s)\s*:\s*(?P<value>\w+)' % tag, out, flags=re.MULTILINE)
  153. if match:
  154. return match.group('value')
  155. return False
  156. def annotation_add(module, pkgng_path, package, tag, value, rootdir_arg):
  157. _value = annotation_query(module, pkgng_path, package, tag, rootdir_arg)
  158. if not _value:
  159. # Annotation does not exist, add it.
  160. rc, out, err = module.run_command('%s %s annotate -y -A %s %s "%s"'
  161. % (pkgng_path, rootdir_arg, package, tag, value))
  162. if rc != 0:
  163. module.fail_json("could not annotate %s: %s"
  164. % (package, out), stderr=err)
  165. return True
  166. elif _value != value:
  167. # Annotation exists, but value differs
  168. module.fail_json(
  169. mgs="failed to annotate %s, because %s is already set to %s, but should be set to %s"
  170. % (package, tag, _value, value))
  171. return False
  172. else:
  173. # Annotation exists, nothing to do
  174. return False
  175. def annotation_delete(module, pkgng_path, package, tag, value, rootdir_arg):
  176. _value = annotation_query(module, pkgng_path, package, tag, rootdir_arg)
  177. if _value:
  178. rc, out, err = module.run_command('%s %s annotate -y -D %s %s'
  179. % (pkgng_path, rootdir_arg, package, tag))
  180. if rc != 0:
  181. module.fail_json("could not delete annotation to %s: %s"
  182. % (package, out), stderr=err)
  183. return True
  184. return False
  185. def annotation_modify(module, pkgng_path, package, tag, value, rootdir_arg):
  186. _value = annotation_query(module, pkgng_path, package, tag, rootdir_arg)
  187. if not value:
  188. # No such tag
  189. module.fail_json("could not change annotation to %s: tag %s does not exist"
  190. % (package, tag))
  191. elif _value == value:
  192. # No change in value
  193. return False
  194. else:
  195. rc,out,err = module.run_command('%s %s annotate -y -M %s %s "%s"'
  196. % (pkgng_path, rootdir_arg, package, tag, value))
  197. if rc != 0:
  198. module.fail_json("could not change annotation annotation to %s: %s"
  199. % (package, out), stderr=err)
  200. return True
  201. def annotate_packages(module, pkgng_path, packages, annotation, rootdir_arg):
  202. annotate_c = 0
  203. annotations = map(lambda _annotation:
  204. re.match(r'(?P<operation>[\+-:])(?P<tag>\w+)(=(?P<value>\w+))?',
  205. _annotation).groupdict(),
  206. re.split(r',', annotation))
  207. operation = {
  208. '+': annotation_add,
  209. '-': annotation_delete,
  210. ':': annotation_modify
  211. }
  212. for package in packages:
  213. for _annotation in annotations:
  214. if operation[_annotation['operation']](module, pkgng_path, package, _annotation['tag'], _annotation['value']):
  215. annotate_c += 1
  216. if annotate_c > 0:
  217. return (True, "added %s annotations." % annotate_c)
  218. return (False, "changed no annotations")
  219. def main():
  220. module = AnsibleModule(
  221. argument_spec = dict(
  222. state = dict(default="present", choices=["present","absent"], required=False),
  223. name = dict(aliases=["pkg"], required=True, type='list'),
  224. cached = dict(default=False, type='bool'),
  225. annotation = dict(default="", required=False),
  226. pkgsite = dict(default="", required=False),
  227. rootdir = dict(default="", required=False)),
  228. supports_check_mode = True)
  229. pkgng_path = module.get_bin_path('pkg', True)
  230. p = module.params
  231. pkgs = p["name"]
  232. changed = False
  233. msgs = []
  234. rootdir_arg = ""
  235. if p["rootdir"] != "":
  236. old_pkgng = pkgng_older_than(module, pkgng_path, [1, 5, 0])
  237. if old_pkgng:
  238. module.fail_json(msg="To use option 'rootdir' pkg version must be 1.5 or greater")
  239. else:
  240. rootdir_arg = "--rootdir %s" % (p["rootdir"])
  241. if p["state"] == "present":
  242. _changed, _msg = install_packages(module, pkgng_path, pkgs, p["cached"], p["pkgsite"], rootdir_arg)
  243. changed = changed or _changed
  244. msgs.append(_msg)
  245. elif p["state"] == "absent":
  246. _changed, _msg = remove_packages(module, pkgng_path, pkgs, rootdir_arg)
  247. changed = changed or _changed
  248. msgs.append(_msg)
  249. if p["annotation"]:
  250. _changed, _msg = annotate_packages(module, pkgng_path, pkgs, p["annotation"], rootdir_arg)
  251. changed = changed or _changed
  252. msgs.append(_msg)
  253. module.exit_json(changed=changed, msg=", ".join(msgs))
  254. # import module snippets
  255. from ansible.module_utils.basic import *
  256. main()