From 79805d235e06e7bf4802cba91e9d1667518be3ca Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Mon, 26 Mar 2018 14:42:22 -0300 Subject: [PATCH 01/34] Changed version number --- package.json | 2 +- readme.md | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f75da43..00a5570 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "python-kong-client", - "version": "0.1.5", + "version": "0.1.6", "description": "## Setup", "main": "index.js", "scripts": { diff --git a/readme.md b/readme.md index 1792f50..1579912 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ ## Description This is a small library to provide [kong](http://getkong.org/) server administration functionality inside your python application -This library is currently in version 0.1.5 and it was built around [kong 0.12.x specifications](https://getkong.org/docs/0.12.x/admin-api/) +This library is currently in version 0.1.6 and it was built around [kong 0.13.x specifications](https://getkong.org/docs/0.13.x/admin-api/) ## Features Supported operations for Apis, Consumers and Plugins diff --git a/setup.py b/setup.py index 9f2bcdf..c2debc5 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ from setuptools import setup -__version__ = '0.1.5' +__version__ = '0.1.6' BASE_DIR = os.path.dirname(os.path.abspath(__file__)) From f1c54e591753c5ea8c6709ef18694f95f7190a99 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 27 Mar 2018 10:52:02 -0300 Subject: [PATCH 02/34] Added tests for services.create refs:#25 --- tests/service_admin_tests.py | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/service_admin_tests.py diff --git a/tests/service_admin_tests.py b/tests/service_admin_tests.py new file mode 100644 index 0000000..81c5fdd --- /dev/null +++ b/tests/service_admin_tests.py @@ -0,0 +1,100 @@ +from abc import abstractmethod +import requests + +import unittest +from unittest.mock import MagicMock + + +from kong.kong_clients import ServiceAdminClient + + +class ServiceAdminClientAbstractTest: + + @abstractmethod + def kong_url(self): + pass + + @abstractmethod + def new_service_admin_client(self): + pass + + def setUp(self): + self.service_protocol = 'http' + self.service_host = 'example.org' + self.service_port = 8080 + self.service_path = '/api' + self.service_name = 'example-service' + self.service_url = '%s://%s:%s%s' % (self.service_protocol, + self.service_host, + self.service_port, + self.service_path) + self.service_retries = '5' + + self.service_dict = { + "id": "4e13f54a-bbf1-47a8-8777-255fed7116f2", + "created_at": 1488869076800, + "updated_at": 1488869076800, + "connect_timeout": 60000, + "protocol": self.service_protocol, + "host": self.service_host, + "port": self.service_port, + "path": self.service_path, + "name": self.service_name, + "retries": 5, + "read_timeout": 60000, + "write_timeout": 60000 + } + + self.session = MagicMock() + self.session.post.return_value.status_code = 201 + self.session.post.return_value.json.return_value = self.service_dict + + self.service_admin_client = self.new_service_admin_client() + + def create_a_service_w_url(self): + return self.service_admin_client.create(name=self.service_name, + url=self.service_url) + + def test_create_a_service_w_url(self): + created = self.create_a_service_w_url() + + self.assertRegex(created['id'], '^[\w\d]{8}-([\w\d]{4}-){3}[\w\d]{12}$') + self.assertTrue(isinstance(created['created_at'], int)) + self.assertTrue(isinstance(created['updated_at'], int)) + self.assertTrue(isinstance(created['connect_timeout'], int)) + self.assertRegex(created['protocol'], self.service_protocol) + self.assertRegex(created['host'], self.service_host) + self.assertTrue(isinstance(created['port'], int)) + self.assertRegex(created['path'], self.service_path) + self.assertRegex(created['name'], self.service_name) + self.assertTrue(isinstance(created['retries'], int)) + + +class ServiceAdminClientMockedTest(ServiceAdminClientAbstractTest, unittest.TestCase): + + @property + def kong_url(self): + return 'http://kong.url/' + + def new_service_admin_client(self): + return ServiceAdminClient(self.kong_url, _session=self.session) + + def test_create_a_service_w_url(self): + super(ServiceAdminClientMockedTest, self).test_create_a_service_w_url() + + expected_data = {'url': self.service_url, 'name': self.service_name} + endpoint = self.kong_url + 'services/' + self.session.post.assert_called_once_with(endpoint, data=expected_data) + + +class ServiceAdminClientServerTest(ServiceAdminClientAbstractTest, unittest.TestCase): + + @property + def kong_url(self): + return 'http://localhost:8001/' + + def new_service_admin_client(self): + return ServiceAdminClient(self.kong_url, _session=requests.session()) + + def tearDown(self): + self.service_admin_client.delete(self.service_name) From 822232ed81da50d62db5e12f535e531100d965dc Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 27 Mar 2018 10:52:52 -0300 Subject: [PATCH 03/34] Declared ServiceAdminClient class refs:#25 --- kong/kong_clients.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 9f243ea..9c7d924 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -159,7 +159,7 @@ def _validate_update_params(self, params): return self._validate_params(params, self._allowed_update_params) def create(self, name, **kwargs): - return self._send_create({**kwargs, **{'name', name}}) + return self._send_create(dict(**kwargs, name=name)) def retrieve(self, pk_or_id): if not isinstance(pk_or_id, str): @@ -359,3 +359,10 @@ def list(self, size=10, **kwargs): def update(self, pk_or_id, **kwargs): response = super(ApiAdminClient, self).update(pk_or_id, **kwargs) return self.__api_data_from_response(response) + + +class ServiceAdminClient(KongAbstractClient): + + @property + def path(self): + return 'services/' From 99b040d85f885e17bcc9460896fee6f02fb9e19d Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 27 Mar 2018 10:54:41 -0300 Subject: [PATCH 04/34] Added ServiceAdminClient to KongAdminClient.services refs:#25 --- kong/kong_clients.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 9c7d924..6ddbb4b 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -41,6 +41,7 @@ def __init__(self, *args, **kwargs): self.apis = ApiAdminClient(self.url, self.session) self.consumers = ConsumerAdminClient(self.url, self.session) self.plugins = PluginAdminClient(self.url, self.session) + self.services = ServiceAdminClient(self.url, self.session) def node_status(self): return self.session.get(self.url + 'status/').json() From f3d03ce4c2d7313a58353a904fa697a1e1ce3aee Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 27 Mar 2018 12:33:07 -0300 Subject: [PATCH 05/34] Fix inconsistent test refs:#25 --- tests/api_admin_tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/api_admin_tests.py b/tests/api_admin_tests.py index c70d89e..331fe1b 100644 --- a/tests/api_admin_tests.py +++ b/tests/api_admin_tests.py @@ -156,8 +156,6 @@ def test_api_admin_update(self): expected_data = {} for k, v in api_data.items(): value = self.api_admin_client._stringify_if_list(v) - if isinstance(value, (str, list)) and not value: - continue expected_data[k] = value api_endpoint = self.apis_endpoint + self.api_name self.session_mock.patch.assert_called_once_with(api_endpoint, data=expected_data) From 3e9201cfd5557aa05e577813a1f63739a7554af7 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 27 Mar 2018 15:35:17 -0300 Subject: [PATCH 06/34] Refactor: Restructured test classes, added create w invalid params refs:#25 --- tests/service_admin_tests.py | 97 +++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 28 deletions(-) diff --git a/tests/service_admin_tests.py b/tests/service_admin_tests.py index 81c5fdd..966b05a 100644 --- a/tests/service_admin_tests.py +++ b/tests/service_admin_tests.py @@ -6,6 +6,7 @@ from kong.kong_clients import ServiceAdminClient +from kong.exceptions import SchemaViolation class ServiceAdminClientAbstractTest: @@ -18,7 +19,10 @@ def kong_url(self): def new_service_admin_client(self): pass - def setUp(self): + def setup_client_var(self): + self.service_admin_client = self.new_service_admin_client() + + def setup_service_vars(self): self.service_protocol = 'http' self.service_host = 'example.org' self.service_port = 8080 @@ -30,34 +34,14 @@ def setUp(self): self.service_path) self.service_retries = '5' - self.service_dict = { - "id": "4e13f54a-bbf1-47a8-8777-255fed7116f2", - "created_at": 1488869076800, - "updated_at": 1488869076800, - "connect_timeout": 60000, - "protocol": self.service_protocol, - "host": self.service_host, - "port": self.service_port, - "path": self.service_path, - "name": self.service_name, - "retries": 5, - "read_timeout": 60000, - "write_timeout": 60000 - } - - self.session = MagicMock() - self.session.post.return_value.status_code = 201 - self.session.post.return_value.json.return_value = self.service_dict - - self.service_admin_client = self.new_service_admin_client() - - def create_a_service_w_url(self): - return self.service_admin_client.create(name=self.service_name, - url=self.service_url) - def test_create_a_service_w_url(self): - created = self.create_a_service_w_url() + created = self.service_admin_client.create(name=self.service_name, + url=self.service_url) + self.assert_correctly_created(created) + + def assert_correctly_created(self, created): + self.assertEquals(12, len(created)) self.assertRegex(created['id'], '^[\w\d]{8}-([\w\d]{4}-){3}[\w\d]{12}$') self.assertTrue(isinstance(created['created_at'], int)) self.assertTrue(isinstance(created['updated_at'], int)) @@ -68,6 +52,23 @@ def test_create_a_service_w_url(self): self.assertRegex(created['path'], self.service_path) self.assertRegex(created['name'], self.service_name) self.assertTrue(isinstance(created['retries'], int)) + self.assertTrue(isinstance(created['read_timeout'], int)) + self.assertTrue(isinstance(created['write_timeout'], int)) + + def test_create_service(self): + created = self.service_admin_client.create(name=self.service_name, + protocol=self.service_protocol, + host=self.service_host, + port=self.service_port, + path=self.service_path) + self.assert_correctly_created(created) + + def test_create_w_invalid_params(self): + self.assertRaises(SchemaViolation, lambda: + self.service_admin_client.create(name=self.service_name, + url=self.service_url, + invalid='invalid') + ) class ServiceAdminClientMockedTest(ServiceAdminClientAbstractTest, unittest.TestCase): @@ -76,13 +77,49 @@ class ServiceAdminClientMockedTest(ServiceAdminClientAbstractTest, unittest.Test def kong_url(self): return 'http://kong.url/' + def setup_mock_vars(self): + self.service_dict = { + "id": "4e13f54a-bbf1-47a8-8777-255fed7116f2", + "created_at": 1488869076800, + "updated_at": 1488869076800, + "connect_timeout": 60000, + "protocol": self.service_protocol, + "host": self.service_host, + "port": self.service_port, + "path": self.service_path, + "name": self.service_name, + "retries": 5, + "read_timeout": 60000, + "write_timeout": 60000 + } + + self.session = MagicMock() + self.session.post.return_value.status_code = 201 + self.session.post.return_value.json.return_value = self.service_dict + def new_service_admin_client(self): return ServiceAdminClient(self.kong_url, _session=self.session) + def setUp(self): + self.setup_service_vars() + self.setup_mock_vars() + self.setup_client_var() + def test_create_a_service_w_url(self): super(ServiceAdminClientMockedTest, self).test_create_a_service_w_url() - expected_data = {'url': self.service_url, 'name': self.service_name} + expected_data = dict(name=self.service_name, url=self.service_url) + endpoint = self.kong_url + 'services/' + self.session.post.assert_called_once_with(endpoint, data=expected_data) + + def test_create_service(self): + super(ServiceAdminClientMockedTest, self).test_create_service() + + expected_data = dict(name=self.service_name, + protocol=self.service_protocol, + host=self.service_host, + port=self.service_port, + path=self.service_path) endpoint = self.kong_url + 'services/' self.session.post.assert_called_once_with(endpoint, data=expected_data) @@ -93,6 +130,10 @@ class ServiceAdminClientServerTest(ServiceAdminClientAbstractTest, unittest.Test def kong_url(self): return 'http://localhost:8001/' + def setUp(self): + self.setup_service_vars() + self.setup_client_var() + def new_service_admin_client(self): return ServiceAdminClient(self.kong_url, _session=requests.session()) From 14e089372d8da35718983f6da1ff734318c0eb81 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 27 Mar 2018 15:35:49 -0300 Subject: [PATCH 07/34] Added params validation when creating a service refs:#25 --- kong/exceptions.py | 2 ++ kong/kong_clients.py | 7 ++++++- kong/structures.py | 20 ++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 kong/exceptions.py diff --git a/kong/exceptions.py b/kong/exceptions.py new file mode 100644 index 0000000..b1ce99b --- /dev/null +++ b/kong/exceptions.py @@ -0,0 +1,2 @@ +class SchemaViolation(Exception): + pass diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 6ddbb4b..dd523d4 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -1,7 +1,7 @@ from abc import abstractmethod from urllib3.util.url import Url, parse_url from requests import session -from kong.structures import ApiData +from kong.structures import ApiData, ConsumerData class RestClient: @@ -367,3 +367,8 @@ class ServiceAdminClient(KongAbstractClient): @property def path(self): return 'services/' + + def create(self, name, **kwargs): + consumer = ConsumerData(name=name, **kwargs) + + return self._send_create(consumer) diff --git a/kong/structures.py b/kong/structures.py index 417eeed..095a1a1 100644 --- a/kong/structures.py +++ b/kong/structures.py @@ -1,4 +1,5 @@ import re +from kong.exceptions import SchemaViolation class ApiData(dict): @@ -55,3 +56,22 @@ def __normalize_uri(uri): string=uri) is None: raise ValueError("invalid uri: %s" % normalized) return normalized + + +class ConsumerData(dict): + + def __init__(self, *args, **kwargs): + super(ConsumerData, self).__init__(*args, **kwargs) + + self.validate() + + def validate(self): + for key, value in self.items(): + if key not in self.allowed_params: + raise SchemaViolation('invalid parameter: %s' % key) + + @property + def allowed_params(self): + return 'name', 'protocol', 'host', 'port', 'path',\ + 'retries', 'connect_timeout', 'send_timeout',\ + 'read_timeout', 'url' From bf9101dc2971faf0b8d8fffb424dbeb7479b9480 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 28 Mar 2018 12:36:18 -0300 Subject: [PATCH 08/34] Added --runslow option to tests refs:#25 --- tests/conftest.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..1a695c6 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,16 @@ +import pytest + + +def pytest_addoption(parser): + parser.addoption("--runslow", action="store_true", + default=False, help="run slow tests") + + +def pytest_collection_modifyitems(config, items): + if config.getoption("--runslow"): + # --runslow given in cli: do not skip slow tests + return + skip_slow = pytest.mark.skip(reason="need --runslow option to run") + for item in items: + if "slow" in item.keywords: + item.add_marker(skip_slow) From 5b0d2cfcd9ad0a57a909ca8239d9a3b70f80735b Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 28 Mar 2018 12:36:38 -0300 Subject: [PATCH 09/34] Marked server hitting tests as slow refs:#25 --- tests/service_admin_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/service_admin_tests.py b/tests/service_admin_tests.py index 966b05a..d89ba11 100644 --- a/tests/service_admin_tests.py +++ b/tests/service_admin_tests.py @@ -4,6 +4,7 @@ import unittest from unittest.mock import MagicMock +import pytest from kong.kong_clients import ServiceAdminClient from kong.exceptions import SchemaViolation @@ -124,6 +125,7 @@ def test_create_service(self): self.session.post.assert_called_once_with(endpoint, data=expected_data) +@pytest.mark.slow class ServiceAdminClientServerTest(ServiceAdminClientAbstractTest, unittest.TestCase): @property From e386370cb60e239b65e0eb814b69cda4983776ce Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 28 Mar 2018 14:20:55 -0300 Subject: [PATCH 10/34] Refactor: created abstract class ObjectData refs:#25 refs:#25 refs:#25 refs:#25 --- kong/kong_clients.py | 18 +++----- kong/structures.py | 91 ++++++++++++++++++++++++---------------- tests/api_admin_tests.py | 24 +++++------ tests/api_data_tests.py | 11 ++--- 4 files changed, 77 insertions(+), 67 deletions(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index dd523d4..c04798e 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -1,7 +1,7 @@ from abc import abstractmethod from urllib3.util.url import Url, parse_url from requests import session -from kong.structures import ApiData, ConsumerData +from kong.structures import ApiData, ServiceData class RestClient: @@ -325,11 +325,7 @@ def path(self): @staticmethod def __api_data_from_response(data): - validated_data = {} - for k, val in data.items(): - if k in ApiData.allowed_parameters(): - validated_data[k] = val - return ApiData(**validated_data) + return ApiData(**data) # pylint: disable=arguments-differ def create(self, api_name_or_data, upstream_url=None, **kwargs): @@ -346,17 +342,13 @@ def create(self, api_name_or_data, upstream_url=None, **kwargs): else: raise ValueError("must provide ApiData instance or name to create a api") - data = self._send_create(api_data) + data = self._send_create(api_data.as_dict()) return self.__api_data_from_response(data) def retrieve(self, pk_or_id): response = super(ApiAdminClient, self).retrieve(pk_or_id) return self.__api_data_from_response(response) - def list(self, size=10, **kwargs): - return map(self.__api_data_from_response, - super(ApiAdminClient, self).list(size, **kwargs)) - def update(self, pk_or_id, **kwargs): response = super(ApiAdminClient, self).update(pk_or_id, **kwargs) return self.__api_data_from_response(response) @@ -369,6 +361,6 @@ def path(self): return 'services/' def create(self, name, **kwargs): - consumer = ConsumerData(name=name, **kwargs) + service = ServiceData(name=name, **kwargs) - return self._send_create(consumer) + return self._send_create(service.as_dict()) diff --git a/kong/structures.py b/kong/structures.py index 095a1a1..e902913 100644 --- a/kong/structures.py +++ b/kong/structures.py @@ -1,11 +1,54 @@ +from abc import abstractmethod + import re +from urllib3.util import parse_url + from kong.exceptions import SchemaViolation -class ApiData(dict): +class ObjectData: - @staticmethod - def allowed_parameters(): + def __init__(self, name, **kwargs): + + self.validate_schema(**kwargs) + + self.validate_parameter('name', name) + self.name = name + + for k, val in kwargs.items(): + self.validate_parameter(k, val) + self.__setattr__(k, val) + + @abstractmethod + def validate_schema(self, **kwargs): + pass + + @property + @abstractmethod + def allowed_parameters(self): + pass + + def validate_parameter(self, parameter, value): + if parameter not in self.allowed_parameters: + raise SchemaViolation('invalid parameter: %s' % parameter) + + if not isinstance(value, (str, int, bool, list, dict)): + raise ValueError('invalid value: %s value must be str, int, bool, list or dict' % parameter) + + def as_dict(self): + return self.__dict__.copy() + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + return not self.__eq__(other) + + +class ApiData(ObjectData): + + @property + def allowed_parameters(self): return 'id', 'name', 'upstream_url',\ 'hosts', 'uris', 'methods', 'strip_uri',\ 'preserve_host', 'retries', 'https_only',\ @@ -21,33 +64,17 @@ def satisfy_semi_optional_parameters(**kwargs): # pylint: disable=invalid-name @staticmethod def satisfy_obligatory_parameters(**kwargs): - return 'name' in kwargs \ - and 'upstream_url' in kwargs - - def __init__(self, *args, **kwargs): - super(ApiData, self).__init__(*args, **kwargs) + return 'upstream_url' in kwargs + def validate_schema(self, **kwargs): if not self.satisfy_obligatory_parameters(**kwargs): - raise ValueError('name and upstream_url must be provided to create') - + raise SchemaViolation('name and upstream_url must be provided to create') if not self.satisfy_semi_optional_parameters(**kwargs): - raise ValueError('uris, methods or hosts must be provided to create') - - for k, val in kwargs.items(): - self.validate(k, val) - - self[k] = val - - def validate(self, parameter, value): - if parameter not in self.allowed_parameters(): - raise KeyError('invalid parameter: %s' % parameter) - - if not isinstance(value, (str, int, bool, list, dict)): - raise ValueError('invalid value: %s value must be str, int, bool, or list' % parameter) + raise SchemaViolation('uris, methods or hosts must be provided to create') def add_uri(self, uri): - self['uris'].append(self.__normalize_uri(uri)) - return ", ".join(self['uris']) + self.uris.append(self.__normalize_uri(uri)) + return ", ".join(self.uris) @staticmethod def __normalize_uri(uri): @@ -58,20 +85,10 @@ def __normalize_uri(uri): return normalized -class ConsumerData(dict): - - def __init__(self, *args, **kwargs): - super(ConsumerData, self).__init__(*args, **kwargs) - - self.validate() - - def validate(self): - for key, value in self.items(): - if key not in self.allowed_params: - raise SchemaViolation('invalid parameter: %s' % key) +class ServiceData(ObjectData): @property - def allowed_params(self): + def allowed_parameters(self): return 'name', 'protocol', 'host', 'port', 'path',\ 'retries', 'connect_timeout', 'send_timeout',\ 'read_timeout', 'url' diff --git a/tests/api_admin_tests.py b/tests/api_admin_tests.py index 331fe1b..158dc67 100644 --- a/tests/api_admin_tests.py +++ b/tests/api_admin_tests.py @@ -47,7 +47,7 @@ def setUp(self): self.session_mock = MagicMock() self.requests_mock.session.return_value = self.session_mock - data_w_id = {**self.api_data, **{'id': self.api_kong_id}} + data_w_id = {**self.api_data.as_dict(), **{'id': self.api_kong_id}} self.session_mock.post.return_value.json.return_value = data_w_id self.session_mock.put.return_value.json.return_value = data_w_id @@ -76,9 +76,9 @@ def test_api_admin_create(self): uris=self.api_uris) # Verify - self.assertEqual(api_data['name'], self.api_name) - self.assertEqual(api_data['upstream_url'], self.api_upstream_url) - self.assertEqual(api_data['uris'], self.api_uris) + self.assertEqual(api_data.name, self.api_name) + self.assertEqual(api_data.upstream_url, self.api_upstream_url) + self.assertEqual(api_data.uris, self.api_uris) def test_api_admin_create_triggers_http_request_to_kong_server(self): """ @@ -93,7 +93,7 @@ def test_api_admin_create_triggers_http_request_to_kong_server(self): upstream_url=self.api_upstream_url, uris=self.api_uris) self.session_mock.post.assert_called_once_with(self.apis_endpoint, - data=expected_api_data) + data=expected_api_data.as_dict()) def test_api_admin_create_using_api_data(self): """ @@ -109,7 +109,7 @@ def test_api_admin_create_using_api_data(self): self.api_admin_client.create(orig_data) # Verify - self.session_mock.post.assert_called_once_with(self.apis_endpoint, data=orig_data) + self.session_mock.post.assert_called_once_with(self.apis_endpoint, data=orig_data.as_dict()) def test_api_admin_delete_by_name(self): """ @@ -148,13 +148,13 @@ def test_api_admin_update(self): # Exercise api_data.add_uri(new_uri) - response = self.api_admin_client.update(api_data['name'], **api_data) + response = self.api_admin_client.update(api_data.name, **api_data.as_dict()) # Verify self.assertTrue(isinstance(response, ApiData)) self.assertEqual(response, api_data) expected_data = {} - for k, v in api_data.items(): + for k, v in api_data.as_dict().items(): value = self.api_admin_client._stringify_if_list(v) expected_data[k] = value api_endpoint = self.apis_endpoint + self.api_name @@ -285,7 +285,7 @@ def test_update_w_invalid_parameters(self): # Verify self.assertRaisesRegex(KeyError, r"unknown field", lambda: self.api_admin_client - .update(self.api_data['name'], **self.api_data)) + .update(self.api_data.name, **self.api_data.as_dict())) def test_update_not_existing_api(self): # Setup @@ -295,7 +295,7 @@ def test_update_not_existing_api(self): # Verify self.assertRaisesRegex(NameError, r"not found", lambda: self.api_admin_client - .update(self.api_data['name'], **self.api_data)) + .update(self.api_data.name, **self.api_data.as_dict())) def test_update_internal_server_error(self): # Setup @@ -305,7 +305,7 @@ def test_update_internal_server_error(self): # Verify self.assertRaisesRegex(Exception, r'internal server error', lambda: self.api_admin_client - .update(self.api_data['name'], **self.api_data)) + .update(self.api_data.name, **self.api_data.as_dict())) def test_list_internal_server_error(self): # Setup @@ -325,7 +325,7 @@ def boom(): def test_retrieve_api(self): # Setup self.session_mock.get.return_value.status_code = 200 - self.session_mock.get.return_value.json.return_value = dict(self.api_data) + self.session_mock.get.return_value.json.return_value = self.api_data.as_dict() # Exercise retrieved = self.api_admin_client.retrieve(self.api_name) diff --git a/tests/api_data_tests.py b/tests/api_data_tests.py index 6ea2173..8ad9987 100644 --- a/tests/api_data_tests.py +++ b/tests/api_data_tests.py @@ -2,6 +2,7 @@ import faker from kong.providers import ApiDataProvider from kong.structures import ApiData +from kong.exceptions import SchemaViolation class ApiDataTests(unittest.TestCase): @@ -43,12 +44,12 @@ def test_create(self): upstream_read_timeout=upstream_read_timeout) # Verify - self.assertEqual(self.api_data['name'], self.api_name) - self.assertEqual(self.api_data['upstream_url'], self.api_upstream_url) - self.assertEqual(self.api_data['uris'], self.api_uris) + self.assertEqual(self.api_data.name, self.api_name) + self.assertEqual(self.api_data.upstream_url, self.api_upstream_url) + self.assertEqual(self.api_data.uris, self.api_uris) def test_create_api_data_wo_uris_method_or_hosts_raises_exception(self): - self.assertRaisesRegex(ValueError, + self.assertRaisesRegex(SchemaViolation, r'provided', lambda: ApiData(name=self.api_name, upstream_url=self.api_upstream_url)) @@ -58,7 +59,7 @@ def test_create_api_data_w_invalid_fields_raises_exception(self): invalid_value = "" # Verify - self.assertRaisesRegex(KeyError, + self.assertRaisesRegex(SchemaViolation, r'invalid_field', lambda: ApiData(name=self.api_name, upstream_url=self.api_upstream_url, From abd5fb3eec24d151abab3df20d6293a4778bd7ca Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 28 Mar 2018 17:00:22 -0300 Subject: [PATCH 11/34] Refactor: Extracted to method assert mock called refs:#25 --- tests/service_admin_tests.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/service_admin_tests.py b/tests/service_admin_tests.py index d89ba11..7fab16d 100644 --- a/tests/service_admin_tests.py +++ b/tests/service_admin_tests.py @@ -109,13 +109,9 @@ def setUp(self): def test_create_a_service_w_url(self): super(ServiceAdminClientMockedTest, self).test_create_a_service_w_url() - expected_data = dict(name=self.service_name, url=self.service_url) - endpoint = self.kong_url + 'services/' - self.session.post.assert_called_once_with(endpoint, data=expected_data) - - def test_create_service(self): - super(ServiceAdminClientMockedTest, self).test_create_service() + self.assert_called_create_in_mock() + def assert_called_create_in_mock(self): expected_data = dict(name=self.service_name, protocol=self.service_protocol, host=self.service_host, @@ -124,6 +120,11 @@ def test_create_service(self): endpoint = self.kong_url + 'services/' self.session.post.assert_called_once_with(endpoint, data=expected_data) + def test_create_service(self): + super(ServiceAdminClientMockedTest, self).test_create_service() + + self.assert_called_create_in_mock() + @pytest.mark.slow class ServiceAdminClientServerTest(ServiceAdminClientAbstractTest, unittest.TestCase): From cbada2d347da9999aa7c8b36fa753cac74b5c620 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 28 Mar 2018 17:00:54 -0300 Subject: [PATCH 12/34] Added schema validation in ServiceData refs:#25 --- kong/structures.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/kong/structures.py b/kong/structures.py index e902913..fa26c0a 100644 --- a/kong/structures.py +++ b/kong/structures.py @@ -1,7 +1,7 @@ from abc import abstractmethod import re -from urllib3.util import parse_url +from urllib3.util import parse_url, Url from kong.exceptions import SchemaViolation @@ -10,17 +10,17 @@ class ObjectData: def __init__(self, name, **kwargs): - self.validate_schema(**kwargs) + validated = self.validate_schema(**kwargs) self.validate_parameter('name', name) self.name = name - for k, val in kwargs.items(): + for k, val in validated.items(): self.validate_parameter(k, val) self.__setattr__(k, val) @abstractmethod - def validate_schema(self, **kwargs): + def validate_schema(self, params): pass @property @@ -72,6 +72,8 @@ def validate_schema(self, **kwargs): if not self.satisfy_semi_optional_parameters(**kwargs): raise SchemaViolation('uris, methods or hosts must be provided to create') + return kwargs + def add_uri(self, uri): self.uris.append(self.__normalize_uri(uri)) return ", ".join(self.uris) @@ -87,8 +89,26 @@ def __normalize_uri(uri): class ServiceData(ObjectData): + def validate_schema(self, **kwargs): + if 'url' in kwargs: + if ('protocol' in kwargs) or ('host' in kwargs) \ + or ('port' in kwargs) or ('path' in kwargs): + raise SchemaViolation('if url is provided porotocol, host, port and path are unnecesary') + + url = parse_url(kwargs.pop('url')) + kwargs['protocol'] = url.scheme + kwargs['host'] = url.host + kwargs['port'] = url.port or 80 + kwargs['path'] = url.path + + return kwargs + @property def allowed_parameters(self): return 'name', 'protocol', 'host', 'port', 'path',\ 'retries', 'connect_timeout', 'send_timeout',\ 'read_timeout', 'url' + + @property + def url(self): + return Url(scheme=self.protocol, host=self.host, port=self.port, path=self.path).url From 0ad7627683862ffe77f7e25fea409aa2d9ccedf2 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 28 Mar 2018 17:01:21 -0300 Subject: [PATCH 13/34] Added ServiceData tests refs:#25 --- tests/service_data_tests.py | 83 +++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/service_data_tests.py diff --git a/tests/service_data_tests.py b/tests/service_data_tests.py new file mode 100644 index 0000000..fa852c6 --- /dev/null +++ b/tests/service_data_tests.py @@ -0,0 +1,83 @@ +import unittest + +from kong.structures import ServiceData + + +class ServiceDataTest(unittest.TestCase): + + def setup_service_vars(self): + self.service_protocol = 'http' + self.service_host = 'example.org' + self.service_port = 8080 + self.service_path = '/api' + self.service_name = 'example-service' + self.service_url = '%s://%s:%s%s' % (self.service_protocol, + self.service_host, + self.service_port, + self.service_path) + self.service_retries = 5 + self.service_connect_timeout = 60000 + self.service_read_timeout = 60000 + self.service_send_timeout = 60000 + + self.service_dict = { + "id": "4e13f54a-bbf1-47a8-8777-255fed7116f2", + "created_at": 1488869076800, + "updated_at": 1488869076800, + "connect_timeout": self.service_connect_timeout, + "protocol": self.service_protocol, + "host": self.service_host, + "port": self.service_port, + "path": self.service_path, + "name": self.service_name, + "retries": self.service_retries, + "read_timeout": self.service_read_timeout, + "send_timeout": self.service_send_timeout + } + + def setUp(self): + self.setup_service_vars() + + def test_create_service(self): + + created = ServiceData(self.service_name, protocol=self.service_protocol, host=self.service_host) + + self.assertEqual(self.service_name, created.name) + self.assertEqual(self.service_protocol, created.protocol) + self.assertEqual(self.service_host, created.host) + + def test_service_data_as_dict(self): + + created = ServiceData(self.service_name, + protocol=self.service_protocol, + host=self.service_host, + connect_timeout=self.service_connect_timeout, + port=self.service_port, + path=self.service_path, + retries=self.service_retries, + read_timeout=self.service_read_timeout, + send_timeout=self.service_send_timeout) + + expected = { + "connect_timeout": self.service_connect_timeout, + "protocol": self.service_protocol, + "host": self.service_host, + "port": self.service_port, + "path": self.service_path, + "name": self.service_name, + "retries": self.service_retries, + "read_timeout": self.service_read_timeout, + "send_timeout": self.service_send_timeout + } + + self.assertEqual(expected, created.as_dict()) + + def test_create_service_using_url(self): + + created = ServiceData(self.service_name, url=self.service_url) + print(created) + self.assertEqual(self.service_protocol, created.protocol) + self.assertEqual(self.service_host, created.host) + self.assertEqual(self.service_port, created.port) + self.assertEqual(self.service_path, created.path) + self.assertEqual(self.service_url, created.url) From a1bf0c0c59cb07aa80b543178ab32f41dae3137e Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 28 Mar 2018 17:03:09 -0300 Subject: [PATCH 14/34] Fixed long lines refs:#25 refs:#25 --- kong/structures.py | 8 +++++--- tests/service_data_tests.py | 4 +++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/kong/structures.py b/kong/structures.py index fa26c0a..1d65195 100644 --- a/kong/structures.py +++ b/kong/structures.py @@ -20,7 +20,7 @@ def __init__(self, name, **kwargs): self.__setattr__(k, val) @abstractmethod - def validate_schema(self, params): + def validate_schema(self, **kwargs): pass @property @@ -33,7 +33,8 @@ def validate_parameter(self, parameter, value): raise SchemaViolation('invalid parameter: %s' % parameter) if not isinstance(value, (str, int, bool, list, dict)): - raise ValueError('invalid value: %s value must be str, int, bool, list or dict' % parameter) + raise ValueError('invalid value: %s value must be str, int, ' + 'bool, list or dict' % parameter) def as_dict(self): return self.__dict__.copy() @@ -93,7 +94,8 @@ def validate_schema(self, **kwargs): if 'url' in kwargs: if ('protocol' in kwargs) or ('host' in kwargs) \ or ('port' in kwargs) or ('path' in kwargs): - raise SchemaViolation('if url is provided porotocol, host, port and path are unnecesary') + raise SchemaViolation('if url is provided porotocol, host,' + ' port and path are unnecesary') url = parse_url(kwargs.pop('url')) kwargs['protocol'] = url.scheme diff --git a/tests/service_data_tests.py b/tests/service_data_tests.py index fa852c6..9349d44 100644 --- a/tests/service_data_tests.py +++ b/tests/service_data_tests.py @@ -40,7 +40,9 @@ def setUp(self): def test_create_service(self): - created = ServiceData(self.service_name, protocol=self.service_protocol, host=self.service_host) + created = ServiceData(self.service_name, + protocol=self.service_protocol, + host=self.service_host) self.assertEqual(self.service_name, created.name) self.assertEqual(self.service_protocol, created.protocol) From 9f508996cc4cf691da9aea94e144a242dd5e7c40 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 3 Apr 2018 14:26:37 -0300 Subject: [PATCH 15/34] Added service update refs:#25 --- kong/kong_clients.py | 10 ++++++++++ tests/service_admin_tests.py | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index c04798e..160b48b 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -356,6 +356,16 @@ def update(self, pk_or_id, **kwargs): class ServiceAdminClient(KongAbstractClient): + @property + def _allowed_update_params(self): + return 'name', 'protocol', 'host', 'port', 'path', \ + 'retries', 'connect_timeout', 'send_timeout', \ + 'read_timeout', 'url' + + @property + def _allowed_query_params(self): + pass + @property def path(self): return 'services/' diff --git a/tests/service_admin_tests.py b/tests/service_admin_tests.py index 7fab16d..85e9347 100644 --- a/tests/service_admin_tests.py +++ b/tests/service_admin_tests.py @@ -71,6 +71,10 @@ def test_create_w_invalid_params(self): invalid='invalid') ) + def test_update(self): + # Exercise + self.service_admin_client.update(self.service_name, path='/new/path') + class ServiceAdminClientMockedTest(ServiceAdminClientAbstractTest, unittest.TestCase): @@ -97,6 +101,8 @@ def setup_mock_vars(self): self.session = MagicMock() self.session.post.return_value.status_code = 201 self.session.post.return_value.json.return_value = self.service_dict + self.session.patch.return_value.status_code = 200 + self.session.patch.return_value.json.return_value = self.service_dict def new_service_admin_client(self): return ServiceAdminClient(self.kong_url, _session=self.session) @@ -125,6 +131,13 @@ def test_create_service(self): self.assert_called_create_in_mock() + def test_update(self): + super().test_update() + + expected_data = {'path': '/new/path'} + endpoint = self.kong_url + 'services/' + self.service_name + self.session.patch.assert_called_once_with(endpoint, data=expected_data) + @pytest.mark.slow class ServiceAdminClientServerTest(ServiceAdminClientAbstractTest, unittest.TestCase): @@ -142,3 +155,8 @@ def new_service_admin_client(self): def tearDown(self): self.service_admin_client.delete(self.service_name) + + def test_update(self): + self.service_admin_client.create(name=self.service_name, + url=self.service_url) + super().test_update() From 89d6ef92cf46c0b649f50308a1dbe5330fa93cf8 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 3 Apr 2018 15:27:22 -0300 Subject: [PATCH 16/34] Rework of ServiceData schema_validation refs:#25 refs:#25 --- kong/structures.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/kong/structures.py b/kong/structures.py index 1d65195..d8ce3d2 100644 --- a/kong/structures.py +++ b/kong/structures.py @@ -91,17 +91,29 @@ def __normalize_uri(uri): class ServiceData(ObjectData): def validate_schema(self, **kwargs): - if 'url' in kwargs: - if ('protocol' in kwargs) or ('host' in kwargs) \ - or ('port' in kwargs) or ('path' in kwargs): - raise SchemaViolation('if url is provided porotocol, host,' - ' port and path are unnecesary') + + if 'url' not in kwargs: + required_fields = ['host', 'protocol'] + for field in required_fields: + if field not in kwargs: + raise SchemaViolation('%s: required field missing' % field) + + if 'port' not in kwargs: + kwargs['port'] = 80 + if 'path' not in kwargs: + kwargs['path'] = '/' + + else: + overflowed_fields = ['host', 'protocol', 'port', 'path'] + for field in overflowed_fields: + if field in kwargs: + raise SchemaViolation('%s: got multiple values' % field) url = parse_url(kwargs.pop('url')) kwargs['protocol'] = url.scheme kwargs['host'] = url.host kwargs['port'] = url.port or 80 - kwargs['path'] = url.path + kwargs['path'] = url.path or '/' return kwargs From 5efee3323de5c36d9f917c60355b1a16b712c69d Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 3 Apr 2018 16:58:50 -0300 Subject: [PATCH 17/34] Added route admin tests refs:#26 --- tests/route_admin_tests.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/route_admin_tests.py diff --git a/tests/route_admin_tests.py b/tests/route_admin_tests.py new file mode 100644 index 0000000..e21dd41 --- /dev/null +++ b/tests/route_admin_tests.py @@ -0,0 +1,34 @@ +import unittest +from unittest.mock import MagicMock + +from kong.kong_clients import RouteAdminClient + + +class RouteAdminTest(unittest.TestCase): + + def setUp(self): + self.service = MagicMock() + self.service.id = "4e13f54a-bbf1-47a8-8777-255fed7116f2" + + self.session = MagicMock() + self.session.post.return_value.status_code = 201 + + self.kong_url = 'http://kog.url/' + self.routes_endpoint = self.kong_url + 'routes/' + + self.route_admin_client = RouteAdminClient(self.kong_url, _session=self.session) + + def test_create_route_w_service(self): + self.create_and_assert(self.service) + + def test_create_route_w_service_id(self): + self.create_and_assert(self.service.id) + + def create_and_assert(self, service_id): + # Setup + methods = ['GET', 'POST'] + # Exercise + self.route_admin_client.create(service=service_id, methods=methods) + # Verify + expected_data = {'service': {'id': self.service.id}, 'methods': methods} + self.session.post.assert_called_once_with(self.routes_endpoint, data=expected_data) From f72d862d6ebdc81d319aaa9ade2c31bcbabde23b Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Tue, 3 Apr 2018 16:59:09 -0300 Subject: [PATCH 18/34] declared route admin refs:#26 --- kong/kong_clients.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 160b48b..9a00002 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -2,6 +2,7 @@ from urllib3.util.url import Url, parse_url from requests import session from kong.structures import ApiData, ServiceData +from kong.exceptions import SchemaViolation class RestClient: @@ -374,3 +375,22 @@ def create(self, name, **kwargs): service = ServiceData(name=name, **kwargs) return self._send_create(service.as_dict()) + + +class RouteAdminClient(KongAbstractClient): + + @property + def path(self): + return 'routes/' + + def create(self, service, **kwargs): + + service_id = service + + if not isinstance(service, str): + try: + service_id = service.id + except AttributeError: + raise SchemaViolation('must provide service or service_id') + + return self._send_create(dict(**kwargs, service={'id': service_id})) From 23174c165dabc0781c74aae10aadf0e8628c5488 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 09:41:06 -0300 Subject: [PATCH 19/34] Removed count since is no longer supported by kong 0.13.0 refs:#26 --- kong/kong_clients.py | 13 ++++++++++--- tests/api_admin_tests.py | 9 +++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 9a00002..b8cbae7 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -129,8 +129,12 @@ def _send_list(self, size=10, offset=None, **kwargs): offset = response['offset'] else: offset = None - + """ + total is deprecated since kong 0.13.0 + return offset, elements, response['total'] + """ + return offset, elements def _send_retrieve(self, name_or_id, endpoint=None): endpoint = endpoint or self.endpoint @@ -176,7 +180,7 @@ def list(self, size=10, **kwargs): def generator(): offset = None while True: - offset, cached, _ = self._send_list(size, offset, **query_params) + offset, cached = self._send_list(size, offset, **query_params) while cached: yield cached.pop() @@ -186,9 +190,12 @@ def generator(): return generator() + """ + Deprecated since kong 0.13.0 + def count(self): return self._send_list(0)[2] - + """ def update(self, pk_or_id, **kwargs): query_params = self._validate_update_params(kwargs) diff --git a/tests/api_admin_tests.py b/tests/api_admin_tests.py index 158dc67..7b546ed 100644 --- a/tests/api_admin_tests.py +++ b/tests/api_admin_tests.py @@ -214,10 +214,11 @@ def test_api_admin_list_w_invalid_params(self): self.assertRaisesRegex(KeyError, 'invalid_field', lambda: self.api_admin_client.list(**invalid_query)) + """ + count is deprecated since kong 0.13.0 + def test_api_admin_count(self): - """ - Test: ApiAdmin.count() returns the number of created apis - """ + "Test: ApiAdmin.count() returns the number of created apis" # Setup amount = self.faker.random_int(1, 50) apis = [] @@ -236,7 +237,7 @@ def test_api_admin_count(self): # Verify self.assertEqual(amount, actual_amount) - + """ def test_create_bad_request(self): # Setup self.session_mock.post.return_value.status_code = 409 From 55f8ac5ee00de4535a00d83f66af027adaefe98d Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 09:41:50 -0300 Subject: [PATCH 20/34] Added routes admin to KongAdminClient refs:#26 --- kong/kong_clients.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index b8cbae7..3e06c55 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -43,6 +43,7 @@ def __init__(self, *args, **kwargs): self.consumers = ConsumerAdminClient(self.url, self.session) self.plugins = PluginAdminClient(self.url, self.session) self.services = ServiceAdminClient(self.url, self.session) + self.routes = RouteAdminClient(self.url, self.session) def node_status(self): return self.session.get(self.url + 'status/').json() From 938218c6df0aa0fa57a812d3ddc914aa640379f6 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 10:16:03 -0300 Subject: [PATCH 21/34] Services create returns ServiceData instance now refs:#26 --- kong/kong_clients.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 3e06c55..a9c00de 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -381,8 +381,9 @@ def path(self): def create(self, name, **kwargs): service = ServiceData(name=name, **kwargs) + created = self._send_create(service.as_dict()) - return self._send_create(service.as_dict()) + return ServiceData(created.pop('name'), **created) class RouteAdminClient(KongAbstractClient): From 94e0fa35fc60ab8989b2650cc73cb3a67bdffee0 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 10:17:30 -0300 Subject: [PATCH 22/34] Added db variables to allowed service data attributes refs:#26 --- kong/structures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kong/structures.py b/kong/structures.py index d8ce3d2..c6ff351 100644 --- a/kong/structures.py +++ b/kong/structures.py @@ -121,7 +121,8 @@ def validate_schema(self, **kwargs): def allowed_parameters(self): return 'name', 'protocol', 'host', 'port', 'path',\ 'retries', 'connect_timeout', 'send_timeout',\ - 'read_timeout', 'url' + 'read_timeout', 'url', 'id', \ + 'created_at', 'updated_at', 'write_timeout' @property def url(self): From 2da522468acee002654cf14685e2cb47b1ca0b37 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 10:18:06 -0300 Subject: [PATCH 23/34] Added server hitting route admin tests refs:#26 --- tests/route_admin_tests.py | 77 +++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/tests/route_admin_tests.py b/tests/route_admin_tests.py index e21dd41..3382595 100644 --- a/tests/route_admin_tests.py +++ b/tests/route_admin_tests.py @@ -1,23 +1,58 @@ +from abc import abstractmethod +import pytest + import unittest from unittest.mock import MagicMock -from kong.kong_clients import RouteAdminClient +import requests + +from kong.kong_clients import RouteAdminClient, ServiceAdminClient + + +class RouteAdminAbstractTests: + + def setUp(self): + self.route_admin_client = RouteAdminClient(self.kong_url, _session=self.session) + + @abstractmethod + def test_create_route_w_service(self): + pass + + @abstractmethod + def test_create_route_w_service_id(self): + pass -class RouteAdminTest(unittest.TestCase): +class RouteAdminMockedTests(RouteAdminAbstractTests, unittest.TestCase): def setUp(self): + self.service_dict = { + "id": "4e13f54a-bbf1-47a8-8777-255fed7116f2", + "created_at": 1488869076800, + "updated_at": 1488869076800, + "connect_timeout": 60000, + "protocol": "http", + "host": "example.org", + "port": 80, + "path": "/api", + "name": "example-service", + "retries": 5, + "read_timeout": 60000, + "write_timeout": 60000 + } + + self.mock_setup() + super().setUp() + + def mock_setup(self): self.service = MagicMock() self.service.id = "4e13f54a-bbf1-47a8-8777-255fed7116f2" - self.session = MagicMock() self.session.post.return_value.status_code = 201 - + self.session.post.return_value.json.return_value = self.service_dict self.kong_url = 'http://kog.url/' self.routes_endpoint = self.kong_url + 'routes/' - self.route_admin_client = RouteAdminClient(self.kong_url, _session=self.session) - def test_create_route_w_service(self): self.create_and_assert(self.service) @@ -32,3 +67,33 @@ def create_and_assert(self, service_id): # Verify expected_data = {'service': {'id': self.service.id}, 'methods': methods} self.session.post.assert_called_once_with(self.routes_endpoint, data=expected_data) + + +@pytest.mark.slow +class RouteAdminServerTests(RouteAdminAbstractTests, unittest.TestCase): + + def setUp(self): + self.kong_url = 'http://localhost:8001/' + self.session = requests.session() + + self.service_admin_client = ServiceAdminClient(self.kong_url) + + self.service = self.service_admin_client.create('test-service', url='http://test.service/path') + super().setUp() + + def tearDown(self): + self.service_admin_client.delete(self.service.id) + + def test_create_route_w_service(self): + # Setup + methods = ['GET', 'POST'] + # Exercise + created = self.route_admin_client.create(service=self.service, methods=methods) + # Verify + self.assertEqual(methods, created.methods) + + def test_create_route_w_service_id(self): + # Setup + methods = ['GET', 'POST'] + # Exercise + created = self.route_admin_client.create(service=self.service.id, methods=methods) \ No newline at end of file From 784fe12d7c38400f7b50ddad997ca2b46b479880 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 10:18:43 -0300 Subject: [PATCH 24/34] Acomodated service admin tests for new return values refs:#26 --- tests/service_admin_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/service_admin_tests.py b/tests/service_admin_tests.py index 85e9347..c020bc4 100644 --- a/tests/service_admin_tests.py +++ b/tests/service_admin_tests.py @@ -42,6 +42,8 @@ def test_create_a_service_w_url(self): self.assert_correctly_created(created) def assert_correctly_created(self, created): + created = created.as_dict() + self.assertEquals(12, len(created)) self.assertRegex(created['id'], '^[\w\d]{8}-([\w\d]{4}-){3}[\w\d]{12}$') self.assertTrue(isinstance(created['created_at'], int)) From 9ed7da5c052655aae114ca1eb90c6f95b9df7f9f Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 10:39:24 -0300 Subject: [PATCH 25/34] Refactor: sending post or patch to requests lib now uses json kw instead of data kw refs:#26 --- kong/kong_clients.py | 4 ++-- tests/api_admin_tests.py | 12 ++++++------ tests/consumer_admin_tests.py | 8 ++++---- tests/plugin_admin_tests.py | 14 +++++++------- tests/service_admin_tests.py | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index a9c00de..af1e43e 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -74,7 +74,7 @@ def _send_create(self, data, endpoint=None): endpoint = endpoint or self.endpoint - response = self.session.post(endpoint, data=data) + response = self.session.post(endpoint, json=data) if response.status_code == 409: raise NameError(response.content) @@ -97,7 +97,7 @@ def _send_delete(self, name_or_id, endpoint=None): def _send_update(self, pk_or_id, data, endpoint=None): url = (endpoint or self.endpoint) + pk_or_id - response = self.session.patch(url, data=data) + response = self.session.patch(url, json=data) if response.status_code == 400: raise KeyError(response.content) diff --git a/tests/api_admin_tests.py b/tests/api_admin_tests.py index 7b546ed..8349267 100644 --- a/tests/api_admin_tests.py +++ b/tests/api_admin_tests.py @@ -93,7 +93,7 @@ def test_api_admin_create_triggers_http_request_to_kong_server(self): upstream_url=self.api_upstream_url, uris=self.api_uris) self.session_mock.post.assert_called_once_with(self.apis_endpoint, - data=expected_api_data.as_dict()) + json=expected_api_data.as_dict()) def test_api_admin_create_using_api_data(self): """ @@ -109,7 +109,7 @@ def test_api_admin_create_using_api_data(self): self.api_admin_client.create(orig_data) # Verify - self.session_mock.post.assert_called_once_with(self.apis_endpoint, data=orig_data.as_dict()) + self.session_mock.post.assert_called_once_with(self.apis_endpoint, json=orig_data.as_dict()) def test_api_admin_delete_by_name(self): """ @@ -158,7 +158,7 @@ def test_api_admin_update(self): value = self.api_admin_client._stringify_if_list(v) expected_data[k] = value api_endpoint = self.apis_endpoint + self.api_name - self.session_mock.patch.assert_called_once_with(api_endpoint, data=expected_data) + self.session_mock.patch.assert_called_once_with(api_endpoint, json=expected_data) def test_api_admin_list(self): """ @@ -350,7 +350,7 @@ def test_update_api_w_multiple_hosts(self): # Verify self.session_mock.patch.assert_called_once_with(self.apis_endpoint + self.api_name, - data={'hosts': 'host1, host2'}) + json={'hosts': 'host1, host2'}) def test_update_api_removing_hosts(self): # Exercise @@ -358,7 +358,7 @@ def test_update_api_removing_hosts(self): # Verify self.session_mock.patch.assert_called_once_with(self.apis_endpoint + self.api_name, - data={'hosts': ''}) + json={'hosts': ''}) def test_update_api_removing_uris(self): # Exercise @@ -366,4 +366,4 @@ def test_update_api_removing_uris(self): # Verify self.session_mock.patch.assert_called_once_with(self.apis_endpoint + self.api_name, - data={'uris': ''}) + json={'uris': ''}) diff --git a/tests/consumer_admin_tests.py b/tests/consumer_admin_tests.py index 741f1e1..b8c1f54 100644 --- a/tests/consumer_admin_tests.py +++ b/tests/consumer_admin_tests.py @@ -48,7 +48,7 @@ def test_create_consumer_w_username(self): # Verify self.session_mock.post.assert_called_once_with(self.consumer_endpoint, - data={'username': self.consumer_username}) + json={'username': self.consumer_username}) def test_create_consumer_w_custom_id(self): # Exercise @@ -56,7 +56,7 @@ def test_create_consumer_w_custom_id(self): # Verify self.session_mock.post.assert_called_once_with(self.consumer_endpoint, - data={'custom_id': self.consumer_custom_id}) + json={'custom_id': self.consumer_custom_id}) def test_create_consumer_w_custom_id_and_username(self): # Exercise @@ -65,7 +65,7 @@ def test_create_consumer_w_custom_id_and_username(self): # Verify self.session_mock.post.assert_called_once_with(self.consumer_endpoint, - data={'username': self.consumer_username, + json={'username': self.consumer_username, 'custom_id': self.consumer_custom_id}) def test_create_consumer_wo_parameters(self): @@ -154,7 +154,7 @@ def test_update_consumer(self): # Verify self.session_mock.patch\ .assert_called_once_with(self.consumer_endpoint + self.consumer_username, - data=data) + json=data) def test_update_consumer_w_invalid_params(self): # Setup diff --git a/tests/plugin_admin_tests.py b/tests/plugin_admin_tests.py index f519f33..bcd4ce8 100644 --- a/tests/plugin_admin_tests.py +++ b/tests/plugin_admin_tests.py @@ -40,7 +40,7 @@ def test_create_plugin_for_all_apis_and_consumers(self): # Verify expected_data = {'name': self.plugin_name} - self.session_mock.post.assert_called_once_with(self.plugins_endpoint, data=expected_data) + self.session_mock.post.assert_called_once_with(self.plugins_endpoint, json=expected_data) def test_create_plugin_for_all_apis_and_specific_consumer(self): @@ -50,7 +50,7 @@ def test_create_plugin_for_all_apis_and_specific_consumer(self): # Verify expected_data = {'name': self.plugin_name, 'consumer_id': self.consumer_id} - self.session_mock.post.assert_called_once_with(self.plugins_endpoint, data=expected_data) + self.session_mock.post.assert_called_once_with(self.plugins_endpoint, json=expected_data) def test_create_plugin_for_specific_api_and_every_consumer(self): @@ -60,7 +60,7 @@ def test_create_plugin_for_specific_api_and_every_consumer(self): # Verify expected_url = self.kong_url + 'apis/' + self.api_name_or_id + '/plugins/' expected_data = {'name': self.plugin_name} - self.session_mock.post.assert_called_once_with(expected_url, data=expected_data) + self.session_mock.post.assert_called_once_with(expected_url, json=expected_data) def test_create_plugin_w_config(self): # Setup @@ -72,7 +72,7 @@ def test_create_plugin_w_config(self): # Verify expected_data = {'name': self.plugin_name, 'config.setting': 'value'} - self.session_mock.post.assert_called_once_with(self.plugins_endpoint, data=expected_data) + self.session_mock.post.assert_called_once_with(self.plugins_endpoint, json=expected_data) def test_retrieve_existing_plugin(self): # Exercise @@ -198,7 +198,7 @@ def test_update_plugin(self): 'consumer_id': self.consumer_id, 'config.setting': 'value'} - self.session_mock.patch.assert_called_once_with(expected_url, data=expected_data) + self.session_mock.patch.assert_called_once_with(expected_url, json=expected_data) def test_update_plugin_wo_api_pk(self): # Setup @@ -216,7 +216,7 @@ def test_update_plugin_wo_api_pk(self): 'consumer_id': self.consumer_id, 'config.setting': 'value'} - self.session_mock.patch.assert_called_once_with(expected_url, data=expected_data) + self.session_mock.patch.assert_called_once_with(expected_url, json=expected_data) def test_update_plugin_wo_config(self): # Setup @@ -230,4 +230,4 @@ def test_update_plugin_wo_config(self): expected_url = self.plugins_endpoint + self.plugin_id expected_data = data - self.session_mock.patch.assert_called_once_with(expected_url, data=expected_data) + self.session_mock.patch.assert_called_once_with(expected_url, json=expected_data) diff --git a/tests/service_admin_tests.py b/tests/service_admin_tests.py index c020bc4..f0dd906 100644 --- a/tests/service_admin_tests.py +++ b/tests/service_admin_tests.py @@ -126,7 +126,7 @@ def assert_called_create_in_mock(self): port=self.service_port, path=self.service_path) endpoint = self.kong_url + 'services/' - self.session.post.assert_called_once_with(endpoint, data=expected_data) + self.session.post.assert_called_once_with(endpoint, json=expected_data) def test_create_service(self): super(ServiceAdminClientMockedTest, self).test_create_service() @@ -138,7 +138,7 @@ def test_update(self): expected_data = {'path': '/new/path'} endpoint = self.kong_url + 'services/' + self.service_name - self.session.patch.assert_called_once_with(endpoint, data=expected_data) + self.session.patch.assert_called_once_with(endpoint, json=expected_data) @pytest.mark.slow From 3dc28978407e83a5e7e23059a56bb24b9107f400 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 10:52:52 -0300 Subject: [PATCH 26/34] Refactor: Extracted to abstract class common behavior refs:#26 refs:#26 --- kong/kong_clients.py | 16 +++++++++++++--- tests/api_admin_tests.py | 2 +- tests/route_admin_tests.py | 31 +++++++++---------------------- 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index af1e43e..f47c856 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -130,9 +130,10 @@ def _send_list(self, size=10, offset=None, **kwargs): offset = response['offset'] else: offset = None + # pylint: disable=pointless-string-statement """ total is deprecated since kong 0.13.0 - + return offset, elements, response['total'] """ return offset, elements @@ -191,12 +192,14 @@ def generator(): return generator() - """ + # pylint: disable=pointless-string-statement + """ Deprecated since kong 0.13.0 - + def count(self): return self._send_list(0)[2] """ + def update(self, pk_or_id, **kwargs): query_params = self._validate_update_params(kwargs) @@ -388,10 +391,17 @@ def create(self, name, **kwargs): class RouteAdminClient(KongAbstractClient): + def _allowed_update_params(self): + pass + + def _allowed_query_params(self): + pass + @property def path(self): return 'routes/' + # pylint: disable=arguments-differ def create(self, service, **kwargs): service_id = service diff --git a/tests/api_admin_tests.py b/tests/api_admin_tests.py index 8349267..faa174b 100644 --- a/tests/api_admin_tests.py +++ b/tests/api_admin_tests.py @@ -216,7 +216,7 @@ def test_api_admin_list_w_invalid_params(self): """ count is deprecated since kong 0.13.0 - + def test_api_admin_count(self): "Test: ApiAdmin.count() returns the number of created apis" # Setup diff --git a/tests/route_admin_tests.py b/tests/route_admin_tests.py index 3382595..4ca9efa 100644 --- a/tests/route_admin_tests.py +++ b/tests/route_admin_tests.py @@ -1,4 +1,3 @@ -from abc import abstractmethod import pytest import unittest @@ -14,13 +13,11 @@ class RouteAdminAbstractTests: def setUp(self): self.route_admin_client = RouteAdminClient(self.kong_url, _session=self.session) - @abstractmethod def test_create_route_w_service(self): - pass + self.create_and_assert(self.service) - @abstractmethod def test_create_route_w_service_id(self): - pass + self.create_and_assert(self.service.id) class RouteAdminMockedTests(RouteAdminAbstractTests, unittest.TestCase): @@ -53,12 +50,6 @@ def mock_setup(self): self.kong_url = 'http://kog.url/' self.routes_endpoint = self.kong_url + 'routes/' - def test_create_route_w_service(self): - self.create_and_assert(self.service) - - def test_create_route_w_service_id(self): - self.create_and_assert(self.service.id) - def create_and_assert(self, service_id): # Setup methods = ['GET', 'POST'] @@ -66,7 +57,7 @@ def create_and_assert(self, service_id): self.route_admin_client.create(service=service_id, methods=methods) # Verify expected_data = {'service': {'id': self.service.id}, 'methods': methods} - self.session.post.assert_called_once_with(self.routes_endpoint, data=expected_data) + self.session.post.assert_called_once_with(self.routes_endpoint, json=expected_data) @pytest.mark.slow @@ -78,22 +69,18 @@ def setUp(self): self.service_admin_client = ServiceAdminClient(self.kong_url) - self.service = self.service_admin_client.create('test-service', url='http://test.service/path') + self.service = self.service_admin_client.create('test-service', + url='http://test.service/path') super().setUp() def tearDown(self): + self.route_admin_client.delete(self.created['id']) self.service_admin_client.delete(self.service.id) - def test_create_route_w_service(self): + def create_and_assert(self, service_or_id): # Setup methods = ['GET', 'POST'] # Exercise - created = self.route_admin_client.create(service=self.service, methods=methods) + self.created = self.route_admin_client.create(service=service_or_id, methods=methods) # Verify - self.assertEqual(methods, created.methods) - - def test_create_route_w_service_id(self): - # Setup - methods = ['GET', 'POST'] - # Exercise - created = self.route_admin_client.create(service=self.service.id, methods=methods) \ No newline at end of file + self.assertEqual(methods, self.created['methods']) From f6a4cef70f11f6ee1b1852fa155da0e9ebe6401c Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 11:12:54 -0300 Subject: [PATCH 27/34] Added routes allowed update parameters refs:#26 --- kong/kong_clients.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index f47c856..770d273 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -391,9 +391,13 @@ def create(self, name, **kwargs): class RouteAdminClient(KongAbstractClient): + @property def _allowed_update_params(self): - pass + return 'protocols', 'methods', 'hosts',\ + 'paths', 'strip_path', 'preserve_host',\ + 'service', + @property def _allowed_query_params(self): pass From 613195b78d5aa588981005b796cc5fea87070d0e Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 11:14:56 -0300 Subject: [PATCH 28/34] Added routes and services allowed query params refs:#26 --- kong/kong_clients.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 770d273..39f9204 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -376,7 +376,7 @@ def _allowed_update_params(self): @property def _allowed_query_params(self): - pass + return [] @property def path(self): @@ -399,7 +399,7 @@ def _allowed_update_params(self): @property def _allowed_query_params(self): - pass + return [] @property def path(self): From a511e1389f3fa0389182474fbc1b16f9ac2c37ce Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 11:18:09 -0300 Subject: [PATCH 29/34] updated readme refs:#26 --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 1579912..17c4f31 100644 --- a/readme.md +++ b/readme.md @@ -7,13 +7,12 @@ This is a small library to provide [kong](http://getkong.org/) server administra This library is currently in version 0.1.6 and it was built around [kong 0.13.x specifications](https://getkong.org/docs/0.13.x/admin-api/) ## Features -Supported operations for Apis, Consumers and Plugins +Supported operations for Services, Routes, Apis, Consumers and Plugins - create - delete - retrieve - update - list -- count Supported additional operations for Plugins - retrieve_enabled @@ -21,6 +20,7 @@ Supported additional operations for Plugins Not supported - update_or_create +- count (dropped in 0.16/kong 0.13.0) - Information routes (yet) - Certificates object routes (yet) - SNI object routes (yet) From 7d6ff9a39aaf0e736d64bc8749c0f98663ae5dc7 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 11:56:07 -0300 Subject: [PATCH 30/34] Added tests for list associated to service refs:#26 --- tests/route_admin_tests.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/route_admin_tests.py b/tests/route_admin_tests.py index 4ca9efa..71a9fb1 100644 --- a/tests/route_admin_tests.py +++ b/tests/route_admin_tests.py @@ -1,3 +1,4 @@ +from abc import abstractmethod import pytest import unittest @@ -19,6 +20,17 @@ def test_create_route_w_service(self): def test_create_route_w_service_id(self): self.create_and_assert(self.service.id) + def test_retrieve_routes_associated_to_service(self): + # Exercise + generator = self.route_admin_client.list_associated_to_service(self.service.id) + + # Verify + self.assert_retrieve_routes_associated_to_service(generator) + + @abstractmethod + def assert_retrieve_routes_associated_to_service(self, result): + pass + class RouteAdminMockedTests(RouteAdminAbstractTests, unittest.TestCase): @@ -47,7 +59,7 @@ def mock_setup(self): self.session = MagicMock() self.session.post.return_value.status_code = 201 self.session.post.return_value.json.return_value = self.service_dict - self.kong_url = 'http://kog.url/' + self.kong_url = 'http://kong.url/' self.routes_endpoint = self.kong_url + 'routes/' def create_and_assert(self, service_id): @@ -59,6 +71,16 @@ def create_and_assert(self, service_id): expected_data = {'service': {'id': self.service.id}, 'methods': methods} self.session.post.assert_called_once_with(self.routes_endpoint, json=expected_data) + def assert_retrieve_routes_associated_to_service(self, result): + self.session.get.return_value.status_code = 200 + try: + result.__next__() + except StopIteration: + pass + # Verify + self.session.get.assert_called_once_with('%sservices/%s/routes/' % (self.kong_url, self.service.id), + data={'offset': None, 'size': 10}) + @pytest.mark.slow class RouteAdminServerTests(RouteAdminAbstractTests, unittest.TestCase): @@ -84,3 +106,11 @@ def create_and_assert(self, service_or_id): self.created = self.route_admin_client.create(service=service_or_id, methods=methods) # Verify self.assertEqual(methods, self.created['methods']) + + def assert_retrieve_routes_associated_to_service(self, result): + routes_list = list(result) + self.assertEqual(0, len(routes_list)) + + self.created = self.route_admin_client.create(self.service.id, paths=['/test-path']) + routes_list = list(self.route_admin_client.list_associated_to_service(self.service.id)) + self.assertEqual(1, len(routes_list)) From 91c45a9c3aa5d195b70091f71de019c73092e477 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 11:56:28 -0300 Subject: [PATCH 31/34] Added list_associated_to_service refs:#26 refs:#26 --- kong/kong_clients.py | 20 ++++++++++++++++++-- readme.md | 3 +++ tests/route_admin_tests.py | 5 +++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 39f9204..0b78342 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -408,12 +408,28 @@ def path(self): # pylint: disable=arguments-differ def create(self, service, **kwargs): + service_id = self.get_service_id(service) + + return self._send_create(dict(**kwargs, service={'id': service_id})) + + def list_associated_to_service(self, service_or_pk, size=10, **kwargs): + + manager = KongAbstractClient(self.url, _session=self.session) + manager.path = 'services/%s/routes/' % self.get_service_id(service_or_pk) + + return manager.list(size, **kwargs) + + @staticmethod + def get_service_id(service): service_id = service if not isinstance(service, str): try: service_id = service.id except AttributeError: - raise SchemaViolation('must provide service or service_id') + try: + service_id = service.name + except AttributeError: + raise SchemaViolation('must provide service or service_id') - return self._send_create(dict(**kwargs, service={'id': service_id})) + return service_id diff --git a/readme.md b/readme.md index 17c4f31..748b49e 100644 --- a/readme.md +++ b/readme.md @@ -14,6 +14,9 @@ Supported operations for Services, Routes, Apis, Consumers and Plugins - update - list +Supported additional operations for Routes +- list_associated_to_service + Supported additional operations for Plugins - retrieve_enabled - retrieve_schema diff --git a/tests/route_admin_tests.py b/tests/route_admin_tests.py index 71a9fb1..6ef2fce 100644 --- a/tests/route_admin_tests.py +++ b/tests/route_admin_tests.py @@ -78,8 +78,9 @@ def assert_retrieve_routes_associated_to_service(self, result): except StopIteration: pass # Verify - self.session.get.assert_called_once_with('%sservices/%s/routes/' % (self.kong_url, self.service.id), - data={'offset': None, 'size': 10}) + self.session.get\ + .assert_called_once_with('%sservices/%s/routes/' % (self.kong_url, self.service.id), + data={'offset': None, 'size': 10}) @pytest.mark.slow From c5caecfb015069c624f7072ee936970c8fe68724 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 14:58:09 -0300 Subject: [PATCH 32/34] Added UpstreamAdminClient refs:#5 --- kong/kong_clients.py | 110 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index 0b78342..eb8276d 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -44,6 +44,7 @@ def __init__(self, *args, **kwargs): self.plugins = PluginAdminClient(self.url, self.session) self.services = ServiceAdminClient(self.url, self.session) self.routes = RouteAdminClient(self.url, self.session) + self.upstreams = UpstreamAdminClient(self.url, self.session) def node_status(self): return self.session.get(self.url + 'status/').json() @@ -433,3 +434,112 @@ def get_service_id(service): raise SchemaViolation('must provide service or service_id') return service_id + + +class UpstreamAdminClient(KongAbstractClient): + + @property + def _allowed_update_params(self): + return [ + 'name', 'slots', 'hash_on', 'hash_fallback', 'hash_on_header', + 'hash_fallback_header', 'healthchecks.active.timeout', + 'healthchecks.active.concurrency', + 'healthchecks.active.http_path', + 'healthchecks.active.healthy.interval', + 'healthchecks.active.healthy.http_statuses', + 'healthchecks.active.healthy.successes', + 'healthchecks.active.unhealthy.interval', + 'healthchecks.active.unhealthy.http_statuses', + 'healthchecks.active.unhealthy.tcp_failures', + 'healthchecks.active.unhealthy.timeouts', + 'healthchecks.active.unhealthy.http_failures', + 'healthchecks.passive.healthy.http_statuses', + 'healthchecks.passive.healthy.successes', + 'healthchecks.passive.unhealthy.http_statuses', + 'healthchecks.passive.unhealthy.tcp_failures', + 'healthchecks.passive.unhealthy.timeouts', + 'healthchecks.passive.unhealthy.http_failures', + ] + + @property + def _allowed_query_params(self): + return 'id', 'name', 'hash_on', 'hash_fallback',\ + 'hash_on_header', 'hash_fallback_header', 'slots' + + @property + def path(self): + return 'upstreams/' + + def health_status(self, name_or_id): + endpoint = endpoint or self.endpoint + url = endpoint + name_or_id + '/health/' + response = self.session.get(url) + + if response.status_code == 404: + raise NameError(response.content) + + if response.status_code != 200: + raise Exception(response.content) + + return response.json() + + +class TargetAdminClient(KongAbstractClient): + + @property + def _allowed_query_params(self): + return 'id', 'target', 'weight' + + @property + def path(self): + return 'upstreams/%s/targets/' + + @property + def endpoint(self): + return self.__endpoint + + @endpoint.setter + def endpoint(self, val): + self.__endpoint = val + + def create(self, upstream_name_or_id, **kwargs): + + if 'target' not in kwargs: + raise SchemaViolation('must provide target url to create a target object') + + self.configure_endpoint(upstream_name_or_id) + + return self._send_create(kwargs) + + def configure_endpoint(self, upstream_name_or_id): + self.endpoint = self.url + (self.path % upstream_name_or_id) + + def list(self, upstream_name_or_id, size=10, **kwargs): + self.configure_endpoint(upstream_name_or_id) + + return super(TargetAdminClient, self).list(size, **kwargs) + + def list_all(self, upstream_name_or_id, size=10, **kwargs): + self.configure_endpoint(upstream_name_or_id) + + self.endpoint += 'all/' + + return super(TargetAdminClient, self).list(size, **kwargs) + + def delete(self, upstream_name_or_id, target_or_id): + self.configure_endpoint(upstream_name_or_id) + + return super(TargetAdminClient, self).delete(target_or_id) + + def set_healthy(self, upstream_name_or_id, target_or_id, healthy): + url = self.url + (self.path % upstream_name_or_id) + target_or_id + ('/healthy/' if healthy else '/unhealthy/') + response = self.session.post(url) + + if response.status_code != 204: + raise Exception(response.content) + + def update(self, pk_or_id, **kwargs): + raise NotImplementedError + + def retrieve(self, pk_or_id): + raise NotImplementedError From 83d2a58e10a5a89a079513a6eb424a5ce1408e4b Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 15:50:27 -0300 Subject: [PATCH 33/34] Added TargetAdminClient refs:#5 refs:#5 refs:#5 --- kong/kong_clients.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/kong/kong_clients.py b/kong/kong_clients.py index eb8276d..8bdb3a4 100644 --- a/kong/kong_clients.py +++ b/kong/kong_clients.py @@ -45,6 +45,7 @@ def __init__(self, *args, **kwargs): self.services = ServiceAdminClient(self.url, self.session) self.routes = RouteAdminClient(self.url, self.session) self.upstreams = UpstreamAdminClient(self.url, self.session) + self.targets = TargetAdminClient(self.url, self.session) def node_status(self): return self.session.get(self.url + 'status/').json() @@ -471,8 +472,7 @@ def path(self): return 'upstreams/' def health_status(self, name_or_id): - endpoint = endpoint or self.endpoint - url = endpoint + name_or_id + '/health/' + url = self.endpoint + name_or_id + '/health/' response = self.session.get(url) if response.status_code == 404: @@ -500,8 +500,9 @@ def endpoint(self): @endpoint.setter def endpoint(self, val): - self.__endpoint = val + self.__endpoint = val # pylint: disable=attribute-defined-outside-init + # pylint: disable=arguments-differ def create(self, upstream_name_or_id, **kwargs): if 'target' not in kwargs: @@ -514,6 +515,7 @@ def create(self, upstream_name_or_id, **kwargs): def configure_endpoint(self, upstream_name_or_id): self.endpoint = self.url + (self.path % upstream_name_or_id) + # pylint: disable=arguments-differ def list(self, upstream_name_or_id, size=10, **kwargs): self.configure_endpoint(upstream_name_or_id) @@ -526,13 +528,16 @@ def list_all(self, upstream_name_or_id, size=10, **kwargs): return super(TargetAdminClient, self).list(size, **kwargs) + # pylint: disable=arguments-differ def delete(self, upstream_name_or_id, target_or_id): self.configure_endpoint(upstream_name_or_id) return super(TargetAdminClient, self).delete(target_or_id) - def set_healthy(self, upstream_name_or_id, target_or_id, healthy): - url = self.url + (self.path % upstream_name_or_id) + target_or_id + ('/healthy/' if healthy else '/unhealthy/') + def set_healthy(self, upstream_name_or_id, target_or_id, is_healthy): + url = self.url + (self.path % upstream_name_or_id) \ + + target_or_id \ + + ('/healthy/' if is_healthy else '/unhealthy/') response = self.session.post(url) if response.status_code != 204: From b89c8ca5efc8eb02275c766adea089587c6c5808 Mon Sep 17 00:00:00 2001 From: Sebastian Gonzalez Date: Wed, 4 Apr 2018 16:23:24 -0300 Subject: [PATCH 34/34] Updated readme --- readme.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/readme.md b/readme.md index 748b49e..3ac18ba 100644 --- a/readme.md +++ b/readme.md @@ -7,28 +7,36 @@ This is a small library to provide [kong](http://getkong.org/) server administra This library is currently in version 0.1.6 and it was built around [kong 0.13.x specifications](https://getkong.org/docs/0.13.x/admin-api/) ## Features -Supported operations for Services, Routes, Apis, Consumers and Plugins +Supported Information Routes +- node_status +- node_information + +Supported operations for Services, Routes, Apis, Consumers, Plugins and Upstreams - create - delete - retrieve - update - list -Supported additional operations for Routes +Additional supported operations for Routes - list_associated_to_service -Supported additional operations for Plugins +Additional supported operations for Plugins - retrieve_enabled - retrieve_schema +Additional supported operations for Upstreams +- health_status + +Additional supported operations for Targets +- list_all +- set_healthy + Not supported - update_or_create - count (dropped in 0.16/kong 0.13.0) -- Information routes (yet) - Certificates object routes (yet) - SNI object routes (yet) -- Upstream object routes (yet) -- Target object routes (yet) ## Usage #### Install @@ -47,7 +55,7 @@ kong_client.apis.create(api_name, # Any string api_upstream_url, # Url string 'http://foo.bar/something" uris=api_uris) # List of uri strings ``` -for more info checkout [kong documentation](https://getkong.org/docs/0.12.x/admin-api/) +for more info checkout [kong documentation](https://getkong.org/docs/0.13.x/admin-api/) ## Development #### setup