[PATCH 3/3] [PW3] Remove XML-RPC API
Daniel Axtens
dja at axtens.net
Fri Oct 18 13:41:49 AEDT 2019
It's been deprecated since 2.0 with the new REST API. That API is
now pretty solid, and git-pw is good. Drop the old API.
Provide a page letting people know that the API is gone if they
access any of the old pages.
This breaks pwclient, which only supports the old API. So we delete
a few things that referred to it or used it, including some old tools.
Signed-off-by: Daniel Axtens <dja at axtens.net>
---
docs/TODO | 6 -
docs/api/index.rst | 5 +-
docs/api/rest/index.rst | 4 +-
docs/api/xmlrpc.rst | 64 --
docs/deployment/configuration.rst | 4 -
docs/deployment/installation.rst | 35 +-
docs/deployment/management.rst | 6 +-
docs/development/api.rst | 5 +-
docs/index.rst | 1 -
docs/usage/clients.rst | 27 +-
patchwork/settings/base.py | 3 -
patchwork/settings/dev.py | 2 -
patchwork/templates/patchwork/about.html | 10 -
patchwork/templates/patchwork/project.html | 16 +-
patchwork/templates/patchwork/pwclientrc | 15 -
.../templates/patchwork/xmlrpc-removed.html | 16 +
patchwork/tests/test_about.py | 16 -
patchwork/tests/test_xmlrpc.py | 224 -----
patchwork/urls.py | 20 +-
patchwork/views/about.py | 6 +-
patchwork/views/project.py | 2 -
patchwork/views/pwclient.py | 28 -
patchwork/views/removed.py | 11 +
patchwork/views/xmlrpc.py | 951 ------------------
.../notes/remove-xmlrpc-b6d26084338efcb4.yaml | 11 +
tools/patchwork-update-commits | 20 -
tools/post-receive.hook | 86 --
27 files changed, 61 insertions(+), 1533 deletions(-)
delete mode 100644 docs/api/xmlrpc.rst
delete mode 100644 patchwork/templates/patchwork/pwclientrc
create mode 100644 patchwork/templates/patchwork/xmlrpc-removed.html
delete mode 100644 patchwork/tests/test_xmlrpc.py
delete mode 100644 patchwork/views/pwclient.py
create mode 100644 patchwork/views/removed.py
delete mode 100644 patchwork/views/xmlrpc.py
create mode 100644 releasenotes/notes/remove-xmlrpc-b6d26084338efcb4.yaml
delete mode 100755 tools/patchwork-update-commits
delete mode 100755 tools/post-receive.hook
diff --git docs/TODO docs/TODO
index 37c30fd951dd..693e4bb743be 100644
--- docs/TODO
+++ docs/TODO
@@ -7,10 +7,4 @@
* store rejected mails
* In-message From: header
* Per-user default filter settings
-* pwclient: -b <bundle> to specify multiple patch ids
-* pwclient: add bundle manipulation and retrieval ops
-* pwclient: specify multiple patches by ID range
-* pwclient: integrate hash parser
-* pwclient: add example scripts (eg, git post-commit hook) to online help
-* pwclient: case-insensitive searches for author and project name
* changing primary email addresses for accounts
diff --git docs/api/index.rst docs/api/index.rst
index c679dae133bf..6e89b7197e39 100644
--- docs/api/index.rst
+++ docs/api/index.rst
@@ -3,12 +3,9 @@
API Documentation
=================
-Patchwork provides two APIs: a REST API and a legacy XML-RPC API. The REST API
-is recommended as the XML-RPC API is deprecated and will be removed in a future
-release.
+Patchwork provides a REST API.
.. toctree::
:maxdepth: 2
/api/rest/index
- /api/xmlrpc
diff --git docs/api/rest/index.rst docs/api/rest/index.rst
index d1169e56e07a..293490b2874f 100644
--- docs/api/rest/index.rst
+++ docs/api/rest/index.rst
@@ -25,8 +25,8 @@ If all you want is reference guides, skip straight to :ref:`rest-api-schemas`.
.. versionadded:: 2.0
The REST API was introduced in Patchwork v2.0. Users of earlier Patchwork
- versions should instead refer to :doc:`XML-RPC API </api/xmlrpc>`
- documentation.
+ versions should instead refer to XML-RPC API documentation provided with
+ those versions.
.. versionchanged:: 2.1
diff --git docs/api/xmlrpc.rst docs/api/xmlrpc.rst
deleted file mode 100644
index 5412cce5d155..000000000000
--- docs/api/xmlrpc.rst
+++ /dev/null
@@ -1,64 +0,0 @@
-The XML-RPC API
-===============
-
-Patchwork provides an XML-RPC API. This API can be used to be used to retrieve
-and modify information about patches, projects and more.
-
-.. important::
-
- The XML-RPC API can be enabled/disabled by the administrator: it may not be
- available in every instance. Refer to ``/about`` on your given instance for
- the status of the API, e.g.
-
- https://patchwork.ozlabs.org/about
-
- Alternatively, simply attempt to make a request to the API.
-
-.. deprecated:: 2.0
-
- The XML-RPC API is a legacy API and has been deprecated in favour of the
- :doc:`REST API <rest/index>`. It will be removed in Patchwork 3.0.
-
-Getting Started
----------------
-
-The Patchwork XML-RPC API provides a number of "methods". Some methods require
-authentication (via HTTP Basic Auth) while others do not. Authentication uses
-your Patchwork account and the on-server documentation will indicate where it
-is necessary. We will only cover the unauthenticated method here for brevity -
-consult the `xmlrpclib`_ documentation for more detailed examples:
-
-To interact with the Patchwork XML-RPC API, a XML-RPC library should be used.
-Python provides such a library - `xmlrpclib`_ - in its standard library. For
-example, to get the version of the XML-RPC API for a Patchwork instance hosted
-at `patchwork.example.com`, run:
-
-.. code-block:: pycon
-
- $ python
- >>> import xmlrpclib # or 'xmlrpc.client' for Python 3
- >>> rpc = xmlrpclib.ServerProxy('http://patchwork.example.com/xmlrpc/')
- >>> rpc.pw_rpc_version()
- 1.1
-
-Once connected, the ``rpc`` object will be populated with a list of available
-functions (or procedures, in RPC terminology). In the above example, we used
-the ``pw_rpc_version`` method, however, it should be possible to use all the
-methods listed in the server documentation.
-
-Further Information
--------------------
-
-Patchwork provides automatically generated documentation for the XML-RPC API.
-You can find this at the following URL:
-
- https://patchwork.example.com/xmlrpc/
-
-where `patchwork.example.com` refers to the URL of your Patchwork instance.
-
-.. versionchanged:: 1.1
-
- Automatic documentation generation for the Patchwork API was introduced in
- Patchwork v1.1. Prior versions of Patchwork do not offer this functionality.
-
-.. _xmlrpclib: https://docs.python.org/2/library/xmlrpclib.html
diff --git docs/deployment/configuration.rst docs/deployment/configuration.rst
index a71dd3f4bb57..482712f96773 100644
--- docs/deployment/configuration.rst
+++ docs/deployment/configuration.rst
@@ -80,10 +80,6 @@ Enable the :doc:`REST API <../api/rest/index>`.
.. versionadded:: 2.0
-``ENABLE_XMLRPC``
-~~~~~~~~~~~~~~~~~
-
-Enable the :doc:`XML-RPC API <../api/xmlrpc>`.
.. TODO(stephenfin) Deprecate this in favor of SECURE_SSL_REDIRECT
diff --git docs/deployment/installation.rst docs/deployment/installation.rst
index f477a110f292..a505796e6381 100644
--- docs/deployment/installation.rst
+++ docs/deployment/installation.rst
@@ -280,15 +280,8 @@ described in :doc:`configuration`.
* ``NOTIFICATION_FROM_EMAIL``
These are not configurable using environment variables and must be configured
-directly in the ``production.py`` settings file instead. For example, if you
-wish to enable the XML-RPC API, you should add the following:
-
-.. code-block:: python
-
- ENABLE_XMLRPC = True
-
-Similarly, should you wish to disable the REST API, you should add the
-following:
+directly in the ``production.py`` settings file instead. For example, should
+you wish to disable the REST API, you should add the following:
.. code-block:: python
@@ -506,8 +499,7 @@ doing the following:
Once the administrative console is accessible, you would want to configure your
different sites and their corresponding domain names, which is required for the
-different emails sent by Patchwork (registration, password recovery) as well as
-the sample ``pwclientrc`` files provided by your project's page.
+different emails sent by Patchwork (registration, password recovery).
.. _deployment-parsemail:
@@ -646,27 +638,6 @@ You can also create such as service yourself using a PaaS provider that
supports incoming mail and writing a little web app.
-.. _deployment-vcs:
-
-(Optional) Configure your VCS to Automatically Update Patches
--------------------------------------------------------------
-
-The ``tools`` directory of the Patchwork distribution contains a file named
-``post-receive.hook`` which is a sample Git hook that can be used to
-automatically update patches to the *Accepted* state when corresponding commits
-are pushed via Git.
-
-To install this hook, simply copy it to the ``.git/hooks`` directory on your
-server, name it ``post-receive``, and make it executable.
-
-This sample hook has support to update patches to different states depending on
-which branch is being pushed to. See the ``STATE_MAP`` setting in that file.
-
-If you are using a system other than Git, you can likely write a similar hook
-using the :doc:`APIs </api/index>` or :doc:`API clients </usage/clients>` to to
-update patch state. If you do write one, please contribute it.
-
-
.. _deployment-cron:
(Optional) Configure the Patchwork Cron Job
diff --git docs/deployment/management.rst docs/deployment/management.rst
index 9c57f1962283..0881dc296cc9 100644
--- docs/deployment/management.rst
+++ docs/deployment/management.rst
@@ -128,9 +128,9 @@ Update the hashes on existing patches.
./manage.py rehash [<patch_id>, ...]
Patchwork stores hashes for each patch it receives. These hashes can be used to
-uniquely identify a patch for things like :ref:`automatically changing the
-state of the patch in Patchwork when it merges <deployment-vcs>`. If you change
-your hashing algorithm, you may wish to rehash the patches.
+uniquely identify a patch for things like automatically changing the state of
+the patch in Patchwork when it is merged. If you change your hashing algorithm,
+you may wish to rehash the patches.
.. option:: patch_id
diff --git docs/development/api.rst docs/development/api.rst
index cea7bc78dd55..e5298e5e9d23 100644
--- docs/development/api.rst
+++ docs/development/api.rst
@@ -3,9 +3,8 @@
Using the APIs
==============
-Patchwork provides two APIs: the legacy :doc:`XML-RPC API </api/xmlrpc>` and
-the :doc:`REST API </api/rest/index>`. You can use these APIs to interact with
-Patchwork programmatically and to develop your own clients.
+Patchwork provides a :doc:`REST API </api/rest/index>`. You can use this API
+to interact with Patchwork programmatically and to develop your own clients.
For quick usage examples of the APIs, refer to the :doc:`documentation
<../api/index>`. For examples of existing clients, refer to
diff --git docs/index.rst docs/index.rst
index b73c647c5222..1eff93d83080 100644
--- docs/index.rst
+++ docs/index.rst
@@ -51,7 +51,6 @@ of community projects.
:caption: API Documentation
api/rest/index
- api/xmlrpc
.. toctree::
:maxdepth: 2
diff --git docs/usage/clients.rst docs/usage/clients.rst
index 01dd62a28e50..40b93f27b344 100644
--- docs/usage/clients.rst
+++ docs/usage/clients.rst
@@ -1,31 +1,8 @@
Clients
=======
-A number of clients are available for interacting with Patchwork's various
-APIs.
-
-
-pwclient
---------
-
-.. versionchanged:: 2.2
-
- :program:`pwclient` was previously provided with Patchwork. It has been
- packaged as a separate application since Patchwork v2.2.0.
-
-The :program:`pwclient` application can be used to interact with Patchwork from
-the command line. Functionality provided by :program:`pwclient` includes:
-
-- Listing patches, projects, and checks
-- Downloading and applying patches to a local code base
-- Modifying the status of patches
-- Creating new checks
-
-More information on :program:`pwclient`, including installation and usage
-instructions, can be found in the `documentation`__ and the `GitHub repo`__.
-
-__ https://pwclient.readthedocs.io/
-__ https://github.com/getpatchwork/pwclient/
+A REST client is available for interacting with Patchwork's API, and other
+projects build on top of the API to provide other functionality.
git-pw
diff --git patchwork/settings/base.py patchwork/settings/base.py
index b86cdc276d5a..fbb0b0d8565b 100644
--- patchwork/settings/base.py
+++ patchwork/settings/base.py
@@ -211,9 +211,6 @@ NOTIFICATION_DELAY_MINUTES = 10
NOTIFICATION_FROM_EMAIL = DEFAULT_FROM_EMAIL
-# Set to True to enable the Patchwork XML-RPC interface
-ENABLE_XMLRPC = False
-
# Set to True to enable the Patchwork REST API
ENABLE_REST_API = True
diff --git patchwork/settings/dev.py patchwork/settings/dev.py
index e110e74579ca..141915fdd001 100644
--- patchwork/settings/dev.py
+++ patchwork/settings/dev.py
@@ -99,6 +99,4 @@ if dbbackup:
# Patchwork settings
#
-ENABLE_XMLRPC = True
-
ENABLE_REST_API = True
diff --git patchwork/templates/patchwork/about.html patchwork/templates/patchwork/about.html
index 210e9513c4f4..9c83ba0e4173 100644
--- patchwork/templates/patchwork/about.html
+++ patchwork/templates/patchwork/about.html
@@ -56,16 +56,6 @@
<span class="label label-warning pull-right">disabled</span>
{% endif %}
</li>
- <li class="list-group-item">
- XML-RPC
- <span class="glyphicon glyphicon-question-sign" title="The XML-RPC
- API"></span>
- {% if enabled_apis.xmlrpc %}
- <span class="label label-success pull-right">enabled</span>
- {% else %}
- <span class="label label-warning pull-right">disabled</span>
- {% endif %}
- </li>
</ul>
</div>
</div>
diff --git patchwork/templates/patchwork/project.html patchwork/templates/patchwork/project.html
index bd9d20e263d8..ce647b9df04b 100644
--- patchwork/templates/patchwork/project.html
+++ patchwork/templates/patchwork/project.html
@@ -55,19 +55,5 @@
{% endif %}
</table>
-{% if enable_xmlrpc %}
-<h2>pwclient</h2>
-
-<p><code>pwclient</code> is the command-line client for Patchwork. Currently,
-it provides access to some read-only features of Patchwork, such as downloading
-and applying patches.</p>
-
-<p>To use pwclient, you will need:</p>
-<ul>
- <li>The <a href="https://github.com/getpatchwork/pwclient">pwclient</a> program</li>
- <li>(optional) A <code><a href="{% url 'pwclientrc' project.linkname %}"
- >.pwclientrc</a></code> file for this project, which should be stored in your
- home directory.</li>
-</ul>
-{% endif %}
+<!-- TODO add git pw info -->
{% endblock %}
diff --git patchwork/templates/patchwork/pwclientrc patchwork/templates/patchwork/pwclientrc
deleted file mode 100644
index 7d466d890da5..000000000000
--- patchwork/templates/patchwork/pwclientrc
+++ /dev/null
@@ -1,15 +0,0 @@
-# Sample .pwclientrc file for the {{ project.linkname }} project,
-# running on {{ site.domain }}.
-#
-# Just append this file to your existing ~/.pwclientrc
-# If you do not already have a ~/.pwclientrc, then copy this file to
-# ~/.pwclientrc, and uncomment the following two lines:
-# [options]
-# default={{ project.linkname }}
-
-[{{ project.linkname }}]
-url = {{ scheme }}://{{ site.domain }}{% url 'xmlrpc' %}
-{% if user.is_authenticated %}
-username = {{ user.username }}
-password = <add your patchwork password here>
-{% endif %}
diff --git patchwork/templates/patchwork/xmlrpc-removed.html patchwork/templates/patchwork/xmlrpc-removed.html
new file mode 100644
index 000000000000..bc2e41ebf313
--- /dev/null
+++ patchwork/templates/patchwork/xmlrpc-removed.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+
+{% block title %}XML-RPC API removed{% endblock %}
+{% block heading %}XML-RPC API removed{% endblock %}
+
+{% block body %}
+<div class="container">
+ <h1>XML-RPC API removed</h1>
+
+ <p>The XML-RPC API has been removed in Patchwork 3 in favour of the REST API.</p>
+
+ <p>If you were using pwclient, try
+ <a href="https://github.com/getpatchwork/git-pw" target="_blank">git-pw</a>.
+ </p>
+</div>
+{% endblock %}
diff --git patchwork/tests/test_about.py patchwork/tests/test_about.py
index d8c35b9f3e5f..2d9385d8ea8b 100644
--- patchwork/tests/test_about.py
+++ patchwork/tests/test_about.py
@@ -15,7 +15,6 @@ class AboutViewTest(TestCase):
def _test_redirect(self, view):
requested_url = reverse(view)
redirect_url = reverse('about')
-
response = self.client.get(requested_url)
self.assertRedirects(response, redirect_url, 301)
@@ -23,21 +22,6 @@ class AboutViewTest(TestCase):
for view in ['help', 'help-about']:
self._test_redirect(view)
- @unittest.skipUnless(settings.ENABLE_XMLRPC,
- 'requires xmlrpc interface (use the ENABLE_XMLRPC '
- 'setting)')
- def test_redirects_xmlrpc(self):
- self._test_redirect('help-pwclient')
-
- def test_xmlrpc(self):
- with self.settings(ENABLE_XMLRPC=False):
- response = self.client.get(reverse('about'))
- self.assertFalse(response.context['enabled_apis']['xmlrpc'])
-
- with self.settings(ENABLE_XMLRPC=True):
- response = self.client.get(reverse('about'))
- self.assertTrue(response.context['enabled_apis']['xmlrpc'])
-
def test_rest(self):
with self.settings(ENABLE_REST_API=False):
response = self.client.get(reverse('about'))
diff --git patchwork/tests/test_xmlrpc.py patchwork/tests/test_xmlrpc.py
deleted file mode 100644
index 79c6c848a0c2..000000000000
--- patchwork/tests/test_xmlrpc.py
+++ /dev/null
@@ -1,224 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2014 Jeremy Kerr <jk at ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-import unittest
-
-from django.conf import settings
-from django.test import LiveServerTestCase
-from django.urls import reverse
-from django.utils.six.moves import xmlrpc_client
-
-from patchwork.tests import utils
-
-
- at unittest.skipUnless(settings.ENABLE_XMLRPC,
- 'requires xmlrpc interface (use the ENABLE_XMLRPC '
- 'setting)')
-class XMLRPCTest(LiveServerTestCase):
-
- def setUp(self):
- self.url = self.live_server_url + reverse('xmlrpc')
- self.rpc = xmlrpc_client.Server(self.url)
-
-
-class XMLRPCGenericTest(XMLRPCTest):
-
- def test_pw_rpc_version(self):
- # If you update the RPC version, update the tests!
- self.assertEqual(self.rpc.pw_rpc_version(), [1, 3, 0])
-
- def test_get_redirect(self):
- response = self.client.patch(self.url)
- self.assertRedirects(response, reverse('project-list'))
-
- def test_invalid_method(self):
- with self.assertRaises(xmlrpc_client.Fault):
- self.rpc.xyzzy()
-
- def test_absent_auth(self):
- with self.assertRaises(xmlrpc_client.Fault):
- self.rpc.patch_set(0, {})
-
-
- at unittest.skipUnless(settings.ENABLE_XMLRPC,
- 'requires xmlrpc interface (use the ENABLE_XMLRPC '
- 'setting)')
-class XMLRPCAuthenticatedTest(LiveServerTestCase):
-
- def setUp(self):
- self.url = self.live_server_url + reverse('xmlrpc')
- # url is of the form http://localhost:PORT/PATH
- # strip the http and replace it with the username/passwd of a user.
- self.project = utils.create_project()
- self.user = utils.create_maintainer(self.project)
- self.url = ('http://%s:%s@' + self.url[7:]) % (self.user.username,
- self.user.username)
- self.rpc = xmlrpc_client.Server(self.url)
-
- def test_patch_set(self):
- patch = utils.create_patch(project=self.project)
- result = self.rpc.patch_get(patch.id)
- self.assertFalse(result['archived'])
-
- self.rpc.patch_set(patch.id, {'archived': True})
-
- # reload the patch
- result = self.rpc.patch_get(patch.id)
- self.assertTrue(result['archived'])
-
-
-class XMLRPCModelTestMixin(object):
-
- def create_multiple(self, count):
- return [self.create_single() for i in range(count)]
-
- def test_get_none(self):
- self.assertEqual(self.get_endpoint(0), {})
-
- def test_list_none(self):
- self.assertEqual(self.list_endpoint(), [])
-
- def test_list_single(self):
- obj = self.create_single()
- result = self.list_endpoint()
- self.assertEqual(len(result), 1)
- self.assertEqual(result[0]['id'], obj.id)
-
- def test_list_named(self):
- obj = self.create_single(name='FOOBARBAZ')
- self.create_multiple(5)
- result = self.list_endpoint('oobarb')
- self.assertEqual(len(result), 1)
- self.assertEqual(result[0]['id'], obj.id)
-
- def test_list_named_none(self):
- self.create_multiple(5)
- result = self.list_endpoint('invisible')
- self.assertEqual(len(result), 0)
-
- def test_get_single(self):
- obj = self.create_single()
- result = self.get_endpoint(obj.id)
- self.assertEqual(result['id'], obj.id)
-
- def test_get_invalid(self):
- obj = self.create_single()
- result = self.get_endpoint(obj.id + 1)
- self.assertEqual(result, {})
-
- def test_list_multiple(self):
- self.create_multiple(5)
- result = self.list_endpoint()
- self.assertEqual(len(result), 5)
-
- def test_list_max_count(self):
- objs = self.create_multiple(5)
- result = self.list_endpoint("", 2)
- self.assertEqual(len(result), 2)
- self.assertEqual(result[0]['id'], objs[0].id)
-
- def test_list_negative_max_count(self):
- objs = self.create_multiple(5)
- result = self.list_endpoint("", -1)
- self.assertEqual(len(result), 1)
- self.assertEqual(result[0]['id'], objs[-1].id)
-
-
-class XMLRPCFilterModelTestMixin(XMLRPCModelTestMixin):
-
- # override these tests due to the way you pass in filters
- def test_list_max_count(self):
- objs = self.create_multiple(5)
- result = self.list_endpoint({'max_count': 2})
- self.assertEqual(len(result), 2)
- self.assertEqual(result[0]['id'], objs[0].id)
-
- def test_list_negative_max_count(self):
- objs = self.create_multiple(5)
- result = self.list_endpoint({'max_count': -1})
- self.assertEqual(len(result), 1)
- self.assertEqual(result[0]['id'], objs[-1].id)
-
- def test_list_named(self):
- obj = self.create_single(name='FOOBARBAZ')
- self.create_multiple(5)
- result = self.list_endpoint({'name__icontains': 'oobarb'})
- self.assertEqual(len(result), 1)
- self.assertEqual(result[0]['id'], obj.id)
-
- def test_list_named_none(self):
- self.create_multiple(5)
- result = self.list_endpoint({'name__icontains': 'invisible'})
- self.assertEqual(len(result), 0)
-
-
-class XMLRPCPatchTest(XMLRPCTest, XMLRPCFilterModelTestMixin):
- def setUp(self):
- super(XMLRPCPatchTest, self).setUp()
- self.get_endpoint = self.rpc.patch_get
- self.list_endpoint = self.rpc.patch_list
- self.create_multiple = utils.create_patches
-
- def create_single(self, **kwargs):
- return utils.create_patches(**kwargs)[0]
-
- def test_patch_check_get(self):
- patch = self.create_single()
- check = utils.create_check(patch=patch)
- result = self.rpc.patch_check_get(patch.id)
- self.assertEqual(result['total'], 1)
- self.assertEqual(result['checks'][0]['id'], check.id)
- self.assertEqual(result['checks'][0]['patch_id'], patch.id)
-
- def test_patch_get_by_hash(self):
- patch = self.create_single()
- result = self.rpc.patch_get_by_hash(patch.hash)
- self.assertEqual(result['id'], patch.id)
-
-
-class XMLRPCPersonTest(XMLRPCTest, XMLRPCModelTestMixin):
-
- def setUp(self):
- super(XMLRPCPersonTest, self).setUp()
- self.get_endpoint = self.rpc.person_get
- self.list_endpoint = self.rpc.person_list
- self.create_single = utils.create_person
-
-
-class XMLRPCProjectTest(XMLRPCTest, XMLRPCModelTestMixin):
-
- def setUp(self):
- super(XMLRPCProjectTest, self).setUp()
- self.get_endpoint = self.rpc.project_get
- self.list_endpoint = self.rpc.project_list
- self.create_single = utils.create_project
-
- def test_list_named(self):
- # project filters by linkname, not name!
- obj = self.create_single(linkname='FOOBARBAZ')
- result = self.list_endpoint('oobarb')
- self.assertEqual(len(result), 1)
- self.assertEqual(result[0]['id'], obj.id)
-
-
-class XMLRPCStateTest(XMLRPCTest, XMLRPCModelTestMixin):
-
- def setUp(self):
- super(XMLRPCStateTest, self).setUp()
- self.get_endpoint = self.rpc.state_get
- self.list_endpoint = self.rpc.state_list
- self.create_single = utils.create_state
-
-
-class XMLRPCCheckTest(XMLRPCTest, XMLRPCFilterModelTestMixin):
-
- def setUp(self):
- super(XMLRPCCheckTest, self).setUp()
- self.get_endpoint = self.rpc.check_get
- self.list_endpoint = self.rpc.check_list
- self.create_single = utils.create_check
-
- def test_list_named(self):
- pass
diff --git patchwork/urls.py patchwork/urls.py
index dcdcfb49e67e..2f2b448da331 100644
--- patchwork/urls.py
+++ patchwork/urls.py
@@ -18,10 +18,9 @@ from patchwork.views import mail as mail_views
from patchwork.views import notification as notification_views
from patchwork.views import patch as patch_views
from patchwork.views import project as project_views
-from patchwork.views import pwclient as pwclient_views
from patchwork.views import series as series_views
from patchwork.views import user as user_views
-from patchwork.views import xmlrpc as xmlrpc_views
+from patchwork.views import removed as removed_views
admin.autodiscover()
@@ -163,16 +162,6 @@ if 'debug_toolbar' in settings.INSTALLED_APPS:
url(r'^__debug__/', include(debug_toolbar.urls)),
]
-if settings.ENABLE_XMLRPC:
- urlpatterns += [
- url(r'xmlrpc/$', xmlrpc_views.xmlrpc, name='xmlrpc'),
- url(r'^project/(?P<project_id>[^/]+)/pwclientrc/$',
- pwclient_views.pwclientrc,
- name='pwclientrc'),
- # legacy redirect
- url(r'^help/pwclient/$', about_views.redirect, name='help-pwclient'),
- ]
-
if settings.ENABLE_REST_API:
if 'rest_framework' not in settings.INSTALLED_APPS:
raise RuntimeError(
@@ -276,3 +265,10 @@ if settings.COMPAT_REDIR:
bundle_views.bundle_mbox_redir,
name='bundle-mbox-redir'),
]
+
+ urlpatterns += [
+ url(r'xmlrpc/$', removed_views.xmlrpc_removed),
+ url(r'^project/(?P<project_id>[^/]+)/pwclientrc/$',
+ removed_views.xmlrpc_removed),
+ url(r'^help/pwclient/$', removed_views.xmlrpc_removed),
+ ]
diff --git patchwork/views/about.py patchwork/views/about.py
index 91c3b74ebf8f..0a6f75d3b218 100644
--- patchwork/views/about.py
+++ patchwork/views/about.py
@@ -14,7 +14,6 @@ def about(request):
context = {
'enabled_apis': {
'rest': settings.ENABLE_REST_API,
- 'xmlrpc': settings.ENABLE_XMLRPC,
},
'admins': () if settings.ADMINS_HIDE else settings.ADMINS,
}
@@ -23,8 +22,5 @@ def about(request):
def redirect(request):
- """Redirect for legacy URLs.
-
- Remove this when Patchwork 3.0 is released.
- """
+ """Redirect for legacy URLs."""
return HttpResponsePermanentRedirect(reverse('about'))
diff --git patchwork/views/project.py patchwork/views/project.py
index 8fa41794f5db..621457d5b732 100644
--- patchwork/views/project.py
+++ patchwork/views/project.py
@@ -3,7 +3,6 @@
#
# SPDX-License-Identifier: GPL-2.0-or-later
-from django.conf import settings
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
@@ -59,6 +58,5 @@ def project_detail(request, project_id):
profile__maintainer_projects=project).select_related('profile'),
'n_patches': n_patches[False] if False in n_patches else 0,
'n_archived_patches': n_patches[True] if True in n_patches else 0,
- 'enable_xmlrpc': settings.ENABLE_XMLRPC,
}
return render(request, 'patchwork/project.html', context)
diff --git patchwork/views/pwclient.py patchwork/views/pwclient.py
deleted file mode 100644
index 72ebcbbb9e90..000000000000
--- patchwork/views/pwclient.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk at ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-from django.conf import settings
-from django.shortcuts import get_object_or_404
-from django.shortcuts import render
-
-from patchwork.models import Project
-
-
-def pwclientrc(request, project_id):
- project = get_object_or_404(Project, linkname=project_id)
-
- context = {
- 'project': project,
- }
- if settings.FORCE_HTTPS_LINKS or request.is_secure():
- context['scheme'] = 'https'
- else:
- context['scheme'] = 'http'
-
- response = render(request, 'patchwork/pwclientrc', context,
- content_type='text/plain')
- response['Content-Disposition'] = 'attachment; filename=.pwclientrc'
-
- return response
diff --git patchwork/views/removed.py patchwork/views/removed.py
new file mode 100644
index 000000000000..7db3c0e3b8d0
--- /dev/null
+++ patchwork/views/removed.py
@@ -0,0 +1,11 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2019 IBM Corporation
+# Author: Daniel Axtens <dja at axtens.net>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from django.shortcuts import render
+
+
+def xmlrpc_removed(request, project_id=None):
+ return render(request, 'patchwork/xmlrpc-removed.html', {}, status=410)
diff --git patchwork/views/xmlrpc.py patchwork/views/xmlrpc.py
deleted file mode 100644
index f60725044ebe..000000000000
--- patchwork/views/xmlrpc.py
+++ /dev/null
@@ -1,951 +0,0 @@
-# Patchwork - automated patch tracking system
-# Copyright (C) 2008 Jeremy Kerr <jk at ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-import base64
-# NOTE(stephenfin) six does not seem to support this
-try:
- from DocXMLRPCServer import XMLRPCDocGenerator
-except ImportError:
- from xmlrpc.server import XMLRPCDocGenerator
-import sys
-
-from django.contrib.auth import authenticate
-from django.http import HttpResponse
-from django.http import HttpResponseRedirect
-from django.http import HttpResponseServerError
-from django.views.decorators.csrf import csrf_exempt
-from django.urls import reverse
-from django.utils import six
-from django.utils.six.moves import xmlrpc_client
-from django.utils.six.moves.xmlrpc_server import SimpleXMLRPCDispatcher
-
-from patchwork.models import Check
-from patchwork.models import Patch
-from patchwork.models import Person
-from patchwork.models import Project
-from patchwork.models import State
-from patchwork.views.utils import patch_to_mbox
-
-
-class PatchworkXMLRPCDispatcher(SimpleXMLRPCDispatcher,
- XMLRPCDocGenerator):
-
- server_name = 'Patchwork XML-RPC API'
- server_title = 'Patchwork XML-RPC API v1 Documentation'
-
- def __init__(self):
- SimpleXMLRPCDispatcher.__init__(self, allow_none=False,
- encoding=None)
- XMLRPCDocGenerator.__init__(self)
-
- def _dumps(obj, *args, **kwargs):
- kwargs['allow_none'] = self.allow_none
- kwargs['encoding'] = self.encoding
- return xmlrpc_client.dumps(obj, *args, **kwargs)
-
- self.dumps = _dumps
-
- # map of name => (auth, func)
- self.func_map = {}
-
- def register_function(self, fn, auth_required):
- self.funcs[fn.__name__] = fn # needed by superclass methods
- self.func_map[fn.__name__] = (auth_required, fn)
-
- def _user_for_request(self, request):
- auth_header = None
-
- if 'HTTP_AUTHORIZATION' in request.META:
- auth_header = request.META.get('HTTP_AUTHORIZATION')
- elif 'Authorization' in request.META:
- auth_header = request.META.get('Authorization')
-
- if auth_header is None or auth_header == '':
- raise Exception('No authentication credentials given')
-
- header = auth_header.strip()
-
- if not header.startswith('Basic '):
- raise Exception('Authentication scheme not supported')
-
- header = header[len('Basic '):].strip()
-
- try:
- decoded = base64.b64decode(header.encode('ascii')).decode('ascii')
- username, password = decoded.split(':', 1)
- except ValueError:
- raise Exception('Invalid authentication credentials')
-
- return authenticate(username=username, password=password)
-
- def _dispatch(self, request, method, params):
- if method not in list(self.func_map.keys()):
- raise Exception('method "%s" is not supported' % method)
-
- auth_required, fn = self.func_map[method]
-
- if auth_required:
- user = self._user_for_request(request)
- if not user:
- raise Exception('Invalid username/password')
-
- params = (user,) + params
-
- return fn(*params)
-
- def _marshaled_dispatch(self, request):
- try:
- params, method = six.moves.xmlrpc_client.loads(request.body)
-
- response = self._dispatch(request, method, params)
- # wrap response in a singleton tuple
- response = (response,)
- response = self.dumps(response, methodresponse=1)
- except six.moves.xmlrpc_client.Fault as fault:
- response = self.dumps(fault)
- except Exception: # noqa
- # report exception back to server
- response = self.dumps(
- six.moves.xmlrpc_client.Fault(
- 1, '%s:%s' % (sys.exc_info()[0], sys.exc_info()[1])),
- )
-
- return response
-
-
-dispatcher = PatchworkXMLRPCDispatcher()
-
-# XMLRPC view function
-
-
- at csrf_exempt
-def xmlrpc(request):
- if request.method not in ['POST', 'GET']:
- return HttpResponseRedirect(reverse('project-list'))
-
- response = HttpResponse()
-
- if request.method == 'POST':
- try:
- ret = dispatcher._marshaled_dispatch(request)
- except Exception: # noqa
- return HttpResponseServerError()
- else:
- ret = dispatcher.generate_html_documentation()
-
- response.write(ret)
-
- return response
-
-# decorator for XMLRPC methods. Setting login_required to true will call
-# the decorated function with a non-optional user as the first argument.
-
-
-def xmlrpc_method(login_required=False):
- def wrap(f):
- dispatcher.register_function(f, login_required)
- return f
-
- return wrap
-
-
-# We allow most of the Django field lookup types for remote queries
-LOOKUP_TYPES = ['iexact', 'contains', 'icontains', 'gt', 'gte', 'lt',
- 'in', 'startswith', 'istartswith', 'endswith',
- 'iendswith', 'range', 'year', 'month', 'day', 'isnull']
-
-
-#######################################################################
-# Helper functions
-#######################################################################
-
-def project_to_dict(obj):
- """Serialize a project object.
-
- Return a trimmed down dictionary representation of a Project
- object which is safe to send to the client. For example:
-
- {
- 'id': 1,
- 'linkname': 'my-project',
- 'name': 'My Project',
- }
-
- Args:
- Project object to serialize.
-
- Returns:
- Serialized Project object.
- """
- return {
- 'id': obj.id,
- 'linkname': obj.linkname,
- 'name': obj.name,
- }
-
-
-def person_to_dict(obj):
- """Serialize a person object.
-
- Return a trimmed down dictionary representation of a Person
- object which is safe to send to the client. For example:
-
- {
- 'id': 1,
- 'email': 'joe.bloggs at example.com',
- 'name': 'Joe Bloggs',
- 'user': None,
- }
-
- Args:
- Person object to serialize.
-
- Returns:
- Serialized Person object.
- """
-
- # Make sure we don't return None even if the user submitted a patch
- # with no real name. XMLRPC can't marshall None.
- if obj.name is not None:
- name = obj.name
- else:
- name = obj.email
-
- return {
- 'id': obj.id,
- 'email': obj.email,
- 'name': name,
- 'user': six.text_type(obj.user).encode('utf-8'),
- }
-
-
-def patch_to_dict(obj):
- """Serialize a patch object.
-
- Return a trimmed down dictionary representation of a Patch
- object which is safe to send to the client. For example:
-
- {
- 'id': 1
- 'date': '2000-12-31 00:11:22',
- 'filename': 'Fix-all-the-bugs.patch',
- 'msgid': '<BLU438-SMTP36690BBDD2CE71A7138B082511A at phx.gbl>',
- 'name': "Fix all the bugs",
- 'project': 'my-project',
- 'project_id': 1,
- 'state': 'New',
- 'state_id': 1,
- 'archived': False,
- 'submitter': 'Joe Bloggs <joe.bloggs at example.com>',
- 'submitter_id': 1,
- 'delegate': 'admin',
- 'delegate_id': 1,
- 'commit_ref': '',
- 'hash': '',
- }
-
- Args:
- Patch object to serialize.
-
- Returns:
- Serialized Patch object.
- """
- return {
- 'id': obj.id,
- 'date': six.text_type(obj.date).encode('utf-8'),
- 'filename': obj.filename,
- 'msgid': obj.msgid,
- 'name': obj.name,
- 'project': six.text_type(obj.project).encode('utf-8'),
- 'project_id': obj.project_id,
- 'state': six.text_type(obj.state).encode('utf-8'),
- 'state_id': obj.state_id,
- 'archived': obj.archived,
- 'submitter': six.text_type(obj.submitter).encode('utf-8'),
- 'submitter_id': obj.submitter_id,
- 'delegate': six.text_type(obj.delegate).encode('utf-8'),
- 'delegate_id': obj.delegate_id or 0,
- 'commit_ref': obj.commit_ref or '',
- 'hash': obj.hash or '',
- }
-
-
-def state_to_dict(obj):
- """Serialize a state object.
-
- Return a trimmed down dictionary representation of a State
- object which is safe to send to the client. For example:
-
- {
- 'id': 1,
- 'name': 'New',
- }
-
- Args:
- State object to serialize.
-
- Returns:
- Serialized State object.
- """
- return {
- 'id': obj.id,
- 'name': obj.name,
- }
-
-
-def check_to_dict(obj):
- """Return a trimmed down dictionary representation of a Check
- object which is OK to send to the client."""
- return {
- 'id': obj.id,
- 'date': six.text_type(obj.date).encode('utf-8'),
- 'patch': six.text_type(obj.patch).encode('utf-8'),
- 'patch_id': obj.patch_id,
- 'user': six.text_type(obj.user).encode('utf-8'),
- 'user_id': obj.user_id,
- 'state': obj.get_state_display(),
- 'target_url': obj.target_url,
- 'description': obj.description,
- 'context': obj.context,
- }
-
-
-def patch_check_to_dict(obj):
- """Return a combined patch check."""
- return {
- 'state': obj.combined_check_state,
- 'total': len(obj.checks),
- 'checks': [check_to_dict(check) for check in obj.checks]
- }
-
-
-#######################################################################
-# Public XML-RPC methods
-#######################################################################
-
-def _get_objects(serializer, objects, max_count):
- if max_count > 0:
- return [serializer(x) for x in objects[:max_count]]
- elif max_count < 0:
- min_count = objects.count() + max_count
- return [serializer(x) for x in objects[min_count:]]
- else:
- return [serializer(x) for x in objects]
-
-
- at xmlrpc_method()
-def pw_rpc_version():
- """Return Patchwork XML-RPC interface version.
-
- The API is versioned separately from patchwork itself. The API
- version only changes when the API itself changes. As these changes
- can include the removal or modification of methods, it is highly
- recommended that one first test the API version for compatibility
- before making method calls.
-
- History:
-
- 1.0.0: Patchwork 1.0 release
- 1.1.0: ???
- 1.2.0: ???
- 1.3.0: Add support for negative indexing of Checks
-
- Returns:
- Version of the API.
- """
- return (1, 3, 0)
-
-
- at xmlrpc_method()
-def project_list(search_str=None, max_count=0):
- """List projects matching a given linkname filter.
-
- Filter projects by linkname. Projects are compared to the search
- string via a case-insensitive containment test, a.k.a. a partial
- match.
-
- Args:
- search_str: The string to compare project names against. If
- blank, all projects will be returned.
- max_count (int): The maximum number of projects to return.
-
- Returns:
- A serialized list of projects matching filter, if any. A list
- of all projects if no filter given.
- """
- if search_str:
- projects = Project.objects.filter(linkname__icontains=search_str)
- else:
- projects = Project.objects.all()
-
- return _get_objects(project_to_dict, projects, max_count)
-
-
- at xmlrpc_method()
-def project_get(project_id):
- """Get a project by its ID.
-
- Retrieve a project matching a given project ID, if any exists.
-
- Args:
- project_id (int): The ID of the project to retrieve.
-
- Returns:
- The serialized project matching the ID, if any, else an empty
- dict.
- """
- try:
- project = Project.objects.get(id=project_id)
- return project_to_dict(project)
- except Project.DoesNotExist:
- return {}
-
-
- at xmlrpc_method()
-def person_list(search_str=None, max_count=0):
- """List persons matching a given name or email filter.
-
- Filter persons by name and email. Persons are compared to the
- search string via a case-insensitive containment test, a.k.a. a
- partial match.
-
- Args:
- search_str: The string to compare person names or emails
- against. If blank, all persons will be returned.
- max_count (int): The maximum number of persons to return.
-
- Returns:
- A serialized list of persons matching filter, if any. A list
- of all persons if no filter given.
- """
- if search_str:
- people = (Person.objects.filter(name__icontains=search_str) |
- Person.objects.filter(email__icontains=search_str))
- else:
- people = Person.objects.all()
-
- return _get_objects(person_to_dict, people, max_count)
-
-
- at xmlrpc_method()
-def person_get(person_id):
- """Get a person by its ID.
-
- Retrieve a person matching a given person ID, if any exists.
-
- Args:
- person_id (int): The ID of the person to retrieve.
-
- Returns:
- The serialized person matching the ID, if any, else an empty
- dict.
- """
- try:
- person = Person.objects.get(id=person_id)
- return person_to_dict(person)
- except Person.DoesNotExist:
- return {}
-
-
- at xmlrpc_method()
-def patch_list(filt=None):
- """List patches matching all of a given set of filters.
-
- Filter patches by one or more of the below fields:
-
- * id
- * name
- * project_id
- * submitter_id
- * delegate_id
- * archived
- * state_id
- * date
- * commit_ref
- * hash
- * msgid
-
- It is also possible to specify the number of patches returned via
- a ``max_count`` filter.
-
- * max_count
-
- With the exception of ``max_count``, the specified field of the
- patches are compared to the search string using a provided
- field lookup type, which can be one of:
-
- * iexact
- * contains
- * icontains
- * gt
- * gte
- * lt
- * in
- * startswith
- * istartswith
- * endswith
- * iendswith
- * range
- * year
- * month
- * day
- * isnull
-
- Please refer to the Django documentation for more information on
- these field lookup types.
-
- An example filter would look like so:
-
- {
- 'name__icontains': 'Joe Bloggs',
- 'max_count': 1,
- }
-
- Args:
- filt (dict): The filters specifying the field to compare, the
- lookup type and the value to compare against. Keys are of
- format ``[FIELD_NAME]`` or ``[FIELD_NAME]__[LOOKUP_TYPE]``.
- Example: ``name__icontains``. Values are plain strings to
- compare against.
-
- Returns:
- A serialized list of patches matching filters, if any. A list
- of all patches if no filter given.
- """
- if filt is None:
- filt = {}
-
- # We allow access to many of the fields. But, some fields are
- # filtered by raw object so we must lookup by ID instead over
- # XML-RPC.
- ok_fields = [
- 'id',
- 'name',
- 'project_id',
- 'submitter_id',
- 'delegate_id',
- 'archived',
- 'state_id',
- 'date',
- 'commit_ref',
- 'hash',
- 'msgid',
- 'max_count',
- ]
-
- dfilter = {}
- max_count = 0
-
- for key in filt:
- parts = key.split('__')
- if parts[0] not in ok_fields:
- # Invalid field given
- return []
- if len(parts) > 1 and LOOKUP_TYPES.count(parts[1]) == 0:
- # Invalid lookup type given
- return []
-
- try:
- if parts[0] == 'project_id':
- dfilter['project'] = Project.objects.get(id=filt[key])
- elif parts[0] == 'submitter_id':
- dfilter['submitter'] = Person.objects.get(id=filt[key])
- elif parts[0] == 'delegate_id':
- dfilter['delegate'] = Person.objects.get(id=filt[key])
- elif parts[0] == 'state_id':
- dfilter['state'] = State.objects.get(id=filt[key])
- elif parts[0] == 'max_count':
- max_count = filt[key]
- else:
- dfilter[key] = filt[key]
- except (Project.DoesNotExist, Person.DoesNotExist, State.DoesNotExist):
- # Invalid Project, Person or State given
- return []
-
- patches = Patch.objects.filter(**dfilter)
-
- # Only extract the relevant fields. This saves a big db load as we
- # no longer fetch content/headers/etc for potentially every patch
- # in a project.
- patches = patches.defer('content', 'headers', 'diff')
-
- return _get_objects(patch_to_dict, patches, max_count)
-
-
- at xmlrpc_method()
-def patch_get(patch_id):
- """Get a patch by its ID.
-
- Retrieve a patch matching a given patch ID, if any exists.
-
- Args:
- patch_id (int): The ID of the patch to retrieve
-
- Returns:
- The serialized patch matching the ID, if any, else an empty
- dict.
- """
- try:
- patch = Patch.objects.get(id=patch_id)
- return patch_to_dict(patch)
- except Patch.DoesNotExist:
- return {}
-
-
- at xmlrpc_method()
-def patch_get_by_hash(hash): # noqa
- """Get a patch by its hash.
-
- Retrieve a patch matching a given patch hash, if any exists.
-
- Args:
- hash: The hash of the patch to retrieve
-
- Returns:
- The serialized patch matching the hash, if any, else an empty
- dict.
- """
- try:
- patch = Patch.objects.get(hash=hash)
- return patch_to_dict(patch)
- except Patch.DoesNotExist:
- return {}
-
-
- at xmlrpc_method()
-def patch_get_by_project_hash(project, hash):
- """Get a patch by its project and hash.
-
- Retrieve a patch matching a given project and patch hash, if any
- exists.
-
- Args:
- project (str): The project of the patch to retrieve.
- hash: The hash of the patch to retrieve.
-
- Returns:
- The serialized patch matching both the project and the hash,
- if any, else an empty dict.
- """
- try:
- patch = Patch.objects.get(project__linkname=project,
- hash=hash)
- return patch_to_dict(patch)
- except Patch.DoesNotExist:
- return {}
-
-
- at xmlrpc_method()
-def patch_get_mbox(patch_id):
- """Get a patch by its ID in mbox format.
-
- Retrieve a patch matching a given patch ID, if any exists, and
- return in mbox format.
-
- Args:
- patch_id (int): The ID of the patch to retrieve.
-
- Returns:
- The serialized patch matching the ID, if any, in mbox format,
- else an empty string.
- """
- try:
- patch = Patch.objects.get(id=patch_id)
- return patch_to_mbox(patch)
- except Patch.DoesNotExist:
- return ''
-
-
- at xmlrpc_method()
-def patch_get_diff(patch_id):
- """Get a patch by its ID in diff format.
-
- Retrieve a patch matching a given patch ID, if any exists, and
- return in diff format.
-
- Args:
- patch_id (int): The ID of the patch to retrieve.
-
- Returns:
- The serialized patch matching the ID, if any, in diff format,
- else an empty string.
- """
- try:
- patch = Patch.objects.get(id=patch_id)
- return patch.diff
- except Patch.DoesNotExist:
- return ''
-
-
- at xmlrpc_method(login_required=True)
-def patch_set(user, patch_id, params):
- """Set fields of a patch.
-
- Modify a patch matching a given patch ID, if any exists, and using
- the provided ``key,value`` pairs. Only the following parameters may
- be set:
-
- * state
- * commit_ref
- * archived
-
- Any other field will be rejected.
-
- **NOTE:** Authentication is required for this method.
-
- Args:
- user (User): The user making the request. This will be
- populated from HTTP Basic Auth.
- patch_id (int): The ID of the patch to modify.
- params (dict): A dictionary of keys corresponding to patch
- object fields and the values that said fields should be
- set to.
-
- Returns:
- True, if successful else raise exception.
-
- Raises:
- Exception: User did not have necessary permissions to edit this
- patch
- Patch.DoesNotExist: The patch did not exist.
- """
- ok_params = ['state', 'commit_ref', 'archived']
-
- patch = Patch.objects.get(id=patch_id)
-
- if not patch.is_editable(user):
- raise Exception('No permissions to edit this patch')
-
- for (k, v) in params.items():
- if k not in ok_params:
- continue
-
- if k == 'state':
- patch.state = State.objects.get(id=v)
-
- else:
- setattr(patch, k, v)
-
- patch.save()
-
- return True
-
-
- at xmlrpc_method()
-def state_list(search_str=None, max_count=0):
- """List states matching a given name filter.
-
- Filter states by name. States are compared to the search string
- via a case-insensitive containment test, a.k.a. a partial match.
-
- Args:
- search_str: The string to compare state names against. If
- blank, all states will be returned.
- max_count (int): The maximum number of states to return.
-
- Returns:
- A serialized list of states matching filter, if any. A list
- of all states if no filter given.
- """
- if search_str:
- states = State.objects.filter(name__icontains=search_str)
- else:
- states = State.objects.all()
-
- return _get_objects(state_to_dict, states, max_count)
-
-
- at xmlrpc_method()
-def state_get(state_id):
- """Get a state by its ID.
-
- Retrieve a state matching a given state ID, if any exists.
-
- Args:
- state_id: The ID of the state to retrieve.
-
- Returns:
- The serialized state matching the ID, if any, else an empty
- dict.
- """
- try:
- state = State.objects.get(id=state_id)
- return state_to_dict(state)
- except State.DoesNotExist:
- return {}
-
-
- at xmlrpc_method()
-def check_list(filt=None):
- """List checks matching all of a given set of filters.
-
- Filter checks by one or more of the below fields:
-
- * id
- * user
- * project_id
- * patch_id
-
- It is also possible to specify the number of patches returned via
- a ``max_count`` filter.
-
- * max_count
-
- With the exception of ``max_count``, the specified field of the
- patches are compared to the search string using a provided
- field lookup type, which can be one of:
-
- * iexact
- * contains
- * icontains
- * gt
- * gte
- * lt
- * in
- * startswith
- * istartswith
- * endswith
- * iendswith
- * range
- * year
- * month
- * day
- * isnull
-
- Please refer to the Django documentation for more information on
- these field lookup types.
-
- An example filter would look like so:
-
- {
- 'user__icontains': 'Joe Bloggs',
- 'max_count': 1,
- }
-
- Args:
- filt (dict): The filters specifying the field to compare, the
- lookup type and the value to compare against. Keys are of
- format ``[FIELD_NAME]`` or ``[FIELD_NAME]__[LOOKUP_TYPE]``.
- Example: ``name__icontains``. Values are plain strings to
- compare against.
-
- Returns:
- A serialized list of Checks matching filters, if any. A list
- of all Checks if no filter given.
- """
- if filt is None:
- filt = {}
-
- # We allow access to many of the fields. But, some fields are
- # filtered by raw object so we must lookup by ID instead over
- # XML-RPC.
- ok_fields = [
- 'id',
- 'user',
- 'project_id',
- 'patch_id',
- 'max_count',
- ]
-
- dfilter = {}
- max_count = 0
-
- for key in filt:
- parts = key.split('__')
- if parts[0] not in ok_fields:
- # Invalid field given
- return []
- if len(parts) > 1:
- if LOOKUP_TYPES.count(parts[1]) == 0:
- # Invalid lookup type given
- return []
-
- if parts[0] == 'user_id':
- dfilter['user'] = Person.objects.filter(id=filt[key])[0]
- if parts[0] == 'project_id':
- dfilter['patch__project'] = Project.objects.filter(
- id=filt[key])[0]
- elif parts[0] == 'patch_id':
- dfilter['patch'] = Patch.objects.filter(id=filt[key])[0]
- elif parts[0] == 'max_count':
- max_count = filt[key]
- else:
- dfilter[key] = filt[key]
-
- checks = Check.objects.filter(**dfilter)
-
- return _get_objects(check_to_dict, checks, max_count)
-
-
- at xmlrpc_method()
-def check_get(check_id):
- """Get a check by its ID.
-
- Retrieve a check matching a given check ID, if any exists.
-
- Args:
- check_id (int): The ID of the check to retrieve
-
- Returns:
- The serialized check matching the ID, if any, else an empty
- dict.
- """
- try:
- check = Check.objects.get(id=check_id)
- return check_to_dict(check)
- except Check.DoesNotExist:
- return {}
-
-
- at xmlrpc_method(login_required=True)
-def check_create(user, patch_id, context, state, target_url="",
- description=""):
- """Add a Check to a patch.
-
- **NOTE:** Authentication is required for this method.
-
- Args:
- patch_id (id): The ID of the patch to create the check against.
- context: Type of test or system that generated this check.
- state: "pending", "success", "warning", or "fail"
- target_url: Link to artifact(s) relating to this check.
- description: A brief description of the check.
-
- Returns:
- True, if successful else raise exception.
- """
- patch = Patch.objects.get(id=patch_id)
- if not patch.is_editable(user):
- raise Exception('No permissions to edit this patch')
- for state_val, state_str in Check.STATE_CHOICES:
- if state == state_str:
- state = state_val
- break
- else:
- raise Exception("Invalid check state: %s" % state)
- Check.objects.create(patch=patch, context=context, state=state, user=user,
- target_url=target_url, description=description)
- return True
-
-
- at xmlrpc_method()
-def patch_check_get(patch_id):
- """Get a patch's combined checks by its ID.
-
- Retrieve a patch's combined checks for the patch matching a given
- patch ID, if any exists.
-
- Args:
- patch_id (int): The ID of the patch to retrieve checks for
-
- Returns:
- The serialized combined patch checks matching the ID, if any,
- else an empty dict.
- """
- try:
- patch = Patch.objects.get(id=patch_id)
- return patch_check_to_dict(patch)
- except Patch.DoesNotExist:
- return {}
diff --git releasenotes/notes/remove-xmlrpc-b6d26084338efcb4.yaml releasenotes/notes/remove-xmlrpc-b6d26084338efcb4.yaml
new file mode 100644
index 000000000000..e86c156844d8
--- /dev/null
+++ releasenotes/notes/remove-xmlrpc-b6d26084338efcb4.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - |
+ To simplify the code base, the XML-RPC API has been removed. This
+ means that `pwclient` will no longer work.
+
+ As a result, `tools/patchwork-update-commits` and
+ `tools/post-receive.hook` have also been removed.
+
+ Users are encouraged to try
+ `git-pw <https://github.com/getpatchwork/git-pw>`_.
diff --git tools/patchwork-update-commits tools/patchwork-update-commits
deleted file mode 100755
index 269dac9eee4e..000000000000
--- tools/patchwork-update-commits
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 Jeremy Kerr <jk at ozlabs.org>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-TOOLS_DIR="$(dirname "$0")"
-PW_DIR="${TOOLS_DIR}/../patchwork"
-
-if [ "$#" -lt 1 ]; then
- echo "usage: $0 <revspec>" >&2
- exit 1
-fi
-
-git rev-list --reverse "$@" |
-while read -r commit; do
- hash=$(git diff "$commit~..$commit" | python "$PW_DIR/hasher.py")
- pwclient update -s Accepted -c "$commit" -h "$hash"
-done
diff --git tools/post-receive.hook tools/post-receive.hook
deleted file mode 100755
index 9f2f0503d7ee..000000000000
--- tools/post-receive.hook
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/bin/bash
-
-# Patchwork - automated patch tracking system
-# Copyright (C) 2010 martin f. krafft <madduck at madduck.net>
-#
-# SPDX-License-Identifier: GPL-2.0-or-later
-
-# Git post-receive hook to update Patchwork patches after Git pushes
-set -eu
-
-PW_DIR=/opt/patchwork/patchwork
-
-#TODO: the state map should really live in the repo's git-config
-STATE_MAP="refs/heads/master:Accepted"
-
-# ignore all commits already present in these refs
-# e.g.,
-# EXCLUDE="refs/heads/upstream refs/heads/other-project"
-EXCLUDE=""
-
-do_exit=0
-trap "do_exit=1" INT
-
-get_patchwork_hash() {
- local hash
- hash=$(git diff "$1~..$1" | python $PW_DIR/hasher.py)
- echo "$hash"
- test -n "$hash"
-}
-
-get_patch_id() {
- local id
- id=$(pwclient info -h "$1" 2>/dev/null | \
- sed -rne 's,- id[[:space:]]*: ,,p')
- echo "$id"
- test -n "$id"
-}
-
-set_patch_state() {
- pwclient update -s "$2" -c "$3" "$1" 2>&1
-}
-
-update_patches() {
- local cnt; cnt=0
- for rev in $(git rev-parse --not ${EXCLUDE} |
- git rev-list --stdin --no-merges --reverse "${1}".."${2}"); do
- if [ "$do_exit" = 1 ]; then
- echo "I: exiting..." >&2
- break
- fi
- hash=$(get_patchwork_hash "$rev")
- if [ -z "$hash" ]; then
- echo "E: failed to hash rev $rev." >&2
- continue
- fi
- id=$(get_patch_id "$hash" || true)
- if [ -z "$id" ]; then
- echo "E: failed to find patch for rev $rev." >&2
- continue
- fi
- reason="$(set_patch_state "$id" "$3" "$rev")"
- if [ -n "$reason" ]; then
- echo "E: failed to update patch #$id${reason:+: $reason}." >&2
- continue
- fi
- echo "I: patch #$id updated using rev $rev." >&2
- cnt=$((cnt + 1))
- done
-
- echo "I: $cnt patch(es) updated to state $3." >&2
-}
-
-while read -r oldrev newrev refname; do
- found=0
- for i in $STATE_MAP; do
- key="${i%:*}"
- if [ "$key" = "$refname" ]; then
- update_patches "$oldrev" "$newrev" ${i#*:}
- found=1
- break
- fi
- done
- if [ $found -eq 0 ]; then
- echo "E: STATE_MAP has no mapping for branch $refname" >&2
- fi
-done
--
2.20.1
More information about the Patchwork
mailing list