From 272f00819fdd415648902d1fbc4fd9d3205bbef9 Mon Sep 17 00:00:00 2001 From: antonio Date: Wed, 4 Sep 2024 09:45:20 +0200 Subject: [PATCH 1/5] Got absolute paths for infrastructuresList and deployed-template --- src/deploymentMenu.ts | 22 +++++++++++++++++++--- src/utils.ts | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/deploymentMenu.ts b/src/deploymentMenu.ts index 52df901..d4b69ab 100644 --- a/src/deploymentMenu.ts +++ b/src/deploymentMenu.ts @@ -3,7 +3,7 @@ import { ContentsManager } from '@jupyterlab/services'; import { KernelManager } from '@jupyterlab/services'; import { Widget } from '@lumino/widgets'; import { Dialog } from '@jupyterlab/apputils'; -import { executeKernelCommand, getIMClientPath } from './utils'; +import { executeKernelCommand, getIMClientPath, getInfrastructuresListPath, getDeployedTemplatePath } from './utils'; interface IDeployInfo { IMuser: string; @@ -100,7 +100,9 @@ const deployInfo: IDeployInfo = { let imageOptions: { uri: string; name: string }[] = []; let deploying = false; // Flag to prevent multiple deployments at the same time -let imClientPath: string | undefined = undefined; +let imClientPath: string; +let infrastructuresListPath: string; +let deployedTemplatesPath: string; //*****************// //* Aux functions *// @@ -557,7 +559,7 @@ async function saveToInfrastructureList(obj: IInfrastructureData) { generateIMCredentials().then(() => { console.log( - 'Generated random IM credentials lol:', + 'Generated random IM credentials:', deployInfo.IMuser, deployInfo.IMpass ); @@ -573,6 +575,20 @@ getIMClientPath() console.error('Error getting IM Client Path:', error); }); +getInfrastructuresListPath() + .then(path => { + process.env.INFRASTRUCTURES_LIST_PATH = infrastructuresListPath; + infrastructuresListPath = path; + console.log('Infrastructures List Path:', infrastructuresListPath); + }); + + getDeployedTemplatePath() + .then(path => { + process.env.DEPLOYED_TEMPLATE_PATH = deployedTemplatesPath; + deployedTemplatesPath = path; + console.log('Deployed Template Path:', deployedTemplatesPath); + }); + const deployChooseProvider = (dialogBody: HTMLElement): void => { // Clear dialog body dialogBody.innerHTML = ''; diff --git a/src/utils.ts b/src/utils.ts index 8676808..6174dc2 100755 --- a/src/utils.ts +++ b/src/utils.ts @@ -37,3 +37,47 @@ export async function getIMClientPath(): Promise { }).catch(reject); }); } + +export async function getInfrastructuresListPath(): Promise { + return new Promise((resolve, reject) => { + // Command to find the absolute path of infrastructuresList.json + const cmd = ` + %%bash + find "$(pwd)" -name "infrastructuresList.json" | head -n 1 + `; + + executeKernelCommand(cmd, output => { + if (output.trim()) { + resolve(output.trim()); + } else { + reject( + new Error( + 'Failed to find infrastructuresList.json. Maybe it is not in the project root.' + ) + ); + } + }).catch(reject); + }); +} + +export async function getDeployedTemplatePath(): Promise { + return new Promise((resolve, reject) => { + // Command to find the absolute path of deployed-template.yaml + const cmd = ` + %%bash + find "$(pwd)" -name "deployed-template.yaml" | head -n 1 + `; + + executeKernelCommand(cmd, output => { + if (output.trim()) { + resolve(output.trim()); + } else { + reject( + new Error( + 'Failed to find deployed-template.yaml. Maybe it is not in the project root.' + ) + ); + } + }).catch(reject); + }); +} \ No newline at end of file From f5b6c91446764ee6260d77416cbc7f477b258f8b Mon Sep 17 00:00:00 2001 From: antonio Date: Mon, 30 Sep 2024 11:24:17 +0200 Subject: [PATCH 2/5] Changed resources path --- deployed-template.yaml | 0 infrastructuresList.json | 1 - .../deployable_templates}/ansible_tasks.yaml | 0 .../deployable_templates}/argo.yaml | 0 .../deployable_templates}/docker_cluster.yaml | 0 .../deployable_templates}/galaxy.yaml | 0 .../deployable_templates}/influxdb.yaml | 0 .../deployable_templates}/kubeapps.yaml | 0 .../deployable_templates}/kubernetes.yaml | 0 .../deployable_templates}/minio_compose.yaml | 0 .../deployable_templates}/noderedvm.yaml | 0 .../deployable_templates}/prometheus.yaml | 0 .../simple-node-disk.yaml | 0 .../deployable_templates}/slurm_cluster.yaml | 0 .../deployable_templates}/slurm_elastic.yaml | 0 .../deployable_templates}/slurm_galaxy.yaml | 0 resources/deployed-template.yaml | 217 ++++++++++++++++++ resources/infrastructuresList.json | 3 + 18 files changed, 220 insertions(+), 1 deletion(-) delete mode 100644 deployed-template.yaml delete mode 100644 infrastructuresList.json rename {templates => resources/deployable_templates}/ansible_tasks.yaml (100%) rename {templates => resources/deployable_templates}/argo.yaml (100%) rename {templates => resources/deployable_templates}/docker_cluster.yaml (100%) rename {templates => resources/deployable_templates}/galaxy.yaml (100%) rename {templates => resources/deployable_templates}/influxdb.yaml (100%) rename {templates => resources/deployable_templates}/kubeapps.yaml (100%) rename {templates => resources/deployable_templates}/kubernetes.yaml (100%) rename {templates => resources/deployable_templates}/minio_compose.yaml (100%) rename {templates => resources/deployable_templates}/noderedvm.yaml (100%) rename {templates => resources/deployable_templates}/prometheus.yaml (100%) rename {templates => resources/deployable_templates}/simple-node-disk.yaml (100%) rename {templates => resources/deployable_templates}/slurm_cluster.yaml (100%) rename {templates => resources/deployable_templates}/slurm_elastic.yaml (100%) rename {templates => resources/deployable_templates}/slurm_galaxy.yaml (100%) create mode 100644 resources/deployed-template.yaml create mode 100644 resources/infrastructuresList.json diff --git a/deployed-template.yaml b/deployed-template.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/infrastructuresList.json b/infrastructuresList.json deleted file mode 100644 index b133b88..0000000 --- a/infrastructuresList.json +++ /dev/null @@ -1 +0,0 @@ -{ "infrastructures": [] } diff --git a/templates/ansible_tasks.yaml b/resources/deployable_templates/ansible_tasks.yaml similarity index 100% rename from templates/ansible_tasks.yaml rename to resources/deployable_templates/ansible_tasks.yaml diff --git a/templates/argo.yaml b/resources/deployable_templates/argo.yaml similarity index 100% rename from templates/argo.yaml rename to resources/deployable_templates/argo.yaml diff --git a/templates/docker_cluster.yaml b/resources/deployable_templates/docker_cluster.yaml similarity index 100% rename from templates/docker_cluster.yaml rename to resources/deployable_templates/docker_cluster.yaml diff --git a/templates/galaxy.yaml b/resources/deployable_templates/galaxy.yaml similarity index 100% rename from templates/galaxy.yaml rename to resources/deployable_templates/galaxy.yaml diff --git a/templates/influxdb.yaml b/resources/deployable_templates/influxdb.yaml similarity index 100% rename from templates/influxdb.yaml rename to resources/deployable_templates/influxdb.yaml diff --git a/templates/kubeapps.yaml b/resources/deployable_templates/kubeapps.yaml similarity index 100% rename from templates/kubeapps.yaml rename to resources/deployable_templates/kubeapps.yaml diff --git a/templates/kubernetes.yaml b/resources/deployable_templates/kubernetes.yaml similarity index 100% rename from templates/kubernetes.yaml rename to resources/deployable_templates/kubernetes.yaml diff --git a/templates/minio_compose.yaml b/resources/deployable_templates/minio_compose.yaml similarity index 100% rename from templates/minio_compose.yaml rename to resources/deployable_templates/minio_compose.yaml diff --git a/templates/noderedvm.yaml b/resources/deployable_templates/noderedvm.yaml similarity index 100% rename from templates/noderedvm.yaml rename to resources/deployable_templates/noderedvm.yaml diff --git a/templates/prometheus.yaml b/resources/deployable_templates/prometheus.yaml similarity index 100% rename from templates/prometheus.yaml rename to resources/deployable_templates/prometheus.yaml diff --git a/templates/simple-node-disk.yaml b/resources/deployable_templates/simple-node-disk.yaml similarity index 100% rename from templates/simple-node-disk.yaml rename to resources/deployable_templates/simple-node-disk.yaml diff --git a/templates/slurm_cluster.yaml b/resources/deployable_templates/slurm_cluster.yaml similarity index 100% rename from templates/slurm_cluster.yaml rename to resources/deployable_templates/slurm_cluster.yaml diff --git a/templates/slurm_elastic.yaml b/resources/deployable_templates/slurm_elastic.yaml similarity index 100% rename from templates/slurm_elastic.yaml rename to resources/deployable_templates/slurm_elastic.yaml diff --git a/templates/slurm_galaxy.yaml b/resources/deployable_templates/slurm_galaxy.yaml similarity index 100% rename from templates/slurm_galaxy.yaml rename to resources/deployable_templates/slurm_galaxy.yaml diff --git a/resources/deployed-template.yaml b/resources/deployed-template.yaml new file mode 100644 index 0000000..fb6ad20 --- /dev/null +++ b/resources/deployed-template.yaml @@ -0,0 +1,217 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 +imports: + - grycap_custom_types: https://raw.githubusercontent.com/grycap/tosca/main/custom_types.yaml +description: > + Deploy a compute node getting the IP and SSH credentials to access via ssh + with an extra HD disk. +metadata: + template_name: VM + template_version: 1.1.0 + template_author: Miguel Caballer + creation_date: 2020-09-08T00:00:00.000Z + display_name: Deploy a VM + icon: images/vm-icon-disk.png + tag: VM + order: 1 + tabs: + VM Data: + - num_cpus + - mem_size + - disk_size + - num_instances + - storage_size + - mount_path + - ports + GPU Data: .*gpu.* + childs: + - docker.yaml + - galaxy.yaml + - users.yml + - ssh_keys.yml + - proxy_host.yaml + - minio_compose.yaml + - nvidia.yml + - ansible_tasks.yml + - ssh_oidc.yaml + - noderedvm.yaml + - cernvmfs.yaml + - image-service.yaml + - ai4eoscvm.yaml + - mlflowvm.yaml + - wget.yml + - sgde.yaml + - dydns_egi_update_vm.yml + - flowfuse.yaml + infra_name: jupyter_c9a1f2bba6932c40e79b0e7498319e1ccb714786741c49522630b4fb52026f72 +topology_template: + inputs: + num_cpus: + type: integer + description: Number of virtual cpus for the VM + default: 1 + constraints: + - valid_values: + - 1 + - 2 + - 4 + - 8 + - 16 + - 32 + - 64 + mem_size: + type: scalar-unit.size + description: Amount of memory for the VM + default: 2 GB + constraints: + - valid_values: + - 2 GB + - 4 GB + - 8 GB + - 16 GB + - 32 GB + - 64 GB + - 128 GB + - 256 GB + - 512 GB + disk_size: + type: scalar-unit.size + description: Size of the root disk of the WNs + default: 20 GB + constraints: + - valid_values: + - 20 GB + - 50 GB + - 100 GB + - 200 GB + num_instances: + type: integer + description: Number of VMs to be spawned + default: 1 + storage_size: + type: scalar-unit.size + description: Size of the extra HD added to the instance (Set 0 if disk is not needed) + default: 0 GB + constraints: + - valid_values: + - 0 GB + - 10 GB + - 20 GB + - 50 GB + - 100 GB + - 200 GB + - 500 GB + - 1 TB + - 2 TB + - 10 TB + - 20 TB + - 40 TB + - 100 TB + mount_path: + type: string + description: Path to mount the extra disk + default: /mnt/disk + num_gpus: + type: integer + description: Number of GPUs to assing to this VM + default: 1 + constraints: + - valid_values: + - 0 + - 1 + - 2 + - 3 + - 4 + gpu_vendor: + type: string + description: GPU Vendor + default: + constraints: + - valid_values: + - + - NVIDIA + - AMD + gpu_model: + type: string + description: GPU Model + default: + ports: + type: map + entry_schema: + type: PortSpec + description: | + List of ports to be Opened in the Cloud site (eg. 22,80,443,2000:2100). + You can also include the remote CIDR (eg. 8.8.0.0/24). + default: + ssh_port: + source: 22 + protocol: tcp + image: + type: string + description: Image to be used for the VM + default: ost://horsemen.i3m.upv.es/79095fa5-7baf-493c-9514-e24d52dd1527 + node_templates: + simple_node: + type: tosca.nodes.indigo.Compute + capabilities: + endpoint: + properties: + network_name: PUBLIC + ports: + get_input: ports + scalable: + properties: + count: + get_input: num_instances + host: + properties: + disk_size: + get_input: disk_size + num_cpus: + get_input: num_cpus + mem_size: + get_input: mem_size + num_gpus: + get_input: num_gpus + gpu_vendor: + get_input: gpu_vendor + gpu_model: + get_input: gpu_model + os: + properties: + image: + get_input: image + interfaces: + Standard: + configure: + implementation: >- + https://raw.githubusercontent.com/grycap/tosca/main/artifacts/dummy.yml + requirements: + - local_storage: + node: my_block_storage + capability: tosca.capabilities.Attachment + relationship: + type: tosca.relationships.AttachesTo + properties: + location: + get_input: mount_path + device: hdb + my_block_storage: + type: tosca.nodes.BlockStorage + properties: + size: + get_input: storage_size + outputs: + node_ip: + value: + get_attribute: + - simple_node + - public_address + - 0 + node_creds: + value: + get_attribute: + - simple_node + - endpoint + - credential + - 0 + diff --git a/resources/infrastructuresList.json b/resources/infrastructuresList.json new file mode 100644 index 0000000..6982678 --- /dev/null +++ b/resources/infrastructuresList.json @@ -0,0 +1,3 @@ +{ + "infrastructures": [] +} From 45a54df42a3c29275afbab5159de4ddac90f3a72 Mon Sep 17 00:00:00 2001 From: antonio Date: Mon, 30 Sep 2024 11:25:40 +0200 Subject: [PATCH 3/5] Created function to dinamically find resources --- apricot_magics/apricot_magics.py | 73 +++++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/apricot_magics/apricot_magics.py b/apricot_magics/apricot_magics.py index 5fe74bb..79e31db 100644 --- a/apricot_magics/apricot_magics.py +++ b/apricot_magics/apricot_magics.py @@ -1,6 +1,7 @@ from tabulate import tabulate from IPython.core.magic import Magics, line_magic, line_cell_magic, magics_class from subprocess import run, PIPE, CalledProcessError +from pathlib import Path import os import json @@ -10,8 +11,7 @@ class Apricot_Magics(Magics): def __init__(self, shell): super().__init__(shell) - self.IM_CLIENT_PATH = '/usr/local/bin/im_client.py' # os.getenv("IM_CLIENT_PATH") - self.file_path = '../infrastructuresList.json' + self.load_paths() ######################## # Auxiliar functions # @@ -20,12 +20,12 @@ def __init__(self, shell): def create_auth_pipe(self, infrastructure_id): # Read the JSON data from the file try: - with open(self.file_path) as f: + with open(self.inf_list_path) as f: data = json.load(f) except FileNotFoundError: - raise FileNotFoundError(f"File not found: {self.file_path}") + raise FileNotFoundError(f"File not found: {self.inf_list_path}") except json.JSONDecodeError: - raise ValueError(f"Error decoding JSON from file: {self.file_path}") + raise ValueError(f"Error decoding JSON from file: {self.inf_list_path}") # Find the infrastructure with the specified ID found_infrastructure = None @@ -61,11 +61,10 @@ def generate_key(self, infrastructure_id, vm_id): ########################################## private_key_content = None host_ip = None - im_client_path = self.get_im_client_path() cmd_getvminfo = [ 'python3', - im_client_path, + self.im_client_path, 'getvminfo', infrastructure_id, vm_id, @@ -113,6 +112,39 @@ def generate_key(self, infrastructure_id, vm_id): # If the subprocess call fails, return the error output return None, None + def load_paths(self): + # Get the absolute path to the current file (apricot_magics.py) + current_dir = Path(__file__).parent + + # Construct the path to the 'resources' folder relative to 'apricot_magics/' + resources_dir = current_dir.parent / "resources" + + self.inf_list_path = resources_dir / "infrastructuresList.json" + self.deployedTemplate_path = resources_dir / "deployed-template.yaml" + + # Check if the files exist + if not self.inf_list_path.exists(): + raise FileNotFoundError(f"File not found: {self.inf_list_path}") + if not self.deployedTemplate_path.exists(): + raise FileNotFoundError(f"File not found: {self.deployedTemplate_path}") + + self.im_client_path = self.find_im_client() + + if not self.im_client_path: + raise FileNotFoundError("im_client.py executable not found in PATH") + + def find_im_client(self) -> str: + """Find the 'im_client.py' executable in the system's PATH using the 'which' command.""" + executable_name = "im_client.py" + try: + # Use 'which' command to find the executable + result = run(['which', executable_name], stdout=PIPE, stderr=PIPE, check=True, text=True) + executable_path = result.stdout.strip() + return executable_path + except (CalledProcessError, FileNotFoundError): + # Return None if the executable is not found + return None + ################## # Magics # ################## @@ -136,7 +168,7 @@ def apricot_log(self, line): # Construct the command to retrieve log messages cmd_getcontmsg = [ "python3", - self.IM_CLIENT_PATH, + self.im_client_path, "getcontmsg", inf_id, "-a", @@ -163,11 +195,12 @@ def apricot_log(self, line): @line_magic def apricot_ls(self, line): infrastructures_list = [] + try: - with open(self.file_path) as f: + with open(self.inf_list_path) as f: data = json.load(f) except FileNotFoundError: - print(f"File not found: {self.file_path}") + print(f"File not found: {self.inf_list_path}") return # Iterate through each infrastructure @@ -187,7 +220,7 @@ def apricot_ls(self, line): cmd_getstate = [ 'python3', - self.IM_CLIENT_PATH, + self.im_client_path, 'getstate', infrastructure_info['InfrastructureID'], '-r', @@ -214,7 +247,7 @@ def apricot_ls(self, line): cmd_getvminfo = [ 'python3', - self.IM_CLIENT_PATH, + self.im_client_path, 'getvminfo', infrastructure_info['InfrastructureID'], '0', @@ -270,7 +303,7 @@ def apricot_info(self, line): # Construct the command to retrieve log messages cmd_getinfo = [ "python3", - self.IM_CLIENT_PATH, + self.im_client_path, "getinfo", inf_id, "-a", @@ -313,7 +346,7 @@ def apricot_vmls(self, line): cmd_getinfo = [ 'python3', - self.IM_CLIENT_PATH, + self.im_client_path, 'getinfo', inf_id, '-r', @@ -557,7 +590,7 @@ def apricot(self, code, cell=None): cmd_destroy = [ 'python3', - self.IM_CLIENT_PATH, + self.im_client_path, 'destroy', inf_id, '-r', @@ -586,13 +619,13 @@ def apricot(self, code, cell=None): # Load infrastructure list from JSON file try: - with open(self.file_path, 'r') as f: + with open(self.inf_list_path, 'r') as f: data = json.load(f) except FileNotFoundError: - print(f"File not found: {self.file_path}") + print(f"File not found: {self.inf_list_path}") return "Failed" except json.JSONDecodeError: - print(f"Error decoding JSON from file: {self.file_path}") + print(f"Error decoding JSON from file: {self.inf_list_path}") return "Failed" # Find and remove the infrastructure with the specified ID @@ -603,10 +636,10 @@ def apricot(self, code, cell=None): # Write the updated infrastructure list back to the JSON file try: - with open(self.file_path, 'w') as f: + with open(self.inf_list_path, 'w') as f: json.dump(data, f, indent=4) except IOError as e: - print(f"Error writing to file {self.file_path}: {e}") + print(f"Error writing to file {self.inf_list_path}: {e}") return "Failed" except CalledProcessError as e: From 856539196f2665d75e07881c62b96c3882340082 Mon Sep 17 00:00:00 2001 From: antonio Date: Mon, 30 Sep 2024 11:28:56 +0200 Subject: [PATCH 4/5] Minor improvements. Created functions to find relative path to resources --- src/index.ts | 4 -- src/listDeployments.ts | 9 ++-- src/utils.ts | 102 ++++++++++++++++++++++++++++++++++------- 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/src/index.ts b/src/index.ts index eb630d4..1689890 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,10 +6,6 @@ import { DOMUtils } from '@jupyterlab/apputils'; import { Widget } from '@lumino/widgets'; import { ButtonExtension } from './buttons'; -// Notifications for deployed infrastructures (https://github.com/jupyterlab/extension-examples/tree/main/notifications) -// Signals to make widgets communicate with eachother (https://github.com/jupyterlab/extension-examples/tree/main/signals) -// state to save and restore data saved in persistent state database (https://github.com/jupyterlab/extension-examples/tree/main/state) - /** * Initialization data for the apricot extension. */ diff --git a/src/listDeployments.ts b/src/listDeployments.ts index d5c8f8e..36eb256 100644 --- a/src/listDeployments.ts +++ b/src/listDeployments.ts @@ -1,7 +1,7 @@ import { KernelManager } from '@jupyterlab/services'; import { Dialog } from '@jupyterlab/apputils'; import { Widget } from '@lumino/widgets'; -import { getIMClientPath } from './utils'; +import { getInfrastructuresListPath, getIMClientPath } from './utils'; interface IInfrastructure { IMuser: string; @@ -59,15 +59,15 @@ function createTable(): HTMLTableElement { async function populateTable(table: HTMLTableElement): Promise { let jsonData: string | null = null; - + const infrastructuresListPath = await getInfrastructuresListPath(); // Kernel manager to execute the bash command const kernelManager = new KernelManager(); const kernel = await kernelManager.startNew(); try { // Read the contents of infrastructuresList.json - const cmdReadJson = '%%bash\n' + 'cat $PWD/infrastructuresList.json'; - + const cmdReadJson = `%%bash cat "${infrastructuresListPath}" +`; const futureReadJson = kernel.requestExecute({ code: cmdReadJson }); futureReadJson.onIOPub = msg => { @@ -318,5 +318,4 @@ async function infrastructureIP( return cmd; } -// Exporting the function that initiates the dialog export { openListDeploymentsDialog }; diff --git a/src/utils.ts b/src/utils.ts index 6174dc2..749a3ca 100755 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,15 +1,26 @@ import { KernelManager } from '@jupyterlab/services'; +let kernelManager: KernelManager | null = null; +let kernel: any | null = null; + +// Get or start a new kernel (reused across all executions) +async function getOrStartKernel() { + if (!kernelManager || !kernel) { + kernelManager = new KernelManager(); + kernel = await kernelManager.startNew(); + } + return kernel; +} + export async function executeKernelCommand( command: string, callback: (output: string) => void ): Promise { try { - const kernelManager = new KernelManager(); - const kernel = await kernelManager.startNew(); + const kernel = await getOrStartKernel(); const future = kernel.requestExecute({ code: command }); - future.onIOPub = msg => { + future.onIOPub = (msg: any) => { const content = msg.content as any; const outputText = content.text || (content.data && content.data['text/plain']); @@ -38,21 +49,20 @@ export async function getIMClientPath(): Promise { }); } -export async function getInfrastructuresListPath(): Promise { +export async function getDeployedTemplatePath(): Promise { return new Promise((resolve, reject) => { - // Command to find the absolute path of infrastructuresList.json - const cmd = ` + const cmdDeployedTemplatePath = ` %%bash - find "$(pwd)" -name "infrastructuresList.json" | head -n 1 + realpath --relative-to="$(pwd)" resources/deployed-template.yaml `; - executeKernelCommand(cmd, output => { + executeKernelCommand(cmdDeployedTemplatePath, output => { if (output.trim()) { resolve(output.trim()); } else { reject( new Error( - 'Failed to find infrastructuresList.json. Maybe it is not in the project root.' + 'Failed to find deployed-template.yaml. Maybe it is not in the resources folder.' ) ); } @@ -60,24 +70,84 @@ export async function getInfrastructuresListPath(): Promise { }); } -export async function getDeployedTemplatePath(): Promise { +export async function getInfrastructuresListPath(): Promise { return new Promise((resolve, reject) => { - // Command to find the absolute path of deployed-template.yaml - const cmd = ` + const cmdInfrastructuresListPath = ` %%bash - find "$(pwd)" -name "deployed-template.yaml" | head -n 1 + realpath --relative-to="$(pwd)" resources/infrastructuresList.json + `; + + executeKernelCommand(cmdInfrastructuresListPath, output => { + if (output.trim()) { + resolve(output.trim()); + } else { + reject( + new Error( + 'Failed to find infrastructuresList.json. Maybe it is not in the resources folder.' + ) + ); + } + }).catch(reject); + }); +} + +export async function getDeployableTemplatesPath(): Promise { + return new Promise((resolve, reject) => { + const cmdTemplatesPath = `%%bash\n + realpath --relative-to="$(pwd)" resources/deployable_templates `; - executeKernelCommand(cmd, output => { + executeKernelCommand(cmdTemplatesPath, output => { if (output.trim()) { resolve(output.trim()); } else { reject( new Error( - 'Failed to find deployed-template.yaml. Maybe it is not in the project root.' + 'Failed to find templates/ directory. Maybe it is not in the project folder.' ) ); } }).catch(reject); }); -} \ No newline at end of file +} + +// // Function to batch run commands in parallel +// export async function getPathsInParallel(): Promise<{ +// imClientPath: string; +// infrastructuresListPath: string; +// deployedTemplatePath: string; +// templatesPath: string; +// }> { +// try { +// const [imClientPath, infrastructuresListPath, deployedTemplatePath, templatesPath] = await Promise.all([ +// getIMClientPath(), +// getInfrastructuresListPath(), +// getDeployedTemplatePath(), +// getTemplatesPath(), +// ]); + +// return { +// imClientPath, +// infrastructuresListPath, +// deployedTemplatePath, +// templatesPath, +// }; +// } catch (error) { +// console.error('Error getting paths in parallel:', error); +// throw error; +// } +// } + +// Function to get the infrastructures list path +// export async function getCurrentWorkingDirectory(): Promise { +// return new Promise((resolve, reject) => { +// const cmdCurrentDir = ` +// %%bash +// pwd +// `; + +// executeKernelCommand(cmdCurrentDir, (output) => { +// resolve(output.trim()); +// }).catch(reject); +// }); +// } From da5385bd62440887c3d4da6e787ce1e31a6d9e91 Mon Sep 17 00:00:00 2001 From: antonio Date: Mon, 30 Sep 2024 11:34:54 +0200 Subject: [PATCH 5/5] Working directory can be dynamically changed --- src/deploymentMenu.ts | 77 ++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/src/deploymentMenu.ts b/src/deploymentMenu.ts index d4b69ab..0c942aa 100644 --- a/src/deploymentMenu.ts +++ b/src/deploymentMenu.ts @@ -1,9 +1,14 @@ import * as jsyaml from 'js-yaml'; -import { ContentsManager } from '@jupyterlab/services'; -import { KernelManager } from '@jupyterlab/services'; +import { ContentsManager, KernelManager } from '@jupyterlab/services'; import { Widget } from '@lumino/widgets'; import { Dialog } from '@jupyterlab/apputils'; -import { executeKernelCommand, getIMClientPath, getInfrastructuresListPath, getDeployedTemplatePath } from './utils'; +import { + executeKernelCommand, + getDeployableTemplatesPath, + getInfrastructuresListPath, + getIMClientPath, + getDeployedTemplatePath +} from './utils'; interface IDeployInfo { IMuser: string; @@ -100,9 +105,6 @@ const deployInfo: IDeployInfo = { let imageOptions: { uri: string; name: string }[] = []; let deploying = false; // Flag to prevent multiple deployments at the same time -let imClientPath: string; -let infrastructuresListPath: string; -let deployedTemplatesPath: string; //*****************// //* Aux functions *// @@ -205,7 +207,7 @@ async function createImagesDropdown( dialogBody: HTMLElement ) { if (!output) { - console.error('Output is empty or undefined.'); + console.log('Waiting for OS images to load.'); return; } @@ -333,13 +335,18 @@ async function createChildsForm( deployDialog: HTMLElement, buttonsContainer: HTMLElement ) { + const templatesPath = await getDeployableTemplatesPath(); + // Create form element const form = document.createElement('form'); form.id = `form-${app.toLowerCase()}`; // Load YAML file asynchronously const contentsManager = new ContentsManager(); - const file = await contentsManager.get(`templates/${app.toLowerCase()}.yaml`); + const file = await contentsManager.get( + `${templatesPath}/${app.toLowerCase()}.yaml` + ); + const yamlContent = file.content as string; // Parse YAML content @@ -426,6 +433,7 @@ async function createChildsForm( //*********************// async function selectImage(obj: IDeployInfo): Promise { + const imClientPath = await getIMClientPath(); const pipeAuth = `${obj.infName}-auth-pipe`; let cmd = `%%bash @@ -493,7 +501,8 @@ async function deployIMCommand( mergedTemplate: string ): Promise { const pipeAuth = `${obj.infName}-auth-pipe`; - const templatePath = '$PWD/deployed-template.yaml'; + const deployedTemplatePath = await getDeployedTemplatePath(); + const imClientPath = await getIMClientPath(); let cmd = `%%bash PWD=$(pwd) @@ -504,7 +513,7 @@ async function deployIMCommand( # Create pipes mkfifo $PWD/${pipeAuth} # Save mergedTemplate as a YAML file - echo '${mergedTemplate}' > ${templatePath} + echo '${mergedTemplate}' > ${deployedTemplatePath} `; // Command to create the IM-cli credentials @@ -522,7 +531,7 @@ async function deployIMCommand( cmd += `echo -e "${authContent}" > $PWD/${pipeAuth} & # Create final command where the output is stored in "imageOut" - imageOut=$(python3 ${imClientPath} -a $PWD/${pipeAuth} create ${templatePath} -r https://im.egi.eu/im) + imageOut=$(python3 ${imClientPath} -a $PWD/${pipeAuth} create ${deployedTemplatePath} -r https://im.egi.eu/im) # Remove pipe rm -f $PWD/${pipeAuth} &> /dev/null # Print IM output on stderr or stdout @@ -539,14 +548,14 @@ async function deployIMCommand( } async function saveToInfrastructureList(obj: IInfrastructureData) { - const filePath = '$PWD/infrastructuresList.json'; + const infrastructuresListPath = await getInfrastructuresListPath(); // Construct the bash command const cmd = ` %%bash PWD=$(pwd) - existingJson=$(cat ${filePath}) + existingJson=$(cat ${infrastructuresListPath}) newJson=$(echo "$existingJson" | jq -c '.infrastructures += [${JSON.stringify(obj)}]') - echo "$newJson" > ${filePath} + echo "$newJson" > ${infrastructuresListPath} `; console.log('Bash command:', cmd); @@ -565,30 +574,6 @@ generateIMCredentials().then(() => { ); }); -getIMClientPath() - .then(path => { - process.env.IM_CLIENT_PATH = path; - imClientPath = path; - console.log('IM Client Path:', imClientPath); - }) - .catch(error => { - console.error('Error getting IM Client Path:', error); - }); - -getInfrastructuresListPath() - .then(path => { - process.env.INFRASTRUCTURES_LIST_PATH = infrastructuresListPath; - infrastructuresListPath = path; - console.log('Infrastructures List Path:', infrastructuresListPath); - }); - - getDeployedTemplatePath() - .then(path => { - process.env.DEPLOYED_TEMPLATE_PATH = deployedTemplatesPath; - deployedTemplatesPath = path; - console.log('Deployed Template Path:', deployedTemplatesPath); - }); - const deployChooseProvider = (dialogBody: HTMLElement): void => { // Clear dialog body dialogBody.innerHTML = ''; @@ -694,6 +679,8 @@ const createCheckboxesForChilds = async ( dialogBody: HTMLElement, childs: string[] ): Promise => { + const templatesPath = await getDeployableTemplatesPath(); + // Create paragraph element for checkboxes const paragraph = document.createElement('p'); paragraph.textContent = 'Select optional recipe features:'; @@ -708,8 +695,9 @@ const createCheckboxesForChilds = async ( const promises = childs.map(async child => { // Load YAML file asynchronously const file = await contentsManager.get( - `templates/${child.toLowerCase()}.yaml` + `${templatesPath}/${child.toLowerCase()}.yaml` ); + const yamlContent = file.content as string; // Parse YAML content @@ -992,6 +980,7 @@ const deployChildsConfiguration = async ( deployInfraConfiguration(dialogBody) ); const nextButton = createButton('Deploy', async () => { + const templatesPath = await getDeployableTemplatesPath(); const contentsManager = new ContentsManager(); const userInputs = ( await Promise.all( @@ -1000,7 +989,9 @@ const deployChildsConfiguration = async ( const childName = form.id.replace('form-', ''); // Fetch YAML content - const file = await contentsManager.get(`templates/${childName}.yaml`); + const file = await contentsManager.get( + `${templatesPath}/${childName}.yaml` + ); const yamlContent = file.content as string; const yamlData: any = jsyaml.load(yamlContent); const recipeInputs = yamlData.topology_template.inputs; @@ -1071,8 +1062,11 @@ async function deployFinalRecipe( deploying = true; try { + const templatesPath = await getDeployableTemplatesPath(); const contentsManager = new ContentsManager(); - const file = await contentsManager.get('templates/simple-node-disk.yaml'); + const file = await contentsManager.get( + `${templatesPath}/simple-node-disk.yaml` + ); const yamlContent = file.content; const parsedTemplate = jsyaml.load(yamlContent) as any; @@ -1179,5 +1173,4 @@ const handleFinalDeployOutput = async ( } }; -// Exporting the function that initiates the dialog export { openDeploymentDialog };