A Simple Pythonic Client wrapper for Cloudera CDP CLI, designed for use with the Ansible framework
To install directly from latest commits, in python requirements.txt
git+git://github.com/cloudera-labs/cdpy@main#egg=cdpy
For General usage, installed from cmdline
pip install cdpy
To install the development branch instead of main
git+git://github.com/cloudera-labs/cdpy@devel#egg=cdpy
Note that this readme covers usage of this wrapper library only, for details of CDPCLI and underlying commands please see the CDPCLI documentation.
Basic function call with defaults where credentials are already available in user profile
from cdpy.cdpy import Cdpy Cdpy().iam.get_user() > {'userId': 'de740ef9-b40e-4497-8b3b-c137481c7633', 'crn': 'crn:altus:iam:us-west-1:558bc1d2-8867-4357-8524-311d51259233:user:de740ef9-b40e-4497-8b3b-c137481c7633', 'email': 'dchaffey@cloudera.com', 'firstName': 'Daniel', 'lastName': 'Chaffelson', 'creationDate': datetime.datetime(2019, 11, 4, 11, 54, 27, 581000, tzinfo=tzutc()), 'accountAdmin': False, 'identityProviderCrn': 'crn:altus:iam:us-west-1:558bc1d2-8867-4357-8524-311d51259233:samlProvider:cloudera-okta-production/a0afd6e3-ffc1-48bd-953a-60003d82f8ae', 'lastInteractiveLogin': datetime.datetime(2020, 12, 1, 11, 32, 38, 901000, tzinfo=tzutc()), 'workloadUsername': 'dchaffey'}
Typical function calls in CDP CLI are shadowed within the relevant Classes when wrapped by cdpy, or may be called directly using the call function shown further down.
Basic function call with target that does not exist (404)
from cdpy.cdpy import Cdpy Cdpy().iam.get_user('fakeusername') >> UserWarning:CDP User could not be retrieved > None
This Class function has been wrapped to automatically handle the NOT_FOUND error generated when requesting an unknown user, note the use of self.sdk.call within the Class
def get_user(self, name=None): return self.sdk.call( # Describe base function calls svc='iam', # Name of the client service func='get_user', # Name of the Function within the service to call ret_field='user', # Optional child field to return, often CDP CLI responses are wrapped like this squelch=[ # List of below Client Error Handlers # Describe any Client Error responses using the provided Squelch class Squelch( field='error_code', # CdpError Field to test value='NOT_FOUND', # String to check for in Field warning='CDP User could not be retrieved', # Warning to throw if encountered default=None # Value to return instead of Error ) ], # Include any keyword args that may be used in the function call, None/'' args will be ignored userId=name # As name is None by default, it will be ignored unless provided )
Basic function call with invalid value
from cdpy.cdpy import Cdpy Cdpy().iam.get_user('') >> UserWarning:Removing empty string arg userId from submission > {'userId': 'de740ef9-b40e-4497-8b3b-c137481c7633', 'crn': 'crn:altus:iam:us-west-1:558bc1d2-8867-4357-8524-311d51259233:user:de740ef9-b40e-4497-8b3b-c137481c7633', 'email': 'dchaffey@cloudera.com', 'firstName': 'Daniel', 'lastName': 'Chaffelson', 'creationDate': datetime.datetime(2019, 11, 4, 11, 54, 27, 581000, tzinfo=tzutc()), 'accountAdmin': False, 'identityProviderCrn': 'crn:altus:iam:us-west-1:558bc1d2-8867-4357-8524-311d51259233:samlProvider:cloudera-okta-production/a0afd6e3-ffc1-48bd-953a-60003d82f8ae', 'lastInteractiveLogin': datetime.datetime(2020, 12, 1, 11, 32, 38, 901000, tzinfo=tzutc()), 'workloadUsername': 'dchaffey'}
Here the invalid Parameter for for typical Ansible parameter name is mapped to userId within CDPCLI and is scrubbed from the submission ( may not submit zero length strings), a user warning is issued. The command is run by default with that Parameter removed, this triggering the 'list all' logic of the underlying call.
This behavior may be bypassed using the scrub_inputs switch and thus raise a native Python Error
Cdpy(scrub_inputs=False).iam.get_user('') Traceback (most recent call last): ...
Call Wrapper execution directly for arbitrary CDP Service and Function with arbitrary keyword param. This call function is the same that is exposed in the more abstract Classes in earlier examples, this is the more direct usage method allowing developers to work at varying levels within the layered abstractions with relative ease
from cdpy.common import CdpcliWrapper CdpcliWrapper().call(svc='iam', func='set_workload_password_policy', maxPasswordLifetimeDays=lifetime)
Define function to call wrapped method with prebuild payload and use custom error handling logic. Note that the env_config arguments are unpacked and passed to the CDP API directly, allowing developers to work with additional parameters without waiting for the higher level abstractions to support them This example also demonstrates bypassing the Squelch error handler to implement custom error handling logic, and the ability to revert to the provided throw_error capabilities (which are superable)
from cdpy.common import CdpcliWrapper from cdpy.common import CdpError wrap = CdpcliWrapper() env_config = dict(valueOne=value, valueTwo=value) resp = wrap.call( svc='environments', func='create_aws_environment', ret_field='environment', ret_error=True, **env_config ) if isinstance(resp, CdpError): if resp.error_code == 'INVALID_ARGUMENT': if 'constraintViolations' not in str(resp.violations): resp.update(message="Received violation warning:\n%s" % self.sdk.dumps(str(resp.violations))) self.sdk.throw_warning(resp) self.sdk.throw_error(resp) return resp
Declare custom error handling function and instantiate with it. This abstraction is specifically to allow developers to replace native Python error handling with framework specific handling, such as the typical Ansible module fail_json seen here
from cdpy.common import CdpError class CdpModule(object) def _cdp_module_throw_error(self, error: 'CdpError'): """Wraps throwing Errors when used as Ansible module""" self.module.fail_json(msg=str(error.__dict__)) self.sdk = Cdpy(error_handler=self._cdp_module_throw_error)
Ideally for extensive development you would make use of the metaclass Cdpy, this is currently used as the basis for the Cloudera CDP Public Cloud Ansible Collection
from cdpy.cdpy import Cdpy client = Cdpy(debug=self.debug, tls_verify=self.tls, strict_errors=self.strict, error_handler=self._cdp_module_throw_error, warning_handler=self._cdp_module_throw_warning) client.sdk.call(...) client.sdk.iam.(...) client.sdk.TERMINATION_STATES etc.
Please create a feature branch from the current development Branch then submit a PR referencing an Issue for discussion.
Please note that we require signed commits inline with Developer Certificate of Origin best-practices for Open Source Collaboration.
This project has been set up using PyScaffold 3.2.3. For details and usage information on PyScaffold see https://pyscaffold.org/.