diff --git a/barbicanclient/client.py b/barbicanclient/client.py index e812ab65..a800d3cf 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -57,62 +57,13 @@ class HTTPAuthError(HTTPError): pass -class Client(object): +class _HTTPClient(object): - def __init__(self, session=None, endpoint=None, project_id=None, + def __init__(self, session, endpoint=None, project_id=None, verify=True, service_type=_DEFAULT_SERVICE_TYPE, service_name=None, interface=_DEFAULT_SERVICE_INTERFACE, region_name=None): - """ - Barbican client object used to interact with barbican service. - - :param session: An instance of a keystoneclient Session that - can be either authenticated, or not authenticated. When using - a non-authenticated Session, you must provide some additional - parameters. When no session is provided it will default to a - non-authenticated Session. - :type session: keystoneclient.session.Session - :param str endpoint: Barbican endpoint url. Required when a session is - not given, or when using a non-authenticated session. - When using an authenticated session, the client will attempt - to get an endpoint from the session. - :param str project_id: The project ID used for context in Barbican. - Required when a session is not given, or when using a - non-authenticated session. - When using an authenticated session, the project ID will be - provided by the authentication mechanism. - :param bool verify: When a session is not given, the client will create - a non-authenticated session. This parameter is passed to the - session that is created. If set to False, it allows - barbicanclient to perform "insecure" TLS (https) requests. - The server's certificate will not be verified against any - certificate authorities. - WARNING: This option should be used with extreme caution. - :param str service_type: Used as an endpoint filter when using an - authenticated keystone session. Defaults to 'key-manager'. - :param str service_name: Used as an endpoint filter when using an - authenticated keystone session. - :param str interface: Used as an endpoint filter when using an - authenticated keystone session. Defaults to 'public'. - :param str region_name: Used as an endpoint filter when using an - authenticated keystone session. - """ - LOG.debug("Creating Client object") - - self._session = session or ks_session.Session(verify=verify) - - if self._session.auth is None: - self._validate_endpoint_and_project_id(endpoint, project_id) - - if endpoint is not None: - self._barbican_endpoint = self._get_normalized_endpoint(endpoint) - else: - self._barbican_endpoint = self._get_normalized_endpoint( - self._session.get_endpoint( - service_type=service_type, service_name=service_name, - interface=interface, region_name=region_name - ) - ) + self._session = session if project_id is None: self._default_headers = dict() @@ -120,25 +71,17 @@ class Client(object): # If provided we'll include the project ID in all requests. self._default_headers = {'X-Project-Id': project_id} - self._base_url = '{0}/{1}'.format(self._barbican_endpoint, - _DEFAULT_API_VERSION) + if not endpoint: + endpoint = session.get_endpoint(service_type=service_type, + service_name=service_name, + interface=interface, + region_name=region_name) - self.secrets = secrets.SecretManager(self) - self.orders = orders.OrderManager(self) - self.containers = containers.ContainerManager(self) - - def _validate_endpoint_and_project_id(self, endpoint, project_id): - if endpoint is None: - raise ValueError('Barbican endpoint url must be provided when not ' - 'using auth in the Keystone Session.') - if project_id is None: - raise ValueError('Project ID must be provided when not using auth ' - 'in the Keystone Session') - - def _get_normalized_endpoint(self, endpoint): if endpoint.endswith('/'): endpoint = endpoint[:-1] - return endpoint + + self._barbican_endpoint = endpoint + self._base_url = '{0}/{1}'.format(endpoint, _DEFAULT_API_VERSION) def _get(self, href, params=None): headers = {'Accept': 'application/json'} @@ -216,6 +159,63 @@ class Client(object): return message +class Client(object): + + def __init__(self, session=None, *args, **kwargs): + """ + Barbican client object used to interact with barbican service. + + :param session: An instance of keystoneclient.session.Session that + can be either authenticated, or not authenticated. When using + a non-authenticated Session, you must provide some additional + parameters. When no session is provided it will default to a + non-authenticated Session. + :param endpoint: Barbican endpoint url. Required when a session is not + given, or when using a non-authentciated session. + When using an authenticated session, the client will attempt + to get an endpoint from the session. + :param project_id: The project ID used for context in Barbican. + Required when a session is not given, or when using a + non-authenticated session. + When using an authenticated session, the project ID will be + provided by the authentication mechanism. + :param verify: When a session is not given, the client will create + a non-authenticated session. This parameter is passed to the + session that is created. If set to False, it allows + barbicanclient to perform "insecure" TLS (https) requests. + The server's certificate will not be verified against any + certificate authorities. + WARNING: This option should be used with caution. + :param service_type: Used as an endpoint filter when using an + authenticated keystone session. Defaults to 'key-management'. + :param service_name: Used as an endpoint filter when using an + authenticated keystone session. + :param interface: Used as an endpoint filter when using an + authenticated keystone session. Defaults to 'public'. + :param region_name: Used as an endpoint filter when using an + authenticated keystone session. + """ + LOG.debug("Creating Client object") + + if not session: + session = ks_session.Session(verify=kwargs.pop('verify', True)) + + if session.auth is None: + if kwargs.get('endpoint') is None: + raise ValueError('Barbican endpoint url must be provided when ' + 'not using auth in the Keystone Session.') + + if kwargs.get('project_id') is None: + raise ValueError('Project ID must be provided when not using ' + 'auth in the Keystone Session') + + httpclient = _HTTPClient(session=session, *args, **kwargs) + + self.secrets = secrets.SecretManager(httpclient) + self.orders = orders.OrderManager(httpclient) + self.containers = containers.ContainerManager(httpclient) + + def env(*vars, **kwargs): """Search for the first defined of possibly many env vars diff --git a/barbicanclient/containers.py b/barbicanclient/containers.py index 6fc0b97a..e64daa0d 100644 --- a/barbicanclient/containers.py +++ b/barbicanclient/containers.py @@ -20,7 +20,7 @@ from oslo_utils.timeutils import parse_isotime from barbicanclient import base from barbicanclient import formatter -from barbicanclient import secrets +from barbicanclient import secrets as secret_manager LOG = logging.getLogger(__name__) @@ -76,6 +76,7 @@ class Container(ContainerFormatter): container_ref=None, created=None, updated=None, status=None, secret_refs=None): self._api = api + self._secret_manager = secret_manager.SecretManager(api) self._name = name self._container_ref = container_ref self._secret_refs = secret_refs @@ -109,7 +110,7 @@ class Container(ContainerFormatter): def _fill_secrets_from_secret_refs(self): if self._secret_refs: self._cached_secrets = dict( - (name.lower(), self._api.secrets.get(secret_ref=secret_ref)) + (name.lower(), self._secret_manager.get(secret_ref=secret_ref)) for name, secret_ref in six.iteritems(self._secret_refs) ) @@ -165,7 +166,7 @@ class Container(ContainerFormatter): @_immutable_after_save def add(self, name, secret): - if not isinstance(secret, secrets.Secret): + if not isinstance(secret, secret_manager.Secret): raise ValueError("Must provide a valid Secret object") if name.lower() in self.secrets: raise KeyError("A secret with this name already exists!") diff --git a/barbicanclient/test/test_client.py b/barbicanclient/test/test_client.py index 495563ab..e9af5e0f 100644 --- a/barbicanclient/test/test_client.py +++ b/barbicanclient/test/test_client.py @@ -26,8 +26,10 @@ class TestClient(testtools.TestCase): self.responses = self.useFixture(fixture.Fixture()) self.endpoint = 'http://localhost:9311' self.project_id = 'project_id' - self.client = client.Client(endpoint=self.endpoint, - project_id=self.project_id) + sess = mock.MagicMock() + self.httpclient = client._HTTPClient(session=sess, + endpoint=self.endpoint, + project_id=self.project_id) class WhenTestingClientInit(TestClient): @@ -38,23 +40,26 @@ class WhenTestingClientInit(TestClient): return sess def test_can_be_used_without_a_session(self): - c = client.Client(endpoint=self.endpoint, - project_id=self.project_id) + c = client._HTTPClient(session=self._get_fake_session(), + endpoint=self.endpoint, + project_id=self.project_id) self.assertIsNotNone(c._session) def test_api_version_is_appended_to_endpoint(self): - c = client.Client(endpoint=self.endpoint, - project_id=self.project_id) + c = client._HTTPClient(session=self._get_fake_session(), + endpoint=self.endpoint, + project_id=self.project_id) self.assertEqual(c._base_url, 'http://localhost:9311/v1') def test_default_headers_are_empty(self): - c = client.Client(session=self._get_fake_session()) + c = client._HTTPClient(self._get_fake_session(), self.endpoint) self.assertIsInstance(c._default_headers, dict) self.assertFalse(bool(c._default_headers)) def test_project_id_is_added_to_default_headers(self): - c = client.Client(endpoint=self.endpoint, - project_id=self.project_id) + c = client._HTTPClient(session=self._get_fake_session(), + endpoint=self.endpoint, + project_id=self.project_id) self.assertIn('X-Project-Id', c._default_headers.keys()) self.assertEqual(c._default_headers['X-Project-Id'], self.project_id) @@ -67,20 +72,26 @@ class WhenTestingClientInit(TestClient): **{"endpoint": self.endpoint}) def test_client_strips_trailing_slash_from_endpoint(self): - c = client.Client(endpoint=self.endpoint + '/', - project_id=self.project_id) + c = client._HTTPClient(session=self._get_fake_session(), + endpoint=self.endpoint + '/', + project_id=self.project_id) self.assertEqual(c._barbican_endpoint, self.endpoint) def test_base_url_starts_with_endpoint_url(self): - c = client.Client(endpoint=self.endpoint, project_id=self.project_id) + c = client._HTTPClient(session=self._get_fake_session(), + endpoint=self.endpoint, + project_id=self.project_id) self.assertTrue(c._base_url.startswith(self.endpoint)) def test_base_url_ends_with_default_api_version(self): - c = client.Client(endpoint=self.endpoint, project_id=self.project_id) + c = client._HTTPClient(session=self._get_fake_session(), + endpoint=self.endpoint, + project_id=self.project_id) self.assertTrue(c._base_url.endswith(client._DEFAULT_API_VERSION)) def test_gets_endpoint_from_keystone_session(self): - c = client.Client(session=self._get_fake_session()) + c = client._HTTPClient(session=self._get_fake_session(), + endpoint=self.endpoint) self.assertEqual(c._barbican_endpoint, self.endpoint) @@ -106,33 +117,33 @@ class WhenTestingClientPost(TestClientWithSession): def setUp(self): super(WhenTestingClientPost, self).setUp() self.session = self._get_fake_session_with_status_code(201) - self.client = client.Client(session=self.session) + self.httpclient = client._HTTPClient(self.session, self.endpoint) def test_post_normalizes_url_with_traling_slash(self): - self.client._post(path='secrets', data={'test_data': 'test'}) + self.httpclient._post(path='secrets', data={'test_data': 'test'}) args, kwargs = self.session.post.call_args url = args[0] self.assertTrue(url.endswith('/')) def test_post_includes_content_type_header_of_application_json(self): - self.client._post(path='secrets', data={'test_data': 'test'}) + self.httpclient._post(path='secrets', data={'test_data': 'test'}) args, kwargs = self.session.post.call_args headers = kwargs.get('headers') self.assertIn('Content-Type', headers.keys()) self.assertEqual(headers['Content-Type'], 'application/json') def test_post_includes_default_headers(self): - self.client._default_headers = {'Test-Default-Header': 'test'} - self.client._post(path='secrets', data={'test_data': 'test'}) + self.httpclient._default_headers = {'Test-Default-Header': 'test'} + self.httpclient._post(path='secrets', data={'test_data': 'test'}) args, kwargs = self.session.post.call_args headers = kwargs.get('headers') self.assertIn('Test-Default-Header', headers.keys()) def test_post_checks_status_code(self): - self.client._check_status_code = mock.MagicMock() - self.client._post(path='secrets', data={'test_data': 'test'}) + self.httpclient._check_status_code = mock.MagicMock() + self.httpclient._post(path='secrets', data={'test_data': 'test'}) resp = self.session.post() - self.client._check_status_code.assert_called_with(resp) + self.httpclient._check_status_code.assert_called_with(resp) class WhenTestingClientGet(TestClientWithSession): @@ -140,65 +151,65 @@ class WhenTestingClientGet(TestClientWithSession): def setUp(self): super(WhenTestingClientGet, self).setUp() self.session = self._get_fake_session_with_status_code(200) - self.client = client.Client(session=self.session) + self.httpclient = client._HTTPClient(self.session, self.endpoint) self.headers = dict() self.href = 'http://test_href' def test_get_uses_href_as_is(self): - self.client._get(self.href) + self.httpclient._get(self.href) args, kwargs = self.session.get.call_args url = args[0] self.assertEqual(url, self.href) def test_get_passes_params(self): params = object() - self.client._get(self.href, params=params) + self.httpclient._get(self.href, params=params) args, kwargs = self.session.get.call_args passed_params = kwargs.get('params') self.assertIs(params, passed_params) def test_get_includes_accept_header_of_application_json(self): - self.client._get(self.href) + self.httpclient._get(self.href) args, kwargs = self.session.get.call_args headers = kwargs.get('headers') self.assertIn('Accept', headers.keys()) self.assertEqual(headers['Accept'], 'application/json') def test_get_includes_default_headers(self): - self.client._default_headers = {'Test-Default-Header': 'test'} - self.client._get(self.href) + self.httpclient._default_headers = {'Test-Default-Header': 'test'} + self.httpclient._get(self.href) args, kwargs = self.session.get.call_args headers = kwargs.get('headers') self.assertIn('Test-Default-Header', headers.keys()) def test_get_checks_status_code(self): - self.client._check_status_code = mock.MagicMock() - self.client._get(self.href) + self.httpclient._check_status_code = mock.MagicMock() + self.httpclient._get(self.href) resp = self.session.get() - self.client._check_status_code.assert_called_with(resp) + self.httpclient._check_status_code.assert_called_with(resp) def test_get_raw_uses_href_as_is(self): - self.client._get_raw(self.href, self.headers) + self.httpclient._get_raw(self.href, self.headers) args, kwargs = self.session.get.call_args url = args[0] self.assertEqual(url, self.href) def test_get_raw_passes_headers(self): - self.client._get_raw(self.href, self.headers) + self.httpclient._get_raw(self.href, self.headers) args, kwargs = self.session.get.call_args headers = kwargs.get('headers') self.assertIs(headers, self.headers) def test_get_raw_includes_default_headers(self): - self.client._default_headers = {'Test-Default-Header': 'test'} - self.client._get_raw(self.href, self.headers) + self.httpclient._default_headers = {'Test-Default-Header': 'test'} + self.httpclient._get_raw(self.href, self.headers) self.assertIn('Test-Default-Header', self.headers.keys()) def test_get_raw_checks_status_code(self): - self.client._check_status_code = mock.MagicMock() - self.client._get_raw(self.href, self.headers) + self.httpclient._check_status_code = mock.MagicMock() + self.httpclient._get_raw(self.href, self.headers) resp = self.session.get() - self.client._check_status_code.assert_called_with(resp) + self.httpclient._check_status_code.assert_called_with(resp) class WhenTestingClientDelete(TestClientWithSession): @@ -206,34 +217,35 @@ class WhenTestingClientDelete(TestClientWithSession): def setUp(self): super(WhenTestingClientDelete, self).setUp() self.session = self._get_fake_session_with_status_code(200) - self.client = client.Client(session=self.session) + self.httpclient = client._HTTPClient(session=self.session, + endpoint=self.endpoint) self.href = 'http://test_href' def test_delete_uses_href_as_is(self): - self.client._delete(self.href) + self.httpclient._delete(self.href) args, kwargs = self.session.delete.call_args url = args[0] self.assertEqual(url, self.href) def test_delete_passes_json(self): json = '{"test": "test"}' - self.client._delete(self.href, json=json) + self.httpclient._delete(self.href, json=json) args, kwargs = self.session.delete.call_args passed_json = kwargs.get('json') self.assertEqual(passed_json, json) def test_delete_includes_default_headers(self): - self.client._default_headers = {'Test-Default-Header': 'test'} - self.client._delete(self.href) + self.httpclient._default_headers = {'Test-Default-Header': 'test'} + self.httpclient._delete(self.href) args, kwargs = self.session.delete.call_args headers = kwargs.get('headers') self.assertIn('Test-Default-Header', headers.keys()) def test_delete_checks_status_code(self): - self.client._check_status_code = mock.MagicMock() - self.client._delete(self.href) + self.httpclient._check_status_code = mock.MagicMock() + self.httpclient._delete(self.href) resp = self.session.get() - self.client._check_status_code.assert_called_with(resp) + self.httpclient._check_status_code.assert_called_with(resp) class WhenTestingCheckStatusCodes(TestClient): @@ -241,20 +253,21 @@ class WhenTestingCheckStatusCodes(TestClient): def test_raises_http_auth_error_for_401_response(self): resp = mock.MagicMock() resp.status_code = 401 - self.assertRaises(client.HTTPAuthError, self.client._check_status_code, + self.assertRaises(client.HTTPAuthError, + self.httpclient._check_status_code, resp) def test_raises_http_server_error_for_500_response(self): resp = mock.MagicMock() resp.status_code = 500 self.assertRaises(client.HTTPServerError, - self.client._check_status_code, resp) + self.httpclient._check_status_code, resp) def test_raises_http_client_error_for_400_response(self): resp = mock.MagicMock() resp.status_code = 400 self.assertRaises(client.HTTPClientError, - self.client._check_status_code, resp) + self.httpclient._check_status_code, resp) class WhenTestingGetErrorMessage(TestClient): @@ -262,14 +275,14 @@ class WhenTestingGetErrorMessage(TestClient): def test_gets_error_message_from_title_in_json(self): resp = mock.MagicMock() resp.json.return_value = {'title': 'test_text'} - msg = self.client._get_error_message(resp) + msg = self.httpclient._get_error_message(resp) self.assertEqual(msg, 'test_text') def test_gets_error_message_from_content_when_no_json(self): resp = mock.MagicMock() resp.json.side_effect = ValueError() resp.content = content = 'content' - msg = self.client._get_error_message(resp) + msg = self.httpclient._get_error_message(resp) self.assertEqual(msg, content) diff --git a/barbicanclient/test/test_client_containers.py b/barbicanclient/test/test_client_containers.py index 32cf4896..ff2c2ad6 100644 --- a/barbicanclient/test/test_client_containers.py +++ b/barbicanclient/test/test_client_containers.py @@ -28,7 +28,8 @@ class ContainerData(object): self.type = 'generic' self.secret = mock.Mock(spec=secrets.Secret) self.secret.__bases__ = (secrets.Secret,) - self.secret.secret_ref = 'http://a/b/1' + self.secret.secret_ref = ('http://barbican/v1/secrets/' + 'a73b62e4-eee2-4169-9a14-b8bb4da71d87') self.secret.name = 'thing1' self.generic_secret_refs = {self.secret.name: self.secret.secret_ref} self.generic_secret_refs_json = [{'name': self.secret.name, @@ -92,9 +93,6 @@ class WhenTestingContainers(test_client.BaseEntityResource): self._setUp('containers') self.container = ContainerData() - self.client.secrets = mock.MagicMock() - self.client.secrets.get.return_value = self.container.secret - self.client.secrets._api = self.client self.manager = self.client.containers self.consumers_post_resource = self.entity_href + '/consumers/'