plugins.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. # -*- coding: utf-8 -*-
  2. """
  3. (c) 2019 - Copyright Red Hat Inc
  4. Authors:
  5. Michal Konecny <mkonecny@redhat.com>
  6. """
  7. from __future__ import absolute_import, print_function, unicode_literals
  8. import logging
  9. import flask
  10. from sqlalchemy.exc import SQLAlchemyError
  11. import pagure.exceptions
  12. import pagure.lib.plugins as plugins_lib
  13. import pagure.lib.query
  14. from pagure.api import (
  15. API,
  16. APIERROR,
  17. api_login_optional,
  18. api_login_required,
  19. api_method,
  20. )
  21. from pagure.api.utils import _check_plugin, _check_token, _get_repo
  22. _log = logging.getLogger(__name__)
  23. # List of ignored form fields, these fields will be not returned in response
  24. IGNORED_FIELDS = ["active"]
  25. def _filter_fields(plugin):
  26. """
  27. Filter IGNORED_FIELDS from form and return list of the valid fields.
  28. :arg plugin: plugin class from which to read fields
  29. :type plugin: plugin class
  30. :return: list of valid fields
  31. """
  32. fields = []
  33. for field in plugin.form_fields:
  34. if field not in IGNORED_FIELDS:
  35. fields.append(field)
  36. return fields
  37. @API.route("/<repo>/settings/<plugin>/install", methods=["POST"])
  38. @API.route("/<namespace>/<repo>/settings/<plugin>/install", methods=["POST"])
  39. @API.route(
  40. "/fork/<username>/<repo>/settings/<plugin>/install", methods=["POST"]
  41. )
  42. @API.route(
  43. "/fork/<username>/<namespace>/<repo>/settings/<plugin>/install",
  44. methods=["POST"],
  45. )
  46. @api_login_required(acls=["modify_project"])
  47. @api_method
  48. def api_install_plugin(repo, plugin, username=None, namespace=None):
  49. """
  50. Install plugin
  51. --------------
  52. Install a plugin to a repository.
  53. ::
  54. POST /api/0/<repo>/settings/<plugin>/install
  55. POST /api/0/<namespace>/<repo>/settings/<plugin>/install
  56. ::
  57. POST /api/0/fork/<username>/<repo>/settings/<plugin>/install
  58. POST /api/0/fork/<username>/<namespace>/<repo>/settings/<plugin>
  59. /install
  60. Sample response
  61. ^^^^^^^^^^^^^^^
  62. ::
  63. {
  64. "plugin": {
  65. "mail_to": "serg@wh40k.com"
  66. },
  67. "message": "Hook 'Mail' activated"
  68. }
  69. """
  70. output = {}
  71. repo = _get_repo(repo, username, namespace)
  72. _check_token(repo, project_token=False)
  73. plugin = _check_plugin(repo, plugin)
  74. fields = []
  75. new = True
  76. dbobj = plugin.db_object()
  77. if hasattr(repo, plugin.backref):
  78. dbobj = getattr(repo, plugin.backref)
  79. # There should always be only one, but let's double check
  80. if dbobj:
  81. new = False
  82. else:
  83. dbobj = plugin.db_object()
  84. form = plugin.form(obj=dbobj, meta={"csrf": False})
  85. form.active.data = True
  86. for field in plugin.form_fields:
  87. fields.append(getattr(form, field))
  88. if form.validate_on_submit():
  89. form.populate_obj(obj=dbobj)
  90. if new:
  91. dbobj.project_id = repo.id
  92. flask.g.session.add(dbobj)
  93. try:
  94. flask.g.session.flush()
  95. except SQLAlchemyError: # pragma: no cover
  96. flask.g.session.rollback()
  97. _log.exception("Could not add plugin %s", plugin.name)
  98. raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
  99. try:
  100. # Set up the main script if necessary
  101. plugin.set_up(repo)
  102. # Install the plugin itself
  103. plugin.install(repo, dbobj)
  104. except pagure.exceptions.FileNotFoundException as err:
  105. flask.g.session.rollback()
  106. _log.exception(err)
  107. raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
  108. try:
  109. flask.g.session.commit()
  110. output["message"] = "Hook '%s' activated" % plugin.name
  111. output["plugin"] = {
  112. field: form[field].data for field in _filter_fields(plugin)
  113. }
  114. except SQLAlchemyError as err: # pragma: no cover
  115. flask.g.session.rollback()
  116. _log.exception(err)
  117. raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
  118. else:
  119. raise pagure.exceptions.APIError(
  120. 400, error_code=APIERROR.EINVALIDREQ, errors=form.errors
  121. )
  122. jsonout = flask.jsonify(output)
  123. return jsonout
  124. @API.route("/<repo>/settings/<plugin>/remove", methods=["POST"])
  125. @API.route("/<namespace>/<repo>/settings/<plugin>/remove", methods=["POST"])
  126. @API.route(
  127. "/fork/<username>/<repo>/settings/<plugin>/remove", methods=["POST"]
  128. )
  129. @API.route(
  130. "/fork/<username>/<namespace>/<repo>/settings/<plugin>/remove",
  131. methods=["POST"],
  132. )
  133. @api_login_required(acls=["modify_project"])
  134. @api_method
  135. def api_remove_plugin(repo, plugin, username=None, namespace=None):
  136. """
  137. Remove plugin
  138. --------------
  139. Remove a plugin from repository.
  140. ::
  141. POST /api/0/<repo>/settings/<plugin>/remove
  142. POST /api/0/<namespace>/<repo>/settings/<plugin>/remove
  143. ::
  144. POST /api/0/fork/<username>/<repo>/settings/<plugin>/remove
  145. POST /api/0/fork/<username>/<namespace>/<repo>/settings/<plugin>
  146. /remove
  147. Sample response
  148. ^^^^^^^^^^^^^^^
  149. ::
  150. {
  151. "plugin": {
  152. "mail_to": "serg@wh40k.com"
  153. },
  154. "message": "Hook 'Mail' deactivated"
  155. }
  156. """
  157. output = {}
  158. repo = _get_repo(repo, username, namespace)
  159. _check_token(repo, project_token=False)
  160. plugin = _check_plugin(repo, plugin)
  161. dbobj = plugin.db_object()
  162. enabled_plugins = {
  163. plugin[0]: plugin[1]
  164. for plugin in plugins_lib.get_enabled_plugins(repo)
  165. }
  166. # If the plugin is not installed raise error
  167. if plugin not in enabled_plugins.keys():
  168. raise pagure.exceptions.APIError(
  169. 400, error_code=APIERROR.EPLUGINNOTINSTALLED
  170. )
  171. if enabled_plugins[plugin]:
  172. dbobj = enabled_plugins[plugin]
  173. form = plugin.form(obj=dbobj)
  174. form.active.data = False
  175. try:
  176. plugin.remove(repo)
  177. except pagure.exceptions.FileNotFoundException as err:
  178. flask.g.session.rollback()
  179. _log.exception(err)
  180. raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
  181. try:
  182. flask.g.session.commit()
  183. output["message"] = "Hook '%s' deactivated" % plugin.name
  184. output["plugin"] = {
  185. field: form[field].data for field in _filter_fields(plugin)
  186. }
  187. except SQLAlchemyError as err: # pragma: no cover
  188. flask.g.session.rollback()
  189. _log.exception(err)
  190. raise pagure.exceptions.APIError(400, error_code=APIERROR.EDBERROR)
  191. jsonout = flask.jsonify(output)
  192. return jsonout
  193. @API.route("/<namespace>/<repo>/settings/plugins")
  194. @API.route("/fork/<username>/<repo>/settings/plugins")
  195. @API.route("/<repo>/settings/plugins")
  196. @API.route("/fork/<username>/<namespace>/<repo>/settings/plugins")
  197. @api_login_optional()
  198. @api_method
  199. def api_view_plugins_project(repo, username=None, namespace=None):
  200. """
  201. List project's plugins
  202. ----------------------
  203. List installed plugins on a project.
  204. ::
  205. GET /api/0/<repo>/settings/plugins
  206. GET /api/0/<namespace>/<repo>/settings/plugins
  207. ::
  208. GET /api/0/fork/<username>/<repo>/settings/plugins
  209. GET /api/0/fork/<username>/<namespace>/<repo>/settings/plugins
  210. Sample response
  211. ^^^^^^^^^^^^^^^
  212. ::
  213. {
  214. 'plugins':
  215. [
  216. {
  217. 'Mail':
  218. {
  219. 'mail_to': 'serg@wh40k.com'
  220. }
  221. }
  222. ],
  223. 'total_plugins': 1
  224. }
  225. """
  226. repo = _get_repo(repo, username, namespace)
  227. plugins = {
  228. plugin[0]: plugin[1]
  229. for plugin in plugins_lib.get_enabled_plugins(repo)
  230. }
  231. output = {}
  232. output["plugins"] = []
  233. for (plugin, dbobj) in plugins.items():
  234. if dbobj:
  235. form = plugin.form(obj=dbobj)
  236. fields = _filter_fields(plugin)
  237. output["plugins"].append(
  238. {plugin.name: {field: form[field].data for field in fields}}
  239. )
  240. output["total_plugins"] = len(output["plugins"])
  241. jsonout = flask.jsonify(output)
  242. return jsonout
  243. @API.route("/_plugins")
  244. @api_method
  245. def api_view_plugins():
  246. """
  247. List plugins
  248. ------------
  249. List every plugin available in this pagure instance. For each plugin their
  250. name is provided as well as the name of the argument
  251. to provide to enable/disable them.
  252. ::
  253. GET /api/0/plugins
  254. Sample response
  255. ^^^^^^^^^^^^^^^
  256. ::
  257. {
  258. 'plugins': [
  259. {
  260. 'Block Un-Signed commits': [
  261. ]
  262. },
  263. {
  264. 'Block non fast-forward pushes': [
  265. 'branches',
  266. ]
  267. },
  268. {
  269. 'Fedmsg': [
  270. ]
  271. },
  272. ],
  273. 'total_issues': 3
  274. }
  275. """
  276. plugins = plugins_lib.get_plugin_names()
  277. output = {}
  278. output["total_plugins"] = len(plugins)
  279. output["plugins"] = []
  280. for plugin_name in plugins:
  281. # Skip plugins that are disabled
  282. if plugin_name in pagure.config.config.get("DISABLED_PLUGINS", []):
  283. continue
  284. plugin = plugins_lib.get_plugin(plugin_name)
  285. fields = _filter_fields(plugin)
  286. output["plugins"].append({plugin_name: fields})
  287. jsonout = flask.jsonify(output)
  288. return jsonout