diff --git a/CHANGELOG.md b/CHANGELOG.md index c0c1b82..b1c036f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. [conjur-api-python#30](https://github.com/cyberark/conjur-api-python/pull/30) - Add support for Show Resource endpoint [conjur-api-python#31](https://github.com/cyberark/conjur-api-python/pull/31) +- Add `resource_exists` method + [conjur-api-python#32](https://github.com/cyberark/conjur-api-python/pull/32) - Add support for LDAP authentication [conjur-api-python#22](https://github.com/cyberark/conjur-api-python/pull/22) diff --git a/README.md b/README.md index 3f823c8..98b7ecb 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,10 @@ For example: `client.list({'kind': 'user', 'inspect': True})` Gets a resource based on its kind and ID. Resource is json data that contains metadata about the resource. +#### `resource_exists(kind, resource_id)` + +Check the existence of a resource based on its kind and ID. Returns a boolean. + #### `get_role(kind, role_id)` Gets a role based on its kind and ID. Role is json data that contains metadata about the role. diff --git a/conjur_api/client.py b/conjur_api/client.py index 76b9fd5..c64e6ec 100644 --- a/conjur_api/client.py +++ b/conjur_api/client.py @@ -111,6 +111,12 @@ async def get_resource(self, kind: str, resource_id: str) -> json: """ return await self._api.get_resource(kind, resource_id) + async def resource_exists(self, kind: str, resource_id: str) -> bool: + """ + Check for the existance of a resource based on its kind and ID + """ + return await self._api.resource_exists(kind, resource_id) + async def get_role(self, kind: str, role_id: str) -> json: """ Gets a role based on its kind and ID diff --git a/conjur_api/http/api.py b/conjur_api/http/api.py index ec31a89..d082bf5 100644 --- a/conjur_api/http/api.py +++ b/conjur_api/http/api.py @@ -16,7 +16,7 @@ from conjur_api.interface.authentication_strategy_interface import AuthenticationStrategyInterface from conjur_api.wrappers.http_response import HttpResponse from conjur_api.wrappers.http_wrapper import HttpVerb, invoke_endpoint -from conjur_api.errors.errors import InvalidResourceException, MissingRequiredParameterException +from conjur_api.errors.errors import HttpStatusError, InvalidResourceException, MissingRequiredParameterException # pylint: disable=too-many-instance-attributes from conjur_api.models import Resource, ConjurConnectionInfo, ListPermittedRolesData, \ ListMembersOfData, CreateHostData, CreateTokenData, SslVerificationMetadata, SslVerificationMode @@ -179,6 +179,33 @@ async def get_resource(self, kind: str, resource_id: str) -> dict: # ?tocpath=Developer%7CREST%C2%A0APIs%7C_____19 return resource + async def resource_exists(self, kind: str, resource_id: str) -> bool: + """ + This method is used to check whether a specific resource exists. + """ + params = { + 'account': self._account, + 'kind': kind, + 'identifier': resource_id + } + params.update(self._default_params) + + try: + await invoke_endpoint(HttpVerb.HEAD, ConjurEndpoint.RESOURCE, + params, + api_token=await self.api_token, + ssl_verification_metadata=self.ssl_verification_data) + except HttpStatusError as err: + if err.status == 404: + return False + + if err.status == 403: + return True + + raise + + return True + async def get_role(self, kind: str, resource_id: str) -> dict: """ This method is used to fetch a specific role. diff --git a/conjur_api/wrappers/http_wrapper.py b/conjur_api/wrappers/http_wrapper.py index 0395938..45dea9e 100644 --- a/conjur_api/wrappers/http_wrapper.py +++ b/conjur_api/wrappers/http_wrapper.py @@ -37,6 +37,7 @@ class HttpVerb(Enum): PUT = 3 DELETE = 4 PATCH = 5 + HEAD = 6 # pylint: disable=too-many-locals,consider-using-f-string,too-many-arguments diff --git a/tests/https/test_unit_client.py b/tests/https/test_unit_client.py index 26e76fe..dd4abe2 100644 --- a/tests/https/test_unit_client.py +++ b/tests/https/test_unit_client.py @@ -237,6 +237,17 @@ async def test_client_get_resource_invokes_api(self, mock_api_token, mock_invoke self.assertTrue(exists_in_args('dummy', args)) mock_invoke_endpoint.assert_called_once() + @patch.object(Api, '_api_token', new_callable=PropertyMock) + async def test_client_resource_exists_invokes_api(self, mock_api_token, mock_invoke_endpoint): + mock_api_token.return_value = 'test_token' + await self.client.resource_exists('role', 'dummy') + + args, kwargs = mock_invoke_endpoint.call_args + self.assertEqual('test_token', kwargs.get('api_token')) + self.assertTrue(exists_in_args('role', args)) + self.assertTrue(exists_in_args('dummy', args)) + mock_invoke_endpoint.assert_called_once() + @patch.object(Api, '_api_token', new_callable=PropertyMock) async def test_client_get_many_invokes_api(self, mock_api_token, mock_invoke_endpoint): mock_api_token.return_value = 'test_token' diff --git a/tests/https/test_unit_http.py b/tests/https/test_unit_http.py index 0e0308b..34a31f9 100644 --- a/tests/https/test_unit_http.py +++ b/tests/https/test_unit_http.py @@ -46,6 +46,7 @@ def test_http_verb_has_all_the_verbs_expected(self): self.assertTrue(HttpVerb.POST) self.assertTrue(HttpVerb.DELETE) self.assertTrue(HttpVerb.PATCH) + self.assertTrue(HttpVerb.HEAD) def create_ssl_verification_metadata(mode=SslVerificationMode.TRUST_STORE, cert_path=None):