diff --git a/dataikuapi/dss/admin.py b/dataikuapi/dss/admin.py index 0ba43cc3..d4c73018 100644 --- a/dataikuapi/dss/admin.py +++ b/dataikuapi/dss/admin.py @@ -3,18 +3,18 @@ from .future import DSSFuture import json, warnings + class DSSConnectionInfo(dict): """A class holding read-only information about a connection. - This class should not be created directly. Instead, use :meth:`DSSConnection.get_info` + Do not create this object directly, use :meth:`DSSConnection.get_info` instead. The main use case of this class is to retrieve the decrypted credentials for a connection, if allowed by the connection permissions. Depending on the connection kind, the credential may be available using :meth:`get_basic_credential` - or :meth:`get_aws_credential` + or :meth:`get_aws_credential`. """ def __init__(self, data): - """Do not call this directly, use :meth:`DSSConnection.get_info`""" super(DSSConnectionInfo, self).__init__(data) def get_type(self): @@ -52,9 +52,9 @@ def get_aws_credential(self): class DSSConnection(object): """ A connection on the DSS instance. + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_connection` instead. """ def __init__(self, client, name): - """Do not call this directly, use :meth:`dataikuapi.DSSClient.get_connection`""" self.client = client self.name = name @@ -149,10 +149,9 @@ def sync_datasets_acls(self): class DSSUser(object): """ A handle for a user on the DSS instance. - Do not create this directly, use :meth:`dataikuapi.DSSClient.get_user` + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_user` instead. """ def __init__(self, client, login): - """Do not call this directly, use :meth:`dataikuapi.DSSClient.get_user`""" self.client = client self.login = login @@ -241,7 +240,7 @@ def get_client_as(self): class DSSOwnUser(object): """ A handle to interact with your own user - Do not create this directly, use :meth:`dataikuapi.DSSClient.get_own_user` + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_own_user` instead. """ def __init__(self, client): self.client = client @@ -257,9 +256,11 @@ def get_settings(self): class DSSUserSettingsBase(object): - """Settings for a DSS user""" + """ + Settings for a DSS user. + Do not create this object directly, use :meth:`DSSUser.get_settings` or :meth:`DSSOwnUser.get_settings` instead. + """ def __init__(self, settings): - """Do not call this directly, use :meth:`DSSUser.get_settings` or :meth:`DSSOwnUser.get_settings` """ self.settings = settings def get_raw(self): @@ -331,10 +332,11 @@ def remove_plugin_credential(self, plugin_id, param_set_id, preset_id, param_nam class DSSUserSettings(DSSUserSettingsBase): - """Settings for a DSS user""" - + """ + Settings for a DSS user. + Do not create this object directly, use :meth:`DSSUser.get_settings` instead. + """ def __init__(self, client, login, settings): - """Do not call this directly, use :meth:`DSSUser.get_settings`""" super(DSSUserSettings, self).__init__(settings) self.client = client self.login = login @@ -366,10 +368,11 @@ def save(self): class DSSOwnUserSettings(DSSUserSettingsBase): - """Settings for the current DSS user""" - + """ + Settings for the current DSS user. + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_own_user` instead. + """ def __init__(self, client, settings): - """Do not call this directly, use :meth:`dataikuapi.DSSClient.get_own_user`""" super(DSSOwnUserSettings, self).__init__(settings) self.client = client @@ -381,9 +384,8 @@ def save(self): class DSSUserActivity(object): """ Activity for a DSS user. - Do not call this directly, use :meth:`DSSUser.get_activity` or :meth:`DSSClient.list_users_activity` + Do not create this object directly, use :meth:`DSSUser.get_activity` or :meth:`DSSClient.list_users_activity` instead. """ - def __init__(self, client, login, activity): self.client = client self.login = login @@ -442,10 +444,9 @@ def last_session_activity(self): class DSSGroup(object): """ A group on the DSS instance. - Do not create this directly, use :meth:`dataikuapi.DSSClient.get_group` + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_group` instead. """ def __init__(self, client, name): - """Do not call this directly, use :meth:`dataikuapi.DSSClient.get_group`""" self.client = client self.name = name @@ -488,10 +489,9 @@ def set_definition(self, definition): class DSSGeneralSettings(object): """ The general settings of the DSS instance. - Do not create this directly, use :meth:`dataikuapi.DSSClient.get_general_settings` + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_general_settings` instead. """ def __init__(self, client): - """Do not call this directly, use :meth:`dataikuapi.DSSClient.get_general_settings`""" self.client = client self.settings = self.client._perform_json("GET", "/admin/general-settings") @@ -614,6 +614,7 @@ def push_container_exec_base_images(self): raise Exception('Container exec base image push failed : %s' % (json.dumps(resp.get('messages', {}).get('messages', {})))) return resp + class DSSUserImpersonationRule(object): """ Helper to build user-level rule items for the impersonation settings @@ -676,6 +677,7 @@ def user_regexp(self, regexp, unix_user, hadoop_user=None): self.raw['targetHadoop'] = hadoop_user return self + class DSSGroupImpersonationRule(object): """ Helper to build group-level rule items for the impersonation settings @@ -720,10 +722,11 @@ def group_regexp(self, regexp, unix_user, hadoop_user=None): self.raw['targetHadoop'] = hadoop_user return self + class DSSCodeEnv(object): """ A code env on the DSS instance. - Do not create this directly, use :meth:`dataikuapi.DSSClient.get_code_env` + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_code_env` instead. """ def __init__(self, client, env_lang, env_name): self.client = client @@ -923,7 +926,7 @@ def get_log(self, log_name): class DSSCodeEnvSettings(object): """ Base settings class for a DSS code env. - Do not instantiate this class directly, use :meth:`DSSCodeEnv.get_settings` + Do not create this object directly, use :meth:`DSSCodeEnv.get_settings` instead. Use :meth:`save` to save your changes """ @@ -948,6 +951,7 @@ def save(self): self.codeenv.client._perform_json( "PUT", "/admin/code-envs/%s/%s" % (self.env_lang, self.env_name), body=self.settings) + class DSSCodeEnvPackageListBearer(object): def get_required_packages(self, as_list=False): """ @@ -1025,11 +1029,10 @@ def set_built_spark_kubernetes_confs(self, *configs, **kwargs): class DSSDesignCodeEnvSettings(DSSCodeEnvSettings, DSSCodeEnvPackageListBearer, DSSCodeEnvContainerConfsBearer): """ Base settings class for a DSS code env on a design node. - Do not instantiate this class directly, use :meth:`DSSCodeEnv.get_settings` + Do not create this object directly, use :meth:`DSSCodeEnv.get_settings` instead. Use :meth:`save` to save your changes """ - def __init__(self, codeenv, settings): super(DSSDesignCodeEnvSettings, self).__init__(codeenv, settings) @@ -1037,15 +1040,13 @@ def __init__(self, codeenv, settings): class DSSAutomationCodeEnvSettings(DSSCodeEnvSettings, DSSCodeEnvContainerConfsBearer): """ Base settings class for a DSS code env on an automation node. - Do not instantiate this class directly, use :meth:`DSSCodeEnv.get_settings` + Do not create this object directly, use :meth:`DSSCodeEnv.get_settings` instead. Use :meth:`save` to save your changes """ - def __init__(self, codeenv, settings): super(DSSAutomationCodeEnvSettings, self).__init__(codeenv, settings) - def get_version(self, version_id=None): """ Get a specific code env version (for versioned envs) or the single @@ -1075,11 +1076,10 @@ def get_version(self, version_id=None): class DSSAutomationCodeEnvVersionSettings(DSSCodeEnvPackageListBearer): """ Base settings class for a DSS code env version on an automation node. - Do not instantiate this class directly, use :meth:`DSSAutomationCodeEnvSettings.get_version` + Do not create this object directly, use :meth:`DSSAutomationCodeEnvSettings.get_version` instead. Use :meth:`save` on the :class:`DSSAutomationCodeEnvSettings` to save your changes """ - def __init__(self, codeenv_settings, version_settings): self.codeenv_settings = codeenv_settings self.settings = version_settings @@ -1136,12 +1136,89 @@ def set_definition(self, definition): body = definition) +class DSSPersonalApiKey(object): + """ + A personal API key on the DSS instance. + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_personal_api_key` instead. + """ + def __init__(self, client, id_): + self.client = client + self.id_ = id_ + + ######################################################## + # Key description + ######################################################## + + def get_definition(self): + """ + Get the API key's definition + + :returns: the personal API key definition, as a JSON object + """ + return self.client._perform_json( + "GET", "/personal-api-keys/%s" % (self.id_)) + + ######################################################## + # Key deletion + ######################################################## + + def delete(self): + """ + Delete the API key + """ + return self.client._perform_empty( + "DELETE", "/personal-api-keys/%s" % self.id_) + + +class DSSPersonalApiKeyListItem(dict): + """ + An item in a list of personal API key. + Do not create this object directly, use :meth:`dataikuapi.DSSClient.list_personal_api_keys` or :meth:`dataikuapi.DSSClient.list_all_personal_api_keys` instead. + """ + def __init__(self, client, data): + super(DSSPersonalApiKeyListItem, self).__init__(data) + self.client = client + + def to_personal_api_key(self): + """Gets the :class:`DSSPersonalApiKey` corresponding to this item""" + return DSSPersonalApiKey(self.client, self["id"]) + + @property + def id(self): + return self["id"] + + @property + def user(self): + return self["user"] + + @property + def key(self): + return self["key"] + + @property + def label(self): + return self["label"] + + @property + def description(self): + return self["description"] + + @property + def created_on(self): + timestamp = self["createdOn"] + return datetime.datetime.fromtimestamp(timestamp / 1000) if timestamp > 0 else None + + @property + def created_by(self): + return self["createdBy"] + + class DSSCluster(object): """ - A handle to interact with a cluster on the DSS instance + A handle to interact with a cluster on the DSS instance. + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_cluster` instead. """ def __init__(self, client, cluster_id): - """Do not call that directly, use :meth:`dataikuapi.DSSClient.get_cluster`""" self.client = client self.cluster_id = cluster_id @@ -1235,10 +1312,10 @@ def stop(self, terminate=True): class DSSClusterSettings(object): """ - The settings of a cluster + The settings of a cluster. + Do not create this object directly, use :meth:`DSSCluster.get_settings` instead. """ def __init__(self, client, cluster_id, settings): - """Do not call directly, use :meth:`DSSCluster.get_settings`""" self.client = client self.cluster_id = cluster_id self.settings = settings @@ -1268,12 +1345,13 @@ def save(self): return self.client._perform_json( "PUT", "/admin/clusters/%s" % (self.cluster_id), body=self.settings) + class DSSClusterStatus(object): """ - The status of a cluster + The status of a cluster. + Do not create this object directly, use :meth:`DSSCluster.get_status` instead. """ def __init__(self, client, cluster_id, status): - """Do not call directly, use :meth:`DSSCluster.get_Status`""" self.client = client self.cluster_id = cluster_id self.status = status @@ -1289,9 +1367,8 @@ class DSSInstanceVariables(dict): """ Dict containing the instance variables. The variables can be modified directly in the dict and persisted using its :meth:`save` method. - Do not create this directly, use :meth:`dataikuapi.DSSClient.get_global_variables` + Do not create this object directly, use :meth:`dataikuapi.DSSClient.get_global_variables` instead. """ - def __init__(self, client, variables): super(dict, self).__init__() self.update(variables) @@ -1309,7 +1386,7 @@ def save(self): class DSSGlobalUsageSummary(object): """ The summary of the usage of the DSS instance. - Do not create this directly, use :meth:`dataikuapi.dss.DSSClient.get_global_usage_summary` + Do not create this object directly, use :meth:`dataikuapi.dss.DSSClient.get_global_usage_summary` instead. """ def __init__(self, data): self.data = data diff --git a/dataikuapi/dssclient.py b/dataikuapi/dssclient.py index 96cdd080..736500bc 100644 --- a/dataikuapi/dssclient.py +++ b/dataikuapi/dssclient.py @@ -10,7 +10,8 @@ from .dss.project import DSSProject from .dss.app import DSSApp from .dss.plugin import DSSPlugin -from .dss.admin import DSSUser, DSSUserActivity, DSSOwnUser, DSSGroup, DSSConnection, DSSGeneralSettings, DSSCodeEnv, DSSGlobalApiKey, DSSCluster, DSSGlobalUsageSummary, DSSInstanceVariables +from .dss.admin import DSSPersonalApiKeyListItem, DSSUser, DSSUserActivity, DSSOwnUser, DSSGroup, DSSConnection, DSSGeneralSettings, DSSCodeEnv, DSSGlobalApiKey, DSSCluster, DSSGlobalUsageSummary, DSSInstanceVariables, DSSPersonalApiKey + from .dss.meaning import DSSMeaning from .dss.sqlquery import DSSSQLQuery from .dss.discussion import DSSObjectDiscussions @@ -19,6 +20,7 @@ import os.path as osp from .utils import DataikuException, dku_basestring_type + class DSSClient(object): """Entry point for the DSS API client""" @@ -635,6 +637,7 @@ def get_global_api_key(self, key): Get a handle to interact with a specific Global API key :param str key: the secret key of the desired API key + :returns: A :class:`dataikuapi.dss.admin.DSSGlobalApiKey` API key handle """ return DSSGlobalApiKey(self, key) @@ -648,6 +651,7 @@ def create_global_api_key(self, label=None, description=None, admin=False): :param str label: the label of the new API key :param str description: the description of the new API key :param str admin: has the new API key admin rights (True or False) + :returns: A :class:`dataikuapi.dss.admin.DSSGlobalApiKey` API key handle """ resp = self._perform_json( @@ -667,6 +671,106 @@ def create_global_api_key(self, label=None, description=None, admin=False): key = resp.get('key', '') return DSSGlobalApiKey(self, key) + ######################################################## + # Personal API Keys + ######################################################## + + def list_personal_api_keys(self, as_type='listitems'): + """ + List all your personal API keys. + + :param str as_type: How to return the personal API keys. Possible values are "listitems" and "objects" + + :return: if as_type=listitems, each key as a :class:`dataikuapi.dss.admin.DSSPersonalApiKeyListItem`. + if as_type=objects, each key is returned as a :class:`dataikuapi.dss.admin.DSSPersonalApiKey`. + """ + resp = self._perform_json( + "GET", "/personal-api-keys/") + + if as_type == "listitems": + return [DSSPersonalApiKeyListItem(self, item) for item in resp] + elif as_type == 'objects': + return [DSSPersonalApiKey(self, item['id']) for item in resp] + else: + raise ValueError("Unknown as_type") + + def get_personal_api_key(self, id): + """ + Get a handle to interact with a specific Personal API key. + + :param str id: the id of the desired API key + + :returns: A :class:`dataikuapi.dss.admin.DSSPersonalApiKey` API key handle + """ + return DSSPersonalApiKey(self, id) + + def create_personal_api_key(self, label="", description="", as_type='dict'): + """ + Create a Personal API key associated with your user. + + :param str label: the label of the new API key + :param str description: the description of the new API key + :param str as_type: How to return the personal API keys. Possible values are "dict" and "object" + + :return: if as_type=dict, the new personal API key is returned as a dict. + if as_type=object, the new personal API key is returned as a :class:`dataikuapi.dss.admin.DSSPersonalApiKey`. + """ + resp = self._perform_json( + "POST", "/personal-api-keys/", body={"label": label, "description": description}) + if resp is None: + raise Exception('API key creation returned no data') + if not resp.get('id', False): + raise Exception('API key creation returned no key') + + if as_type == 'object': + return DSSPersonalApiKey(self, resp["id"]) + else: + return resp + + def list_all_personal_api_keys(self, as_type='listitems'): + """ + List all personal API keys. + Only admin can list all the keys. + + :param str as_type: How to return the personal API keys. Possible values are "listitems" and "objects" + + :return: if as_type=listitems, each key as a :class:`dataikuapi.dss.admin.DSSPersonalApiKeyListItem`. + if as_type=objects, each key is returned as a :class:`dataikuapi.dss.admin.DSSPersonalApiKey`. + """ + resp = self._perform_json( + "GET", "/admin/personal-api-keys/") + if as_type == "listitems": + return [DSSPersonalApiKeyListItem(self, item) for item in resp] + elif as_type == 'objects': + return [DSSPersonalApiKey(self, item['id']) for item in resp] + else: + raise ValueError("Unknown as_type") + + def create_personal_api_key_for_user(self, user, label="", description="", as_type='object'): + """ + Create a Personal API key associated on behalf of a user. + Only admin can create a key for another user. + + :param str label: the label of the new API key + :param str description: the description of the new API key + :param str user: the id of the user to impersonate + :param str as_type: How to return the personal API keys. Possible values are "dict" and "object" + + :return: if as_type=dict, the new personal API key is returned as a dict. + if as_type=object, the new personal API key is returned as a :class:`dataikuapi.dss.admin.DSSPersonalApiKey`. + """ + resp = self._perform_json( + "POST", "/admin/personal-api-keys/", body={"user": user, "label": label, "description": description}) + if resp is None: + raise Exception('API key creation returned no data') + if not resp.get('id', False): + raise Exception('API key creation returned no key') + + if as_type == 'object': + return DSSPersonalApiKey(self, resp["id"]) + else: + return resp + ######################################################## # Meanings ######################################################## @@ -1024,18 +1128,6 @@ def get_ticket_from_browser_headers(self, headers_dict): """ return self._perform_json("POST", "/auth/ticket-from-browser-headers", body=headers_dict) - def create_personal_api_key(self, label): - """ - Creates a personal API key corresponding to the user doing the request. - This can be called if the DSSClient was initialized with an internal - ticket or with a personal API key - - :param: label string: Label for the new API key - :returns: a dict of the new API key, containing at least "secret", i.e. the actual secret API key - :rtype: dict - """ - return self._perform_json("POST", "/auth/personal-api-keys", - params={"label": label}) ######################################################## # Container execution @@ -1055,7 +1147,6 @@ def apply_kubernetes_namespaces_policies(self): resp = self._perform_json("POST", "/admin/container-exec/actions/apply-kubernetes-policies") return DSSFuture.from_resp(self, resp) - ######################################################## # Global Instance Info ######################################################## @@ -1063,7 +1154,8 @@ def apply_kubernetes_namespaces_policies(self): def get_instance_info(self): """ Get global information about the DSS instance - :return: a :classss:`DSSInstanceInfo` + + :returns: a :class:`DSSInstanceInfo` """ resp = self._perform_json("GET", "/instance-info") return DSSInstanceInfo(resp) @@ -1193,6 +1285,7 @@ def execute(self, settings=None): return self.client._perform_json("POST", "/projects/import/%s/process" % (self.import_id), body = settings) + class DSSInstanceInfo(object): """Global information about the DSS instance"""