diff --git a/dataikuapi/__init__.py b/dataikuapi/__init__.py index 89b5119a..5c4337bb 100644 --- a/dataikuapi/__init__.py +++ b/dataikuapi/__init__.py @@ -1,8 +1,9 @@ from .dssclient import DSSClient +from .fmclient import FMClientAWS, FMClientAzure from .apinode_client import APINodeClient from .apinode_admin_client import APINodeAdminClient from .dss.recipe import GroupingRecipeCreator, JoinRecipeCreator, StackRecipeCreator, WindowRecipeCreator, SyncRecipeCreator, SamplingRecipeCreator, SQLQueryRecipeCreator, CodeRecipeCreator, SplitRecipeCreator, SortRecipeCreator, TopNRecipeCreator, DistinctRecipeCreator, DownloadRecipeCreator, PredictionScoringRecipeCreator, ClusteringScoringRecipeCreator -from .dss.admin import DSSUserImpersonationRule, DSSGroupImpersonationRule \ No newline at end of file +from .dss.admin import DSSUserImpersonationRule, DSSGroupImpersonationRule diff --git a/dataikuapi/fm/__init__.py b/dataikuapi/fm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dataikuapi/fm/future.py b/dataikuapi/fm/future.py new file mode 100644 index 00000000..c52b9a04 --- /dev/null +++ b/dataikuapi/fm/future.py @@ -0,0 +1,101 @@ +import sys, time + + +class FMFuture(object): + """ + A future on the DSS instance + """ + + def __init__( + self, client, job_id, state=None, result_wrapper=lambda result: result + ): + self.client = client + self.job_id = job_id + self.state = state + self.state_is_peek = True + self.result_wrapper = result_wrapper + + @staticmethod + def from_resp(client, resp, result_wrapper=lambda result: result): + """Creates a DSSFuture from a parsed JSON response""" + return FMFuture( + client, resp.get("jobId", None), state=resp, result_wrapper=result_wrapper + ) + + @classmethod + def get_result_wait_if_needed(cls, client, ret): + if "jobId" in ret: + future = FMFuture(client, ret["jobId"], ret) + future.wait_for_result() + return future.get_result() + else: + return ret["result"] + + def abort(self): + """ + Abort the future + """ + return self.client._perform_tenant_empty("DELETE", "/futures/%s" % self.job_id) + + def get_state(self): + """ + Get the status of the future, and its result if it's ready + """ + self.state = self.client._perform_tenant_json( + "GET", "/futures/%s" % self.job_id, params={"peek": False} + ) + self.state_is_peek = False + return self.state + + def peek_state(self): + """ + Get the status of the future, and its result if it's ready + """ + self.state = self.client._perform_tenant_json( + "GET", "/futures/%s" % self.job_id, params={"peek": True} + ) + self.state_is_peek = True + return self.state + + def get_result(self): + """ + Get the future result if it's ready, raises an Exception otherwise + """ + if ( + self.state is None + or not self.state.get("hasResult", False) + or self.state_is_peek + ): + self.get_state() + if self.state.get("hasResult", False): + return self.result_wrapper(self.state.get("result", None)) + else: + raise Exception("Result not ready") + + def has_result(self): + """ + Checks whether the future has a result ready + """ + if self.state is None or not self.state.get("hasResult", False): + self.get_state() + return self.state.get("hasResult", False) + + def wait_for_result(self): + """ + Wait and get the future result + """ + if self.state.get("hasResult", False): + return self.result_wrapper(self.state.get("result", None)) + if ( + self.state is None + or not self.state.get("hasResult", False) + or self.state_is_peek + ): + self.get_state() + while not self.state.get("hasResult", False): + time.sleep(5) + self.get_state() + if self.state.get("hasResult", False): + return self.result_wrapper(self.state.get("result", None)) + else: + raise Exception("No result") diff --git a/dataikuapi/fm/instances.py b/dataikuapi/fm/instances.py new file mode 100644 index 00000000..51301f21 --- /dev/null +++ b/dataikuapi/fm/instances.py @@ -0,0 +1,297 @@ +from enum import Enum +from .future import FMFuture + + +class FMInstanceCreator(object): + def __init__( + self, client, label, instance_settings_template_id, virtual_network_id, image_id + ): + """ + Helper to create a DSS Instance + + :param object client: :class:`dataikuapi.fm.fmclient` + :param str label: The label of the instance + :param str instance_settings_template: The instance settings template id this instance should be based on + :param str virtual_network: The virtual network where the instance should be spawned + :param str image_id: The ID of the DSS runtime image (ex: dss-9.0.3-default) + """ + self.client = client + self.data = {} + self.data["label"] = label + self.data["instanceSettingsTemplateId"] = instance_settings_template_id + self.data["virtualNetworkId"] = virtual_network_id + self.data["imageId"] = image_id + + # Set the default value for dssNodeType + self.data["dssNodeType"] = "design" + + def with_dss_node_type(self, dss_node_type): + """ + Set the DSS Node type of the instance to create + + :param str dss_node_type: Optional , the type of the dss node to create. Supports "design", "automation ordeployer". Defaults to "design" + :rtype: :class:`dataikuapi.fm.instances.FMInstanceCreator` + """ + if dss_node_type not in ["design", "automation", "deployer"]: + raise ValueError( + 'Only "design", "automation" or "deployer" dss_node_type are supported' + ) + self.data["dssNodeType"] = dss_node_type + return self + + def with_cloud_instance_type(self, cloud_instance_type): + """ + Set the machine type for the DSS Instance + + :param str cloud_instance_type + :rtype: :class:`dataikuapi.fm.instances.FMInstanceCreator` + """ + self.data["cloudInstanceType"] = cloud_instance_type + return self + + def with_data_volume_options( + self, + data_volume_type=None, + data_volume_size=None, + data_volume_size_max=None, + data_volume_IOPS=None, + data_volume_encryption=None, + data_volume_encryption_key=None, + ): + """ + Set the options of the data volume to use with the DSS Instance + + :param str data_volume_type: Optional, Data volume type + :param int data_volume_size: Optional, Data volume initial size + :param int data_volume_size_max: Optional, Data volume maximum size + :param int data_volume_IOPS: Optional, Data volume IOPS + :param object data_volume_encryption: Optional, a :class:`dataikuapi.fm.instances.FMInstanceEncryptionMode` setting the encryption mode of the data volume + :param str data_volume_encryption_key: Optional, the encryption key to use when data_volume_encryption_key is FMInstanceEncryptionMode.CUSTOM + :rtype: :class:`dataikuapi.fm.instances.FMInstanceCreator` + """ + if type(data_volume_encryption) is not FMInstanceEncryptionMode: + raise TypeError( + "data_volume encryption needs to be of type FMInstanceEncryptionMode" + ) + + self.data["dataVolumeType"] = data_volume_type + self.data["dataVolumeSizeGB"] = data_volume_size + self.data["dataVolumeSizeMaxGB"] = data_volume_size_max + self.data["dataVolumeIOPS"] = data_volume_IOPS + self.data["dataVolumeEncryption"] = data_volume_encryption.value + self.data["dataVolumeEncryptionKey"] = data_volume_encryption_key + return self + + def with_cloud_tags(self, cloud_tags): + """ + Set the tags to be applied to the cloud resources created for this DSS instance + + :param dict cloud_tags: a key value dictionary of tags to be applied on the cloud resources + :rtype: :class:`dataikuapi.fm.instances.FMInstanceCreator` + """ + self.data["cloudTags"] = cloud_tags + return self + + def with_fm_tags(self, fm_tags): + """ + A list of tags to add on the DSS Instance in Fleet Manager + + :param list fm_tags: Optional, list of tags to be applied on the instance in the Fleet Manager + :rtype: :class:`dataikuapi.fm.instances.FMInstanceCreator` + """ + self.data["fmTags"] = fm_tags + return self + + +class FMAWSInstanceCreator(FMInstanceCreator): + def with_aws_root_volume_options( + self, + aws_root_volume_size=None, + aws_root_volume_type=None, + aws_root_volume_IOPS=None, + ): + """ + Set the options of the root volume of the DSS Instance + + :param int aws_root_volume_size: Optional, the root volume size + :param str aws_root_volume_type: Optional, the root volume type + :param int aws_root_volume_IOPS: Optional, the root volume IOPS + :rtype: :class:`dataikuapi.fm.instances.FMAWSInstanceCreator` + """ + self.data["awsRootVolumeSizeGB"] = aws_root_volume_size + self.data["awsRootVolumeType"] = aws_root_volume_type + self.data["awsRootVolumeIOPS"] = aws_root_volume_IOPS + return self + + def create(self): + """ + Create the DSS instance + + :return: Created DSS Instance + :rtype: :class:`dataikuapi.fm.instances.FMAWSInstance` + """ + instance = self.client._perform_tenant_json( + "POST", "/instances", body=self.data + ) + return FMAWSInstance(self.client, instance) + + +class FMAzureInstanceCreator(FMInstanceCreator): + def create(self): + """ + Create the DSS instance + + :return: Created DSS Instance + :rtype: :class:`dataikuapi.fm.instances.FMAzureInstance` + """ + instance = self.client._perform_tenant_json( + "POST", "/instances", body=self.data + ) + return FMAzureInstance(self.client, instance) + + +class FMInstance(object): + """ + A handle to interact with a DSS instance. + Do not create this directly, use :meth:`FMClient.get_instance` or :meth: `FMClient.new_instance_creator` + """ + + def __init__(self, client, instance_data): + self.client = client + self.instance_data = instance_data + self.id = instance_data["id"] + + def reprovision(self): + """ + Reprovision the physical DSS instance + + :return: A :class:`~dataikuapi.fm.future.FMFuture` representing the reprovision process + :rtype: :class:`~dataikuapi.fm.future.FMFuture` + """ + future = self.client._perform_tenant_json( + "GET", "/instances/%s/actions/reprovision" % self.id + ) + return FMFuture.from_resp(self.client, future) + + def deprovision(self): + """ + Deprovision the physical DSS instance + + :return: A :class:`~dataikuapi.fm.future.FMFuture` representing the deprovision process + :rtype: :class:`~dataikuapi.fm.future.FMFuture` + """ + future = self.client._perform_tenant_json( + "GET", "/instances/%s/actions/deprovision" % self.id + ) + return FMFuture.from_resp(self.client, future) + + def restart_dss(self): + """ + Restart the DSS running on the physical instance + + :return: A :class:`~dataikuapi.fm.future.FMFuture` representing the restart process + :rtype: :class:`~dataikuapi.fm.future.FMFuture` + """ + future = self.client._perform_tenant_json( + "GET", "/instances/%s/actions/restart-dss" % self.id + ) + return FMFuture.from_resp(self.client, future) + + def save(self): + """ + Update the Instance. + """ + self.client._perform_tenant_empty( + "PUT", "/instances/%s" % self.id, body=self.instance_data + ) + self.instance_data = self.client._perform_tenant_json( + "GET", "/instances/%s" % self.id + ) + + def get_status(self): + """ + Get the physical DSS instance's status + """ + status = self.client._perform_tenant_json( + "GET", "/instances/%s/status" % self.id + ) + return FMInstanceStatus(status) + + def delete(self): + """ + Delete the DSS instance + + :return: A :class:`~dataikuapi.fm.future.FMFuture` representing the deletion process + :rtype: :class:`~dataikuapi.fm.future.FMFuture` + """ + future = self.client._perform_tenant_json( + "GET", "/instances/%s/actions/delete" % self.id + ) + return FMFuture.from_resp(self.client, future) + + def set_automated_snapshots(self, enable, period, keep=0): + """ + Set the automated snapshots policy for this instance + + :param boolean enable: Enable the automated snapshots + :param int period: The time period between 2 snapshot in hours + :param int keep: Optional, the number of snapshot to keep. Use 0 to keep all snapshots. Defaults to 0. + """ + self.instance_data["enableAutomatedSnapshot"] = enable + self.instance_data["automatedSnapshotPeriod"] = period + self.instance_data["automatedSnapshotRetention"] = keep + return self + + def set_custom_certificate(self, pem_data): + """ + Set the custom certificate for this instance + + Only needed when Virtual Network HTTPS Strategy is set to Custom Certificate + + param: str pem_data: The SSL certificate + """ + self.instance_data["sslCertificatePEM"] = pem_data + return self + + +class FMAWSInstance(FMInstance): + def set_elastic_ip(self, enable, elasticip_allocation_id): + """ + Set a public elastic ip for this instance + + :param boolan enable: Enable the elastic ip allocation + :param str elaticip_allocation_id: AWS ElasticIP allocation ID + """ + self.instance_data["awsAssignElasticIP"] = enable + self.instance_data["awsElasticIPAllocationId"] = elasticip_allocation_id + return self + + +class FMAzureInstance(FMInstance): + def set_elastic_ip(self, enable, public_ip_id): + """ + Set a public elastic ip for this instance + + :param boolan enable: Enable the elastic ip allocation + :param str public_ip_id: Azure Public IP ID + """ + self.instance_data["azureAssignElasticIP"] = enable + self.instance_data["azurePublicIPId"] = public_ip_id + return self + + +class FMInstanceEncryptionMode(Enum): + NONE = "NONE" + DEFAULT_KEY = "DEFAULT_KEY" + TENANT = "TENANT" + CUSTOM = "CUSTOM" + + +class FMInstanceStatus(dict): + """A class holding read-only information about an Instance. + This class should not be created directly. Instead, use :meth:`FMInstance.get_info` + """ + + def __init__(self, data): + """Do not call this directly, use :meth:`FMInstance.get_status`""" + super(FMInstanceStatus, self).__init__(data) diff --git a/dataikuapi/fm/instancesettingstemplates.py b/dataikuapi/fm/instancesettingstemplates.py new file mode 100644 index 00000000..1ca123f0 --- /dev/null +++ b/dataikuapi/fm/instancesettingstemplates.py @@ -0,0 +1,363 @@ +from enum import Enum +import json +from dataikuapi.fm.future import FMFuture + + +class FMInstanceSettingsTemplateCreator(object): + def __init__(self, client, label): + """ + A builder class to create an Instance Settings Template + + :param str label: The label of the Virtual Network + """ + + self.data = {} + self.data["label"] = label + self.client = client + + def create(self): + """ + Create the Instance Settings Template + + :return: Created InstanceSettingsTemplate + :rtype: :class:`dataikuapi.fm.instancesettingstemplates.FMInstanceSettingsTemplate` + """ + + template = self.client._perform_tenant_json( + "POST", "/instance-settings-templates", body=self.data + ) + return FMInstanceSettingsTemplate(self.client, template) + + def with_setup_actions(self, setup_actions): + """ + Add setup actions + + :param list setup_actions: List of :class:`dataikuapi.fm.instancesettingstemplates.FMSetupAction` to be played on an instance + :rtype: :class:`dataikuapi.fm.instancesettingstemplates.FMInstanceSettingsTemplateCreator` + """ + self.data["setupActions"] = setup_actions + return self + + def with_license(self, license_file_path=None, license_string=None): + """ + Override global license + + :param str license_file_path: Optional, load the license from a json file + :param str license_string: Optional, load the license from a json string + """ + if license_file_path is not None: + with open(license_file_path) as json_file: + license = json.load(json_file) + elif license_string is not None: + license = json.loads(license_string) + else: + raise ValueError( + "a valid license_file_path or license_string needs to be provided" + ) + self.data["license"] = json.dumps(license, indent=2) + return self + + +class FMAWSInstanceSettingsTemplateCreator(FMInstanceSettingsTemplateCreator): + def with_aws_keypair(self, aws_keypair_name): + """ + Add an AWS Keypair to the DSS instance. + Needed to get SSH access to the DSS instance, using the centos user. + + :param str aws_keypair_name: Name of an AWS key pair to add to the instance. + """ + self.data["awsKeyPairName"] = aws_keypair_name + return self + + def with_startup_instance_profile(self, startup_instance_profile_arn): + """ + Add a Instance Profile to be assign to the DSS instance on startup + + :param str startup_instance_profile_arn: ARN of the Instance profile assigned to the DSS instance at startup time + """ + self.data["startupInstanceProfileArn"] = startup_instance_profile_arn + return self + + def with_runtime_instance_profile(self, runtime_instance_profile_arn): + """ + Add a Instance Profile to be assign to the DSS instance when running + + :param str runtime_instance_profile_arn: ARN of the Instance profile assigned to the DSS instance during runtime + """ + self.data["runtimeInstanceProfileArn"] = runtime_instance_profile_arn + return self + + def with_restrict_aws_metadata_server_access( + self, restrict_aws_metadata_server_access=True + ): + """ + Restrict AWS metadata server access on the DSS instance. + + :param boolean restrict_aws_metadata_server_access: Optional, If true, restrict the access to the metadata server access. Defaults to true + """ + self.data[ + "restrictAwsMetadataServerAccess" + ] = restrict_aws_metadata_server_access + return self + + def with_default_aws_api_access_mode(self): + """ + The DSS Instance will use the Runtime Instance Profile to access AWS API. + """ + self.data["dataikuAwsAPIAccessMode"] = "NONE" + return self + + def with_keypair_aws_api_access_mode( + self, + aws_access_key_id, + aws_keypair_storage_mode="NONE", + aws_secret_access_key=None, + aws_secret_access_key_aws_secret_name=None, + aws_secrets_manager_region=None, + ): + """ + DSS Instance will use an Access Key to authenticate against the AWS API. + + :param str aws_access_key_id: AWS Access Key ID. + :param str aws_keypair_storage_mode: Optional, the storage mode of the AWS api key. Accepts "NONE", "INLINE_ENCRYPTED" or "AWS_SECRETS_MANAGER". Defaults to "NONE" + :param str aws_secret_access_key: Optional, AWS Access Key Secret. Only needed if keypair_storage_mode is "INLINE_ENCRYPTED" + :param str aws_secret_access_key_aws_secret_name: Optional, ASM secret name. Only needed if aws_keypair_storage_mode is "AWS_SECRET_MANAGER" + :param str aws_secrets_manager_region: Optional, Secret Manager region to use. Only needed if aws_keypair_storage_mode is "AWS_SECRET_MANAGER" + """ + if aws_keypair_storage_mode not in [ + "NONE", + "INLINE_ENCRYPTED", + "AWS_SECRETS_MANAGER", + ]: + raise ValueError( + 'aws_keypair_storage_mode should be either "NONE", "INLINE_ENCRYPTED" or "AWS_SECRET_MANAGER"' + ) + + self.data["dataikuAwsAPIAccessMode"] = "KEYPAIR" + self.data["dataikuAwsKeypairStorageMode"] = aws_keypair_storage_mode + + if aws_keypair_storage_mode == "NONE": + return self + + self.data["dataikuAwsAccessKeyId"] = aws_access_key_id + + if aws_keypair_storage_mode == "INLINE_ENCRYPTED": + if aws_secret_access_key == None: + raise ValueError( + 'When aws_keypair_storage_mode is "INLINE_ENCRYPTED", aws_secret_access_key should be provided' + ) + self.data["dataikuAwsSecretAccessKey"] = aws_secret_access_key + elif aws_keypair_storage_mode == "AWS_SECRETS_MANAGER": + if aws_secret_access_key_aws_secret_name == None: + raise ValueError( + 'When aws_keypair_storage_mode is "AWS_SECRETS_MANAGER", aws_secret_access_key_aws_secret_name should be provided' + ) + self.data[ + "dataikuAwsSecretAccessKeyAwsSecretName" + ] = aws_secret_access_key_aws_secret_name + self.data["awsSecretsManagerRegion"] = aws_secrets_manager_region + + return self + + +class FMAzureInstanceSettingsTemplateCreator(FMInstanceSettingsTemplateCreator): + def with_ssh_key(self, ssh_public_key): + """ + Add an SSH public key to the DSS Instance. + Needed to access it through SSH, using the centos user. + + :param str ssh_public_key: The content of the public key to add to the instance. + """ + self.data["azureSshKey"] = ssh_public_key + return self + + def with_startup_managed_identity(self, startup_managed_identity): + """ + Add a managed identity to be assign to the DSS instance on startup + + :param str startup_managed_identity: Managed Identity ID + """ + self.data["startupManagedIdentity"] = startup_managed_identity + return self + + def with_runtime_managed_identity(self, runtime_managed_identity): + """ + Add a managed identity to be assign to the DSS instance when running + + :param str runtime_managed_identity: Managed Identity ID + """ + self.data["runtimeManagedIdentity"] = runtime_managed_identity + return self + + +class FMInstanceSettingsTemplate(object): + def __init__(self, client, ist_data): + self.client = client + self.id = ist_data["id"] + self.ist_data = ist_data + + def save(self): + """ + Update the Instance Settings Template. + """ + self.client._perform_tenant_empty( + "PUT", "/instance-settings-templates/%s" % self.id, body=self.ist_data + ) + self.ist_data = self.client._perform_tenant_json( + "GET", "/instance-settings-templates/%s" % self.id + ) + + def delete(self): + """ + Delete the DSS Instance Settings Template. + + :return: A :class:`~dataikuapi.fm.future.FMFuture` representing the deletion process + :rtype: :class:`~dataikuapi.fm.future.FMFuture` + """ + future = self.client._perform_tenant_json( + "DELETE", "/instance-settings-templates/%s" % self.id + ) + return FMFuture.from_resp(self.client, future) + + def add_setup_action(self, setup_action): + """ + Add a setup_action + + :param object setup_action: a :class:`dataikuapi.fm.instancesettingstemplates.FMSetupAction` + """ + self.ist_data["setupActions"].append(setup_action) + return self + + +class FMSetupAction(dict): + def __init__(self, setupActionType, params=None): + """ + A class representing a SetupAction + + Do not create this directly, use: + - :meth:`dataikuapi.fm.instancesettingstemplates.FMSetupAction.add_authorized_key` + + """ + data = { + "type": setupActionType.value, + } + if params: + data["params"] = params + + super(FMSetupAction, self).__init__(data) + + @staticmethod + def add_authorized_key(ssh_key): + """ + Return a ADD_AUTHORIZED_KEY FMSetupAction + """ + return FMSetupAction(FMSetupActionType.ADD_AUTHORIZED_KEY, {"sshKey": ssh_key}) + + @staticmethod + def run_ansible_task(stage, yaml_string): + """ + Return a RUN_ANSIBLE_TASK FMSetupAction + + :param object stage: a :class:`dataikuapi.fm.instancesettingstemplates.FMSetupActionStage` + :param str yaml_string: a yaml encoded string defining the ansibles tasks to run + """ + return FMSetupAction( + FMSetupActionType.RUN_ANSIBLE_TASKS, + {"stage": stage.value, "ansibleTasks": yaml_string}, + ) + + @staticmethod + def install_system_packages(packages): + """ + Return an INSTALL_SYSTEM_PACKAGES FMSetupAction + + :param list packages: List of packages to install + """ + return FMSetupAction( + FMSetupActionType.INSTALL_SYSTEM_PACKAGES, {"packages": packages} + ) + + @staticmethod + def setup_advanced_security(basic_headers=True, hsts=False): + """ + Return an SETUP_ADVANCED_SECURITY FMSetupAction + + :param boolean basic_headers: Optional, Prevent browsers to render Web content served by DSS to be embedded into a frame, iframe, embed or object tag. Defaults to True + :param boolean hsts: Optional, Enforce HTTP Strict Transport Security. Defaults to False + """ + return FMSetupAction( + FMSetupActionType.SETUP_ADVANCED_SECURITY, + {"basic_headers": basic_headers, "hsts": hsts}, + ) + + @staticmethod + def install_jdbc_driver( + database_type, + url, + paths_in_archive=None, + http_headers=None, + http_username=None, + http_password=None, + datadir_subdirectory=None, + ): + """ + Return a INSTALL_JDBC_DRIVER FMSetupAction + + :param object database_type: a :class:`dataikuapi.fm.instancesettingstemplates.FMSetupActionAddJDBCDriverDatabaseType` + :param str url: The full address to the driver. Supports http(s)://, s3://, abs:// or file:// endpoints + :param list paths_in_archive: Optional, must be used when the driver is shipped as a tarball or a ZIP file. Add here all the paths to find the JAR files in the driver archive. Paths are relative to the top of the archive. Wildcards are supported. + :param dict http_headers: Optional, If you download the driver from a HTTP(S) endpoint, add here the headers you want to add to the query. This setting is ignored for any other type of download. + :param str http_username: Optional, If the HTTP(S) endpoint expect a Basic Authentication, add here the username. To explicitely specify which Assigned Identity use if the machine have several, set the client_id here. To authenticate with a SAS Token on Azure Blob Storage (not recommended), use "token" as the value here. + :param str http_password: Optional, If the HTTP(S) endpoint expect a Basic Authentication, add here the password. To authenticate with a SAS Token on Azure Blob Storage (not recommended), store the token in this field. + :param str datadir_subdirectory: Optional, Some drivers are shipped with a high number of JAR files along with them. In that case, you might want to install them under an additional level in the DSS data directory. Set the name of this subdirectory here. Not required for most drivers. + """ + return FMSetupAction( + FMSetupActionType.INSTALL_JDBC_DRIVER, + { + "url": url, + "dbType": database_type.value, + "pathsInArchive": paths_in_archive, + "headers": http_headers, + "username": http_username, + "password": http_password, + "subpathInDatadir": datadir_subdirectory, + }, + ) + + @staticmethod + def setup_k8s_and_spark(): + """ + Return a SETUP_K8S_AND_SPARK FMSetupAction + """ + return FMSetupAction(FMSetupActionType.SETUP_K8S_AND_SPARK) + + +class FMSetupActionType(Enum): + RUN_ANSIBLE_TASKS = "RUN_ANSIBLE_TASKS" + INSTALL_SYSTEM_PACKAGES = "INSTALL_SYSTEM_PACKAGES" + INSTALL_DSS_PLUGINS_FROM_STORE = "INSTALL_DSS_PLUGINS_FROM_STORE" + SETUP_K8S_AND_SPARK = "SETUP_K8S_AND_SPARK" + SETUP_RUNTIME_DATABASE = "SETUP_RUNTIME_DATABASE" + SETUP_MUS_AUTOCREATE = "SETUP_MUS_AUTOCREATE" + SETUP_UI_CUSTOMIZATION = "SETUP_UI_CUSTOMIZATION" + SETUP_MEMORY_SETTINGS = "SETUP_MEMORY_SETTINGS" + SETUP_GRAPHICS_EXPORT = "SETUP_GRAPHICS_EXPORT" + ADD_AUTHORIZED_KEY = "ADD_AUTHORIZED_KEY" + INSTALL_JDBC_DRIVER = "INSTALL_JDBC_DRIVER" + SETUP_ADVANCED_SECURITY = "SETUP_ADVANCED_SECURITY" + + +class FMSetupActionStage(Enum): + after_dss_startup = "after_dss_startup" + after_install = "after_install" + before_install = "before_install" + + +class FMSetupActionAddJDBCDriverDatabaseType(Enum): + mysql = "mysql" + mssql = "mssql" + oracle = "oracle" + mariadb = "mariadb" + snowflake = "snowflake" + athena = "athena" + bigquery = "bigquery" diff --git a/dataikuapi/fm/tenant.py b/dataikuapi/fm/tenant.py new file mode 100644 index 00000000..80e45d95 --- /dev/null +++ b/dataikuapi/fm/tenant.py @@ -0,0 +1,148 @@ +import json + + +class FMCloudCredentials(object): + """ + A Tenant Cloud Credentials in the FM instance + """ + + def __init__(self, client, cloud_credentials): + self.client = client + self.cloud_credentials = cloud_credentials + + def set_cmk_key(self, cmk_key_id): + self.cloud_credentials["awsCMKId"] = cmk_key_id + return self + + def set_static_license(self, license_file_path=None, license_string=None): + """ + Set a default static license for the DSS instances + + :param str license_file_path: Optional, load the license from a json file + :param str license_string: Optional, load the license from a json string + """ + if license_file_path is not None: + with open(license_file_path) as json_file: + license = json.load(json_file) + elif license_string is not None: + license = json.loads(license_string) + else: + raise ValueError( + "a valid license_file_path or license_string needs to be provided" + ) + self.cloud_credentials["licenseMode"] = "STATIC" + self.cloud_credentials["license"] = json.dumps(license, indent=2) + return self + + def set_automatically_updated_license(self, license_token): + """ + Set an automatically updated license for the DSS instances + + :param str license_token: License token + """ + if license_token is None: + raise ValueError("a valid license_token needs to be provided") + self.cloud_credentials["licenseMode"] = "AUTO_UPDATE" + self.cloud_credentials["licenseToken"] = license_token + return self + + def set_authentication(self, authentication): + """ + Set the authentication for the tenant + + :param object: a :class:`dataikuapi.fm.tenant.FMCloudAuthentication` + """ + self.cloud_credentials.update(authentication) + return self + + def save(self): + """Saves back the settings to the project""" + + self.client._perform_tenant_empty( + "PUT", "/cloud-credentials", body=self.cloud_credentials + ) + + +class FMCloudTags(object): + """ + A Tenant Cloud Tags in the FM instance + """ + + def __init__(self, client, cloud_tags): + self.client = client + self.cloud_tags = json.loads(cloud_tags["msg"]) + + @property + def tags(self): + return self.cloud_tags + + def save(self): + """Saves the tags on FM""" + + self.client._perform_tenant_empty("PUT", "/cloud-tags", body=self.cloud_tags) + + +class FMCloudAuthentication(dict): + def __init__(self, data): + """ + A class holding the Cloud Authentication information + + Do not create this directly, use: + - :meth:`dataikuapi.fm.tenant.FMCloudAuthentication.aws_same_as_fm` to use the same authentication as Fleet Manager + - :meth:`dataikuapi.fm.tenant.FMCloudAuthentication.aws_iam_role` to use a custom IAM Role + - :meth:`dataikuapi.fm.tenant.FMCloudAuthentication.aws_keypair` to use a AWS Access key ID and AWS Secret Access Key pair + """ + super(FMCloudAuthentication, self).__init__(data) + + @staticmethod + def aws_same_as_fm(): + """ + AWS Only: Use the same authentication as Fleet Manager + """ + return FMCloudAuthentication({"awsAuthenticationMode": "SAME_AS_FM"}) + + @staticmethod + def aws_iam_role(role_arn): + """ + AWS Only: Use an IAM Role + + params: str role_arn: ARN of the IAM Role + """ + return FMCloudAuthentication( + {"awsAuthenticationMode": "IAM_ROLE", "awsIAMRoleARN": role_arn} + ) + + @staticmethod + def aws_keypair(access_key_id, secret_access_key): + """ + AWS Only: Use an AWS Access Key + + :param str access_key_id: AWS Access Key ID + :param str secret_access_key: AWS Secret Access Key + """ + return FMCloudAuthentication( + { + "awsAuthenticationMode": "KEYPAIR", + "awsAccessKeyId": access_key_id, + "awsSecretAccessKey": secret_access_key, + } + ) + + @staticmethod + def azure(subscription, tenant_id, environment, client_id): + """ + Azure Only + + :param str subscription: Azure Subscription + :param str tenant_id: Azure Tenant Id + :param str environment: Azure Environment + :param str client_id: Azure Client Id + """ + data = { + "azureSubscription": subscription, + "azureTenantId": tenant_id, + "azureEnvironment": environment, + "azureFMAppClientId": client_id, + } + + return FMCloudAuthentication(data) diff --git a/dataikuapi/fm/virtualnetworks.py b/dataikuapi/fm/virtualnetworks.py new file mode 100644 index 00000000..35aae048 --- /dev/null +++ b/dataikuapi/fm/virtualnetworks.py @@ -0,0 +1,257 @@ +from dataikuapi.fm.future import FMFuture + + +class FMVirtualNetworkCreator(object): + def __init__(self, client, label): + """ + A builder class to create a Virtual Network + + :param str label: The label of the Virtual Network + """ + self.client = client + self.data = {} + self.data["label"] = label + self.data["mode"] = "EXISTING_MONOTENANT" + + def with_internet_access_mode(self, internet_access_mode): + """ + :param str internet_access_mode: The internet access mode of the instances created in this virtual network. Accepts "YES", "NO", "EGRESS_ONLY". Defaults to "YES" + """ + if internet_access_mode not in ["YES", "NO", "EGRESS_ONLY"]: + raise ValueError( + 'internet_access_mode should be either "YES", "NO", or "EGRESS_ONLY"' + ) + + self.data["internetAccessMode"] = internet_access_mode + return self + + +class FMAWSVirtualNetworkCreator(FMVirtualNetworkCreator): + def with_vpc(self, aws_vpc_id, aws_subnet_id): + """ + Setup the VPC and Subnet to with the VirtualNetwork + + :param str aws_vpc_id: ID of the VPC to use + :param str aws_subnet_id: ID of the subnet to use + """ + self.data["awsVpcId"] = aws_vpc_id + self.data["awsSubnetId"] = aws_subnet_id + return self + + def with_auto_create_security_groups(self): + """ + Automatically create the AWS Security Groups when creating this VirtualNetwork + """ + self.data["awsAutoCreateSecurityGroups"] = True + return self + + def with_aws_security_groups(self, *aws_security_groups): + """ + Use pre-created AWS Security Groups + + :param str *aws_security_groups: Up to 5 security group ids to assign to the instances created in this virtual network. + """ + self.data["awsAutoCreateSecurityGroups"] = False + self.data["awsSecurityGroups"] = aws_security_groups + return self + + def create(self): + """ + Create the VirtualNetwork + + :return: Created VirtualNetwork + :rtype: :class:`dataikuapi.fm.virtualnetworks.FMAWSVirtualNetwork` + """ + vn = self.client._perform_tenant_json( + "POST", "/virtual-networks", body=self.data + ) + return FMAWSVirtualNetwork(self.client, vn) + + +class FMAzureVirtualNetworkCreator(FMVirtualNetworkCreator): + def with_azure_virtual_network(self, azure_vn_id, azure_subnet_id): + """ + Setup the Azure Virtual Network and Subnet to with the VirtualNetwork + + :param str azure_vn_id: Resource ID of the Azure Virtual Network to use + :param str azure_subnet_id: Resource ID of the subnet to use + """ + self.data["azureVnId"] = azure_vn_id + self.data["azureSubnetId"] = azure_subnet_id + return self + + def with_auto_update_security_groups(self, auto_update_security_groups=True): + """ + Auto update the security groups of the Azure Virtual Network + + :param boolean auto_update_security_groups: Optional, Auto update the subnet security group. Defaults to True + """ + self.data["azureAutoUpdateSecurityGroups"] = auto_update_security_groups + return self + + def create(self): + """ + Create the VirtualNetwork + + :return: Created VirtualNetwork + :rtype: :class:`dataikuapi.fm.virtualnetworks.FMAzureVirtualNetwork` + """ + vn = self.client._perform_tenant_json( + "POST", "/virtual-networks", body=self.data + ) + return FMAzureVirtualNetwork(self.client, vn) + + +class FMVirtualNetwork(object): + def __init__(self, client, vn_data): + self.client = client + self.vn_data = vn_data + self.id = self.vn_data["id"] + + def save(self): + """ + Update the Virtual Network. + """ + self.client._perform_tenant_empty( + "PUT", "/virtual-networks/%s" % self.id, body=self.vn_data + ) + self.vn_data = self.client._perform_tenant_json( + "GET", "/virtual-networks/%s" % self.id + ) + + def delete(self): + """ + Delete the DSS Instance Settings Template. + + :return: A :class:`~dataikuapi.fm.future.FMFuture` representing the deletion process + :rtype: :class:`~dataikuapi.fm.future.FMFuture` + """ + future = self.client._perform_tenant_json( + "DELETE", "/virtual-networks/%s" % self.id + ) + return FMFuture.from_resp(self.client, future) + + def set_fleet_management( + self, enable, event_server=None, deployer_management="NO_MANAGED_DEPLOYER" + ): + """ + When enabled, all instances in this virtual network know each other and can centrally manage deployer and logs centralization + + :param boolean enable: Enable or not the Fleet Management + + :param str event_server: Optional, Node name of the node that should act as the centralized event server for logs concentration. Audit logs of all design, deployer and automation nodes will automatically be sent there. + :param str deployer_management: Optional, Accepts: + - "NO_MANAGED_DEPLOYER": Do not manage the the deployer. This is the default mode. + - "CENTRAL_DEPLOYER": Central deployer. Recommanded if you have more than one design node or may have more than one design node in the future. + - "EACH_DESIGN_NODE": Deployer from design. Recommanded if you have a single design node and want a simpler setup. + """ + + self.vn_data["managedNodesDirectory"] = enable + self.vn_data["eventServerNodeLabel"] = event_server + self.vn_data["nodesDirectoryDeployerMode"] = deployer_management + return self + + def set_https_strategy(self, https_strategy): + """ + Set the HTTPS strategy for this virtual network + + :param object: a :class:`dataikuapi.fm.virtualnetworks.FMHTTPSStrategy` + """ + self.vn_data.update(https_strategy) + return self + + +class FMAWSVirtualNetwork(FMVirtualNetwork): + def set_dns_strategy( + self, + assign_domain_name, + aws_private_ip_zone53_id=None, + aws_public_ip_zone53_id=None, + ): + """ + Set the DNS strategy for this virtual network + + :param boolean assign_domain_name: If false, don't assign domain names, use ip_only + :param str aws_private_ip_zone53_id: Optional, AWS Only, the ID of the AWS Route53 Zone to use for private ip + :param str aws_public_ip_zone53_id: Optional, AWS Only, the ID of the AWS Route53 Zone to use for public ip + """ + + if assign_domain_name: + self.vn_data["dnsStrategy"] = "VN_SPECIFIC_CLOUD_DNS_SERVICE" + self.vn_data["awsRoute53PrivateIPZoneId"] = aws_private_ip_zone53_id + self.vn_data["awsRoute53PublicIPZoneId"] = aws_public_ip_zone53_id + else: + self.vn_data["dnsStrategy"] = "NONE" + + return self + + +class FMAzureVirtualNetwork(FMVirtualNetwork): + def set_dns_strategy(self, assign_domain_name, azure_dns_zone_id=None): + """ + Set the DNS strategy for this virtual network + + :param boolean assign_domain_name: If false, don't assign domain names, use ip_only + :param str azure_dns_zone_id: Optional, Azure Only, the ID of the Azure DNS zone to use + """ + + if assign_domain_name: + self.vn_data["dnsStrategy"] = "VN_SPECIFIC_CLOUD_DNS_SERVICE" + self.vn_data["azureDnsZoneId"] = azure_dns_zone_id + else: + self.vn_data["dnsStrategy"] = "NONE" + + return self + + +class FMHTTPSStrategy(dict): + def __init__(self, data, https_strategy, http_redirect=False): + """ + A class holding HTTPS Strategy for Virtual Network + + Do not create this directly, use: + - :meth:`dataikuapi.fm.virtualnetwork.FMHTTPSStrategy.disable` to use HTTP only + - :meth:`dataikuapi.fm.virtualnetwork.FMHTTPSStrategy.self_signed` to use self-signed certificates + - :meth:`dataikuapi.fm.virtualnetwork.FMHTTPSStrategy.custom_cert` to use custom certificates + - :meth:`dataikuapi.fm.virtualnetwork.FMHTTPSStrategy.lets_encrypt` to use Let's Encrypt + """ + super(FMHTTPSStrategy, self).__init__(data) + self["httpsStrategy"] = https_strategy + if http_redirect: + self["httpStrategy"] = "REDIRECT" + else: + self["httpStrategy"] = "DISABLE" + + @staticmethod + def disable(): + """ + Use HTTP only + """ + return FMHTTPSStrategy({}, "NONE", False) + + @staticmethod + def self_signed(http_redirect): + """ + Use self-signed certificates + + :param bool http_redirect: If true, HTTP is redirected to HTTPS. If false, HTTP is disabled. Defaults to false + """ + return FMHTTPSStrategy({}, "SELF_SIGNED", http_redirect) + + @staticmethod + def custom_cert(http_redirect): + """ + Use a custom certificate for each instance + + :param bool http_redirect: If true, HTTP is redirected to HTTPS. If false, HTTP is disabled. Defaults to false + """ + return FMHTTPSStrategy({}, "CUSTOM_CERTIFICATE", http_redirect) + + @staticmethod + def lets_encrypt(contact_mail): + """ + Use Let's Encrypt to generate https certificates + + :param str contact_mail: The contact email provided to Let's Encrypt + """ + return FMHTTPSStrategy({"contactMail": contact_mail}, "LETSENCRYPT", True) diff --git a/dataikuapi/fmclient.py b/dataikuapi/fmclient.py new file mode 100644 index 00000000..1b8bc2db --- /dev/null +++ b/dataikuapi/fmclient.py @@ -0,0 +1,395 @@ +import json +from requests import Session +from requests import exceptions +from requests.auth import HTTPBasicAuth +import os.path as osp + +from enum import Enum +from .utils import DataikuException + +from .fm.tenant import FMCloudCredentials, FMCloudTags +from .fm.virtualnetworks import ( + FMVirtualNetwork, + FMAWSVirtualNetworkCreator, + FMAzureVirtualNetworkCreator, + FMAWSVirtualNetwork, + FMAzureVirtualNetwork, +) +from .fm.instances import ( + FMInstance, + FMInstanceEncryptionMode, + FMAWSInstanceCreator, + FMAzureInstanceCreator, + FMAWSInstance, + FMAzureInstance, +) +from .fm.instancesettingstemplates import ( + FMInstanceSettingsTemplate, + FMAWSInstanceSettingsTemplateCreator, + FMAzureInstanceSettingsTemplateCreator, +) + + +class FMClient(object): + def __init__( + self, + host, + api_key_id, + api_key_secret, + tenant_id="main", + extra_headers=None, + ): + """ + Base class for the different FM Clients + Do not create this class, instead use :class:`dataikuapi.FMClientAWS` or :class:`dataikuapi.FMClientAzure` + """ + if self.cloud == None: + raise NotImplementedError( + "Do not use FMClient directly, instead use FMClientAWS or FMClientAzure" + ) + self.api_key_id = api_key_id + self.api_key_secret = api_key_secret + self.host = host + self.__tenant_id = tenant_id + self._session = Session() + + if self.api_key_id is not None and self.api_key_secret is not None: + self._session.auth = HTTPBasicAuth(self.api_key_id, self.api_key_secret) + else: + raise ValueError("API Key ID and API Key secret are required") + + if extra_headers is not None: + self._session.headers.update(extra_headers) + + ######################################################## + # Tenant + ######################################################## + + def get_cloud_credentials(self): + """ + Get Cloud Credentials + + :return: Cloud credentials + :rtype: :class:`dataikuapi.fm.tenant.FMCloudCredentials` + """ + creds = self._perform_tenant_json("GET", "/cloud-credentials") + return FMCloudCredentials(self, creds) + + def get_cloud_tags(self): + """ + Get Tenant's Cloud Tags + + :param string tenant_id + + :return: tenant's cloud tags + :rtype: :class:`dataikuapi.fm.tenant.FMCloudTags` + """ + tags = self._perform_tenant_json("GET", "/cloud-tags") + return FMCloudTags(self, tags) + + ######################################################## + # VirtualNetwork + ######################################################## + + def _make_virtual_network(self, vn): + if self.cloud == "AWS": + return FMAWSVirtualNetwork(self, vn) + elif self.cloud == "Azure": + return FMAzureVirtualNetwork(self, vn) + else: + raise Exception("Unknown cloud type %s" % self.cloud) + + def list_virtual_networks(self): + """ + List all Virtual Networks + + :return: list of virtual networks + :rtype: list of :class:`dataikuapi.fm.virtualnetworks.FMVirtualNetwork` + """ + vns = self._perform_tenant_json("GET", "/virtual-networks") + return [self._make_virtual_network(x) for x in vns] + + def get_virtual_network(self, virtual_network_id): + """ + Get a Virtual Network + + :param str virtual_network_id + + :return: requested virtual network + :rtype: :class:`dataikuapi.fm.virtualnetworks.FMVirtualNetwork` + """ + vn = self._perform_tenant_json( + "GET", "/virtual-networks/%s" % virtual_network_id + ) + return self._make_virtual_network(vn) + + ######################################################## + # Instance settings template + ######################################################## + + def list_instance_templates(self): + """ + List all Instance Settings Templates + + :return: list of instance settings template + :rtype: list of :class:`dataikuapi.fm.tenant.FMInstanceSettingsTemplate` + """ + templates = self._perform_tenant_json("GET", "/instance-settings-templates") + return [FMInstanceSettingsTemplate(self, x) for x in templates] + + def get_instance_template(self, template_id): + """ + Get an Instance Template + + :param str template_id + + :return: requested instance settings template + :rtype: :class:`dataikuapi.fm.instancesettingstemplates.FMInstanceSettingsTemplate` + """ + template = self._perform_tenant_json( + "GET", "/instance-settings-templates/%s" % template_id + ) + return FMInstanceSettingsTemplate(self, template) + + ######################################################## + # Instance + ######################################################## + + def _make_instance(self, i): + if self.cloud == "AWS": + return FMAWSInstance(self, i) + elif self.cloud == "Azure": + return FMAzureInstance(self, i) + else: + raise Exception("Unknown cloud type %s" % self.cloud) + + def list_instances(self): + """ + List all DSS Instances + + :return: list of instances + :rtype: list of :class:`dataikuapi.fm.instances.FMInstance` + """ + instances = self._perform_tenant_json("GET", "/instances") + return [self._make_instance(x) for x in instances] + + def get_instance(self, instance_id): + """ + Get a DSS Instance + + :param str instance_id + + :return: Instance + :rtype: :class:`dataikuapi.fm.instances.FMInstance` + """ + instance = self._perform_tenant_json("GET", "/instances/%s" % instance_id) + return self._make_instance(instance) + + ######################################################## + # Internal Request handling + ######################################################## + + def _perform_http( + self, + method, + path, + params=None, + body=None, + stream=False, + files=None, + raw_body=None, + ): + if body is not None: + body = json.dumps(body) + if raw_body is not None: + body = raw_body + try: + http_res = self._session.request( + method, + "%s/api/public%s" % (self.host, path), + params=params, + data=body, + files=files, + stream=stream, + ) + http_res.raise_for_status() + return http_res + except exceptions.HTTPError: + try: + ex = http_res.json() + except ValueError: + ex = {"message": http_res.text} + raise DataikuException( + "%s: %s" + % ( + ex.get("errorType", "Unknown error"), + ex.get("message", "No message"), + ) + ) + + def _perform_empty( + self, method, path, params=None, body=None, files=None, raw_body=None + ): + self._perform_http( + method, + path, + params=params, + body=body, + files=files, + stream=False, + raw_body=raw_body, + ) + + def _perform_json( + self, method, path, params=None, body=None, files=None, raw_body=None + ): + return self._perform_http( + method, + path, + params=params, + body=body, + files=files, + stream=False, + raw_body=raw_body, + ).json() + + def _perform_tenant_json( + self, method, path, params=None, body=None, files=None, raw_body=None + ): + return self._perform_json( + method, + "/tenants/%s%s" % (self.__tenant_id, path), + params=params, + body=body, + files=files, + raw_body=raw_body, + ) + + def _perform_tenant_empty( + self, method, path, params=None, body=None, files=None, raw_body=None + ): + self._perform_empty( + method, + "/tenants/%s%s" % (self.__tenant_id, path), + params=params, + body=body, + files=files, + raw_body=raw_body, + ) + + +class FMClientAWS(FMClient): + def __init__( + self, + host, + api_key_id, + api_key_secret, + tenant_id="main", + extra_headers=None, + ): + """ + AWS Only - Instantiate a new FM API client on the given host with the given API key. + + API keys can be managed in FM on the project page or in the global settings. + + The API key will define which operations are allowed for the client. + + :param str host: Full url of the FM + + """ + self.cloud = "AWS" + super(FMClientAWS, self).__init__( + host, api_key_id, api_key_secret, tenant_id, extra_headers + ) + + def new_virtual_network_creator(self, label): + """ + Instantiate a new virtual network creator + + :param str label: The label of the + :rtype: :class:`dataikuapi.fm.virtualnetworks.FMAWSVirtualNetworkCreator` + """ + return FMAWSVirtualNetworkCreator(self, label) + + def new_instance_template_creator(self, label): + """ + Instantiate a new instance template creator + + :param str label: The label of the instance + :rtype: :class:`dataikuapi.fm.instancesettingstemplates.FMAWSInstanceSettingsTemplateCreator` + """ + return FMAWSInstanceSettingsTemplateCreator(self, label) + + def new_instance_creator( + self, label, instance_settings_template_id, virtual_network_id, image_id + ): + """ + Instantiate a new instance creator + + :param str label: The label of the instance + :param str instance_settings_template: The instance settings template id this instance should be based on + :param str virtual_network: The virtual network where the instance should be spawned + :param str image_id: The ID of the DSS runtime image (ex: dss-9.0.3-default) + :rtype: :class:`dataikuapi.fm.instances.FMAWSInstanceCreator` + """ + return FMAWSInstanceCreator( + self, label, instance_settings_template_id, virtual_network_id, image_id + ) + + +class FMClientAzure(FMClient): + def __init__( + self, + host, + api_key_id, + api_key_secret, + tenant_id="main", + extra_headers=None, + ): + """ + Azure Only - Instantiate a new FM API client on the given host with the given API key. + + API keys can be managed in FM on the project page or in the global settings. + + The API key will define which operations are allowed for the client. + + :param str host: Full url of the FM + """ + self.cloud = "Azure" + super(FMClientAWS, self).__init__( + host, api_key_id, api_key_secret, tenant_id, extra_headers + ) + + def new_virtual_network_creator(self, label): + """ + Instantiate a new virtual network creator + + :param str label: The label of the + :rtype: :class:`dataikuapi.fm.virtualnetworks.FMAzureVirtualNetworkCreator` + """ + return FMAzureVirtualNetworkCreator(self, label) + + def new_instance_template_creator(self, label): + """ + Instantiate a new instance template creator + + :param str label: The label of the instance + :rtype: :class:`dataikuapi.fm.instancesettingstemplates.FMAzureInstanceSettingsTemplateCreator` + """ + return FMAzureInstanceSettingsTemplateCreator(self, label) + + def new_instance_creator( + self, label, instance_settings_template_id, virtual_network_id, image_id + ): + """ + Instantiate a new instance creator + + :param str label: The label of the instance + :param str instance_settings_template: The instance settings template id this instance should be based on + :param str virtual_network: The virtual network where the instance should be spawned + :param str image_id: The ID of the DSS runtime image (ex: dss-9.0.3-default) + :rtype: :class:`dataikuapi.fm.instances.FMAzureInstanceCreator` + """ + return FMAzureInstanceCreator( + self, label, instance_settings_template_id, virtual_network_id, image_id + )