cloudos-cli 2.62.0__tar.gz → 2.64.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {cloudos_cli-2.62.0/cloudos_cli.egg-info → cloudos_cli-2.64.1}/PKG-INFO +25 -4
  2. cloudos_cli-2.62.0/PKG-INFO → cloudos_cli-2.64.1/README.md +24 -38
  3. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/__main__.py +179 -13
  4. cloudos_cli-2.64.1/cloudos_cli/_version.py +1 -0
  5. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/clos.py +57 -53
  6. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/datasets/datasets.py +15 -14
  7. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/link/link.py +1 -0
  8. cloudos_cli-2.62.0/README.md → cloudos_cli-2.64.1/cloudos_cli.egg-info/PKG-INFO +59 -3
  9. cloudos_cli-2.62.0/cloudos_cli/_version.py +0 -1
  10. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/LICENSE +0 -0
  11. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/__init__.py +0 -0
  12. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/configure/__init__.py +0 -0
  13. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/configure/configure.py +0 -0
  14. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/cost/__init__.py +0 -0
  15. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/cost/cost.py +0 -0
  16. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/datasets/__init__.py +0 -0
  17. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/import_wf/__init__.py +0 -0
  18. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/import_wf/import_wf.py +0 -0
  19. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/jobs/__init__.py +0 -0
  20. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/jobs/job.py +0 -0
  21. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/link/__init__.py +0 -0
  22. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/logging/__init__.py +0 -0
  23. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/logging/logger.py +0 -0
  24. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/procurement/__init__.py +0 -0
  25. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/procurement/images.py +0 -0
  26. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/queue/__init__.py +0 -0
  27. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/queue/queue.py +0 -0
  28. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/__init__.py +0 -0
  29. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/array_job.py +0 -0
  30. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/cloud.py +0 -0
  31. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/details.py +0 -0
  32. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/errors.py +0 -0
  33. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/last_wf.py +0 -0
  34. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/requests.py +0 -0
  35. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/resources.py +0 -0
  36. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/SOURCES.txt +0 -0
  37. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/dependency_links.txt +0 -0
  38. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/entry_points.txt +0 -0
  39. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/requires.txt +0 -0
  40. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/top_level.txt +0 -0
  41. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/setup.cfg +0 -0
  42. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/setup.py +0 -0
  43. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/__init__.py +0 -0
  44. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/functions_for_pytest.py +0 -0
  45. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_cli_project_create.py +0 -0
  46. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_cost/__init__.py +0 -0
  47. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_cost/test_job_cost.py +0 -0
  48. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_logging/__init__.py +0 -0
  49. {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_logging/test_logger.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudos_cli
3
- Version: 2.62.0
3
+ Version: 2.64.1
4
4
  Summary: Python package for interacting with CloudOS
5
5
  Home-page: https://github.com/lifebit-ai/cloudos-cli
6
6
  Author: David Piñeyro
@@ -643,6 +643,12 @@ Nextflow standard output: s3://path/to/location/of/logs/stdout.txt
643
643
  Trace file: s3://path/to/location/of/logs/trace.txt
644
644
  ```
645
645
 
646
+ You can also link the logs directory to an interactive session using the `--link` flag. This will mount the entire logs directory, providing access to all log files in your interactive session:
647
+
648
+ ```bash
649
+ cloudos job logs --profile my_profile --job-id "12345678910" --link --session-id your_session_id
650
+ ```
651
+
646
652
  #### Get Job Results
647
653
 
648
654
  The following command allows you to get the path where CloudOS stores the output files for a job. This can be used only on your user's jobs and for jobs with "completed" status.
@@ -656,6 +662,12 @@ Executing results...
656
662
  results: s3://path/to/location/of/results/results/
657
663
  ```
658
664
 
665
+ You can also link all result directories to an interactive session using the `--link` flag. This will mount all result directories from the job, providing direct access to output files in your interactive session:
666
+
667
+ ```bash
668
+ cloudos job results --profile my_profile --job-id "12345678910" --link --session-id your_session_id
669
+ ```
670
+
659
671
 
660
672
  #### Clone or Resume job
661
673
 
@@ -908,6 +920,15 @@ Finding working directory path...
908
920
  Working directory for job 68747bac9e7fe38ec6e022ad: az://123456789000.blob.core.windows.net/cloudos-987652349087/projects/455654676/jobs/54678856765/work
909
921
  ```
910
922
 
923
+ You can also link the working directory to an interactive session using the `--link` flag. This requires specifying a session ID either through the `--session-id` option or from a configured profile:
924
+
925
+ ```shell
926
+ cloudos job workdir \
927
+ --profile profile-name \
928
+ --job-id 62c83a1191fe06013b7ef355 \
929
+ --link --session-id your_session_id
930
+ ```
931
+
911
932
  #### List Jobs
912
933
 
913
934
  You can get a summary of workspace jobs in two different formats:
@@ -1387,7 +1408,7 @@ The output of this command is a list of files and folders present in the specifi
1387
1408
  > If the `<path>` is left empty, the command will return the list of folders present in the selected project.
1388
1409
 
1389
1410
  If you require more information on the files and folder listed, you can use the `--details` flag that will output a table containing the following columns:
1390
- - Type (folder or file)
1411
+ - Type (s3 folder, azure folder , virtual folder, file (user uploaded) or file (virtual copy))
1391
1412
  - Owner
1392
1413
  - Size (in human readable format)
1393
1414
  - Last updated
@@ -1776,7 +1797,7 @@ j_id = j.send_job(job_config)
1776
1797
  To check the status:
1777
1798
 
1778
1799
  ```python
1779
- j_status = j.get_job_status(j_id)
1800
+ j_status = j.get_job_status(j_id, workspace_id)
1780
1801
  j_status_h = json.loads(j_status.content)["status"]
1781
1802
  print(j_status_h)
1782
1803
  ```
@@ -1851,7 +1872,7 @@ print(c_status_h)
1851
1872
  j = jb.Job(cloudos_url, apikey, None, workspace_id, project_name, workflow_name, True, mainfile,
1852
1873
  importsfile)
1853
1874
  j_id = j.send_job(job_config, workflow_type='wdl', cromwell_id=json.loads(c_status.content)["_id"])
1854
- j_status = j.get_job_status(j_id)
1875
+ j_status = j.get_job_status(j_id, workspace_id)
1855
1876
  j_status_h = json.loads(j_status.content)["status"]
1856
1877
  print(j_status_h)
1857
1878
 
@@ -1,38 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: cloudos_cli
3
- Version: 2.62.0
4
- Summary: Python package for interacting with CloudOS
5
- Home-page: https://github.com/lifebit-ai/cloudos-cli
6
- Author: David Piñeyro
7
- Author-email: david.pineyro@lifebit.ai
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Operating System :: POSIX :: Linux
10
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
11
- Requires-Python: >=3.9
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: click>=8.0.1
15
- Requires-Dist: rich-click>=1.8.2
16
- Requires-Dist: pandas>=1.3.4
17
- Requires-Dist: numpy>=1.26.4
18
- Requires-Dist: requests>=2.26.0
19
- Provides-Extra: test
20
- Requires-Dist: pytest; extra == "test"
21
- Requires-Dist: mock; extra == "test"
22
- Requires-Dist: responses; extra == "test"
23
- Requires-Dist: requests_mock; extra == "test"
24
- Dynamic: author
25
- Dynamic: author-email
26
- Dynamic: classifier
27
- Dynamic: description
28
- Dynamic: description-content-type
29
- Dynamic: home-page
30
- Dynamic: license-file
31
- Dynamic: provides-extra
32
- Dynamic: requires-dist
33
- Dynamic: requires-python
34
- Dynamic: summary
35
-
36
1
  # cloudos-cli
37
2
 
38
3
  [![CI_tests](https://github.com/lifebit-ai/cloudos-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/lifebit-ai/cloudos-cli/actions/workflows/ci.yml)
@@ -643,6 +608,12 @@ Nextflow standard output: s3://path/to/location/of/logs/stdout.txt
643
608
  Trace file: s3://path/to/location/of/logs/trace.txt
644
609
  ```
645
610
 
611
+ You can also link the logs directory to an interactive session using the `--link` flag. This will mount the entire logs directory, providing access to all log files in your interactive session:
612
+
613
+ ```bash
614
+ cloudos job logs --profile my_profile --job-id "12345678910" --link --session-id your_session_id
615
+ ```
616
+
646
617
  #### Get Job Results
647
618
 
648
619
  The following command allows you to get the path where CloudOS stores the output files for a job. This can be used only on your user's jobs and for jobs with "completed" status.
@@ -656,6 +627,12 @@ Executing results...
656
627
  results: s3://path/to/location/of/results/results/
657
628
  ```
658
629
 
630
+ You can also link all result directories to an interactive session using the `--link` flag. This will mount all result directories from the job, providing direct access to output files in your interactive session:
631
+
632
+ ```bash
633
+ cloudos job results --profile my_profile --job-id "12345678910" --link --session-id your_session_id
634
+ ```
635
+
659
636
 
660
637
  #### Clone or Resume job
661
638
 
@@ -908,6 +885,15 @@ Finding working directory path...
908
885
  Working directory for job 68747bac9e7fe38ec6e022ad: az://123456789000.blob.core.windows.net/cloudos-987652349087/projects/455654676/jobs/54678856765/work
909
886
  ```
910
887
 
888
+ You can also link the working directory to an interactive session using the `--link` flag. This requires specifying a session ID either through the `--session-id` option or from a configured profile:
889
+
890
+ ```shell
891
+ cloudos job workdir \
892
+ --profile profile-name \
893
+ --job-id 62c83a1191fe06013b7ef355 \
894
+ --link --session-id your_session_id
895
+ ```
896
+
911
897
  #### List Jobs
912
898
 
913
899
  You can get a summary of workspace jobs in two different formats:
@@ -1387,7 +1373,7 @@ The output of this command is a list of files and folders present in the specifi
1387
1373
  > If the `<path>` is left empty, the command will return the list of folders present in the selected project.
1388
1374
 
1389
1375
  If you require more information on the files and folder listed, you can use the `--details` flag that will output a table containing the following columns:
1390
- - Type (folder or file)
1376
+ - Type (s3 folder, azure folder , virtual folder, file (user uploaded) or file (virtual copy))
1391
1377
  - Owner
1392
1378
  - Size (in human readable format)
1393
1379
  - Last updated
@@ -1776,7 +1762,7 @@ j_id = j.send_job(job_config)
1776
1762
  To check the status:
1777
1763
 
1778
1764
  ```python
1779
- j_status = j.get_job_status(j_id)
1765
+ j_status = j.get_job_status(j_id, workspace_id)
1780
1766
  j_status_h = json.loads(j_status.content)["status"]
1781
1767
  print(j_status_h)
1782
1768
  ```
@@ -1851,7 +1837,7 @@ print(c_status_h)
1851
1837
  j = jb.Job(cloudos_url, apikey, None, workspace_id, project_name, workflow_name, True, mainfile,
1852
1838
  importsfile)
1853
1839
  j_id = j.send_job(job_config, workflow_type='wdl', cromwell_id=json.loads(c_status.content)["_id"])
1854
- j_status = j.get_job_status(j_id)
1840
+ j_status = j.get_job_status(j_id, workspace_id)
1855
1841
  j_status_h = json.loads(j_status.content)["status"]
1856
1842
  print(j_status_h)
1857
1843
 
@@ -780,6 +780,7 @@ def run(ctx,
780
780
  print('\tPlease, wait until job completion (max wait time of ' +
781
781
  f'{wait_time} seconds).\n')
782
782
  j_status = j.wait_job_completion(job_id=j_id,
783
+ workspace_id=workspace_id,
783
784
  wait_time=wait_time,
784
785
  request_interval=request_interval,
785
786
  verbose=verbose,
@@ -793,7 +794,7 @@ def run(ctx,
793
794
  print(f'\nJob status for job "{j_name}" (ID: {j_id}): {j_final_s}')
794
795
  sys.exit(1)
795
796
  else:
796
- j_status = j.get_job_status(j_id, verify_ssl)
797
+ j_status = j.get_job_status(j_id, workspace_id, verify_ssl)
797
798
  j_status_h = json.loads(j_status.content)["status"]
798
799
  print(f'\tYour current job status is: {j_status_h}')
799
800
  print('\tTo further check your job status you can either go to ' +
@@ -813,6 +814,9 @@ def run(ctx,
813
814
  help=(f'The CloudOS url you are trying to access to. Default={CLOUDOS_URL}.'),
814
815
  default=CLOUDOS_URL,
815
816
  required=True)
817
+ @click.option('--workspace-id',
818
+ help='The specific CloudOS workspace id.',
819
+ required=True)
816
820
  @click.option('--job-id',
817
821
  help='The job id in CloudOS to search for.',
818
822
  required=True)
@@ -830,6 +834,7 @@ def run(ctx,
830
834
  def job_status(ctx,
831
835
  apikey,
832
836
  cloudos_url,
837
+ workspace_id,
833
838
  job_id,
834
839
  verbose,
835
840
  disable_ssl_verification,
@@ -841,7 +846,7 @@ def job_status(ctx,
841
846
  # Create a dictionary with required and non-required params
842
847
  required_dict = {
843
848
  'apikey': True,
844
- 'workspace_id': False,
849
+ 'workspace_id': True,
845
850
  'workflow_name': False,
846
851
  'project_name': False,
847
852
  'session_id': False,
@@ -855,6 +860,7 @@ def job_status(ctx,
855
860
  INIT_PROFILE,
856
861
  CLOUDOS_URL,
857
862
  profile=profile,
863
+ workspace_id=workspace_id,
858
864
  required_dict=required_dict,
859
865
  apikey=apikey,
860
866
  cloudos_url=cloudos_url
@@ -862,6 +868,7 @@ def job_status(ctx,
862
868
  )
863
869
  apikey = user_options['apikey']
864
870
  cloudos_url = user_options['cloudos_url']
871
+ workspace_id = user_options['workspace_id']
865
872
 
866
873
  print('Executing status...')
867
874
  verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
@@ -873,7 +880,7 @@ def job_status(ctx,
873
880
  print('\t' + str(cl) + '\n')
874
881
  print(f'\tSearching for job id: {job_id}')
875
882
  try:
876
- j_status = cl.get_job_status(job_id, verify_ssl)
883
+ j_status = cl.get_job_status(job_id, workspace_id, verify_ssl)
877
884
  j_status_h = json.loads(j_status.content)["status"]
878
885
  print(f'\tYour current job status is: {j_status_h}\n')
879
886
  j_url = f'{cloudos_url}/app/advanced-analytics/analyses/{job_id}'
@@ -901,6 +908,12 @@ def job_status(ctx,
901
908
  @click.option('--job-id',
902
909
  help='The job id in CloudOS to search for.',
903
910
  required=True)
911
+ @click.option('--link',
912
+ help='Link the working directory to an interactive session.',
913
+ is_flag=True)
914
+ @click.option('--session-id',
915
+ help='The specific CloudOS interactive session id. Required when using --link flag.',
916
+ required=False)
904
917
  @click.option('--verbose',
905
918
  help='Whether to print information messages or not.',
906
919
  is_flag=True)
@@ -917,6 +930,8 @@ def job_workdir(ctx,
917
930
  cloudos_url,
918
931
  workspace_id,
919
932
  job_id,
933
+ link,
934
+ session_id,
920
935
  verbose,
921
936
  disable_ssl_verification,
922
937
  ssl_cert,
@@ -944,17 +959,30 @@ def job_workdir(ctx,
944
959
  required_dict=required_dict,
945
960
  apikey=apikey,
946
961
  cloudos_url=cloudos_url,
947
- workspace_id=workspace_id
962
+ workspace_id=workspace_id,
963
+ session_id=session_id
948
964
  )
949
965
  )
950
966
  apikey = user_options['apikey']
951
967
  cloudos_url = user_options['cloudos_url']
952
968
  workspace_id = user_options['workspace_id']
969
+ # Get session_id from user_options (which includes profile data)
970
+ session_id = user_options.get('session_id') or session_id
971
+
972
+ # Validate link flag requirements AFTER loading profile
973
+ if link and not session_id:
974
+ raise click.ClickException("--session-id is required when using --link flag")
953
975
 
954
976
  print('Finding working directory path...')
955
977
  verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
956
978
  if verbose:
957
979
  print('\t...Preparing objects')
980
+ print('\tUsing the following parameters:')
981
+ print(f'\t\tCloudOS url: {cloudos_url}')
982
+ print(f'\t\tWorkspace ID: {workspace_id}')
983
+ print(f'\t\tJob ID: {job_id}')
984
+ if link:
985
+ print(f'\t\tSession ID: {session_id}')
958
986
  cl = Cloudos(cloudos_url, apikey, None)
959
987
  if verbose:
960
988
  print('\tThe following Cloudos object was created:')
@@ -963,6 +991,24 @@ def job_workdir(ctx,
963
991
  try:
964
992
  workdir = cl.get_job_workdir(job_id, workspace_id, verify_ssl)
965
993
  print(f"Working directory for job {job_id}: {workdir}")
994
+
995
+ # Link to interactive session if requested
996
+ if link:
997
+ if verbose:
998
+ print(f'\tLinking working directory to interactive session {session_id}...')
999
+
1000
+ # Use Link class to perform the linking
1001
+ link_client = Link(
1002
+ cloudos_url=cloudos_url,
1003
+ apikey=apikey,
1004
+ cromwell_token=None, # Not needed for linking operations
1005
+ workspace_id=workspace_id,
1006
+ project_name=None, # Not needed for S3 paths
1007
+ verify=verify_ssl
1008
+ )
1009
+
1010
+ link_client.link_folder(workdir.strip(), session_id)
1011
+
966
1012
  except BadRequestException as e:
967
1013
  raise ValueError(f"Job '{job_id}' not found or not accessible: {str(e)}")
968
1014
  except Exception as e:
@@ -985,6 +1031,12 @@ def job_workdir(ctx,
985
1031
  @click.option('--job-id',
986
1032
  help='The job id in CloudOS to search for.',
987
1033
  required=True)
1034
+ @click.option('--link',
1035
+ help='Link the logs directories to an interactive session.',
1036
+ is_flag=True)
1037
+ @click.option('--session-id',
1038
+ help='The specific CloudOS interactive session id. Required when using --link flag.',
1039
+ required=False)
988
1040
  @click.option('--verbose',
989
1041
  help='Whether to print information messages or not.',
990
1042
  is_flag=True)
@@ -1001,6 +1053,8 @@ def job_logs(ctx,
1001
1053
  cloudos_url,
1002
1054
  workspace_id,
1003
1055
  job_id,
1056
+ link,
1057
+ session_id,
1004
1058
  verbose,
1005
1059
  disable_ssl_verification,
1006
1060
  ssl_cert,
@@ -1028,17 +1082,30 @@ def job_logs(ctx,
1028
1082
  required_dict=required_dict,
1029
1083
  apikey=apikey,
1030
1084
  cloudos_url=cloudos_url,
1031
- workspace_id=workspace_id
1085
+ workspace_id=workspace_id,
1086
+ session_id=session_id
1032
1087
  )
1033
1088
  )
1034
1089
  apikey = user_options['apikey']
1035
1090
  cloudos_url = user_options['cloudos_url']
1036
1091
  workspace_id = user_options['workspace_id']
1092
+ # Get session_id from user_options (which includes profile data)
1093
+ session_id = user_options.get('session_id') or session_id
1094
+
1095
+ # Validate link flag requirements AFTER loading profile
1096
+ if link and not session_id:
1097
+ raise click.ClickException("--session-id is required when using --link flag")
1037
1098
 
1038
1099
  print('Executing logs...')
1039
1100
  verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
1040
1101
  if verbose:
1041
1102
  print('\t...Preparing objects')
1103
+ print('\tUsing the following parameters:')
1104
+ print(f'\t\tCloudOS url: {cloudos_url}')
1105
+ print(f'\t\tWorkspace ID: {workspace_id}')
1106
+ print(f'\t\tJob ID: {job_id}')
1107
+ if link:
1108
+ print(f'\t\tSession ID: {session_id}')
1042
1109
  cl = Cloudos(cloudos_url, apikey, None)
1043
1110
  if verbose:
1044
1111
  print('\tThe following Cloudos object was created:')
@@ -1047,7 +1114,37 @@ def job_logs(ctx,
1047
1114
  try:
1048
1115
  logs = cl.get_job_logs(job_id, workspace_id, verify_ssl)
1049
1116
  for name, path in logs.items():
1050
- print(f"{name}: {path}\n")
1117
+ print(f"{name}: {path}")
1118
+
1119
+ # Link to interactive session if requested
1120
+ if link:
1121
+ if logs:
1122
+ # Extract the parent logs directory from any log file path
1123
+ # All log files should be in the same logs directory
1124
+ first_log_path = next(iter(logs.values()))
1125
+ # Remove the filename to get the logs directory
1126
+ # e.g., "s3://bucket/path/to/logs/filename.txt" -> "s3://bucket/path/to/logs"
1127
+ logs_dir = '/'.join(first_log_path.split('/')[:-1])
1128
+
1129
+ if verbose:
1130
+ print(f'\tLinking logs directory to interactive session {session_id}...')
1131
+ print(f'\t\tLogs directory: {logs_dir}')
1132
+
1133
+ # Use Link class to perform the linking
1134
+ link_client = Link(
1135
+ cloudos_url=cloudos_url,
1136
+ apikey=apikey,
1137
+ cromwell_token=None, # Not needed for linking operations
1138
+ workspace_id=workspace_id,
1139
+ project_name=None, # Not needed for S3 paths
1140
+ verify=verify_ssl
1141
+ )
1142
+
1143
+ link_client.link_folder(logs_dir, session_id)
1144
+ else:
1145
+ if verbose:
1146
+ print('\tNo logs found to link.')
1147
+
1051
1148
  except BadRequestException as e:
1052
1149
  raise ValueError(f"Job '{job_id}' not found or not accessible: {str(e)}")
1053
1150
  except Exception as e:
@@ -1070,6 +1167,12 @@ def job_logs(ctx,
1070
1167
  @click.option('--job-id',
1071
1168
  help='The job id in CloudOS to search for.',
1072
1169
  required=True)
1170
+ @click.option('--link',
1171
+ help='Link the results directories to an interactive session.',
1172
+ is_flag=True)
1173
+ @click.option('--session-id',
1174
+ help='The specific CloudOS interactive session id. Required when using --link flag.',
1175
+ required=False)
1073
1176
  @click.option('--verbose',
1074
1177
  help='Whether to print information messages or not.',
1075
1178
  is_flag=True)
@@ -1086,6 +1189,8 @@ def job_results(ctx,
1086
1189
  cloudos_url,
1087
1190
  workspace_id,
1088
1191
  job_id,
1192
+ link,
1193
+ session_id,
1089
1194
  verbose,
1090
1195
  disable_ssl_verification,
1091
1196
  ssl_cert,
@@ -1113,17 +1218,30 @@ def job_results(ctx,
1113
1218
  required_dict=required_dict,
1114
1219
  apikey=apikey,
1115
1220
  cloudos_url=cloudos_url,
1116
- workspace_id=workspace_id
1221
+ workspace_id=workspace_id,
1222
+ session_id=session_id
1117
1223
  )
1118
1224
  )
1119
1225
  apikey = user_options['apikey']
1120
1226
  cloudos_url = user_options['cloudos_url']
1121
1227
  workspace_id = user_options['workspace_id']
1228
+ # Get session_id from user_options (which includes profile data)
1229
+ session_id = user_options.get('session_id') or session_id
1230
+
1231
+ # Validate link flag requirements AFTER loading profile
1232
+ if link and not session_id:
1233
+ raise click.ClickException("--session-id is required when using --link flag")
1122
1234
 
1123
1235
  print('Executing results...')
1124
1236
  verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
1125
1237
  if verbose:
1126
1238
  print('\t...Preparing objects')
1239
+ print('\tUsing the following parameters:')
1240
+ print(f'\t\tCloudOS url: {cloudos_url}')
1241
+ print(f'\t\tWorkspace ID: {workspace_id}')
1242
+ print(f'\t\tJob ID: {job_id}')
1243
+ if link:
1244
+ print(f'\t\tSession ID: {session_id}')
1127
1245
  cl = Cloudos(cloudos_url, apikey, None)
1128
1246
  if verbose:
1129
1247
  print('\tThe following Cloudos object was created:')
@@ -1132,7 +1250,28 @@ def job_results(ctx,
1132
1250
  try:
1133
1251
  results = cl.get_job_results(job_id, workspace_id, verify_ssl)
1134
1252
  for name, path in results.items():
1135
- print(f"{name}: {path}\n")
1253
+ print(f"{name}: {path}")
1254
+
1255
+ # Link to interactive session if requested
1256
+ if link:
1257
+ if verbose:
1258
+ print(f'\tLinking {len(results)} result directories to interactive session {session_id}...')
1259
+
1260
+ # Use Link class to perform the linking
1261
+ link_client = Link(
1262
+ cloudos_url=cloudos_url,
1263
+ apikey=apikey,
1264
+ cromwell_token=None, # Not needed for linking operations
1265
+ workspace_id=workspace_id,
1266
+ project_name=None, # Not needed for S3 paths
1267
+ verify=verify_ssl
1268
+ )
1269
+
1270
+ for name, path in results.items():
1271
+ if verbose:
1272
+ print(f'\t\tLinking {name} ({path})...')
1273
+ link_client.link_folder(path, session_id)
1274
+
1136
1275
  except BadRequestException as e:
1137
1276
  raise ValueError(f"Job '{job_id}' not found or not accessible: {str(e)}")
1138
1277
  except Exception as e:
@@ -1149,6 +1288,9 @@ def job_results(ctx,
1149
1288
  help=(f'The CloudOS url you are trying to access to. Default={CLOUDOS_URL}.'),
1150
1289
  default=CLOUDOS_URL,
1151
1290
  required=True)
1291
+ @click.option('--workspace-id',
1292
+ help='The specific CloudOS workspace id.',
1293
+ required=True)
1152
1294
  @click.option('--job-id',
1153
1295
  help='The job id in CloudOS to search for.',
1154
1296
  required=True)
@@ -1179,6 +1321,7 @@ def job_results(ctx,
1179
1321
  def job_details(ctx,
1180
1322
  apikey,
1181
1323
  cloudos_url,
1324
+ workspace_id,
1182
1325
  job_id,
1183
1326
  output_format,
1184
1327
  output_basename,
@@ -1193,7 +1336,7 @@ def job_details(ctx,
1193
1336
  # Create a dictionary with required and non-required params
1194
1337
  required_dict = {
1195
1338
  'apikey': True,
1196
- 'workspace_id': False,
1339
+ 'workspace_id': True,
1197
1340
  'workflow_name': False,
1198
1341
  'project_name': False,
1199
1342
  'session_id': False,
@@ -1207,6 +1350,7 @@ def job_details(ctx,
1207
1350
  INIT_PROFILE,
1208
1351
  CLOUDOS_URL,
1209
1352
  profile=profile,
1353
+ workspace_id=workspace_id,
1210
1354
  required_dict=required_dict,
1211
1355
  apikey=apikey,
1212
1356
  cloudos_url=cloudos_url
@@ -1214,6 +1358,7 @@ def job_details(ctx,
1214
1358
  )
1215
1359
  apikey = user_options['apikey']
1216
1360
  cloudos_url = user_options['cloudos_url']
1361
+ workspace_id = user_options['workspace_id']
1217
1362
 
1218
1363
  if ctx.get_parameter_source('output_basename') == click.core.ParameterSource.DEFAULT:
1219
1364
  output_basename = f"{job_id}_details"
@@ -1230,7 +1375,7 @@ def job_details(ctx,
1230
1375
 
1231
1376
  # check if the API gives a 403 error/forbidden error
1232
1377
  try:
1233
- j_details = cl.get_job_status(job_id, verify_ssl)
1378
+ j_details = cl.get_job_status(job_id, workspace_id, verify_ssl)
1234
1379
  except BadRequestException as e:
1235
1380
  if '403' in str(e) or 'Forbidden' in str(e):
1236
1381
  raise ValueError("API can only show job details of your own jobs, cannot see other user's job details.")
@@ -1518,7 +1663,7 @@ def abort_jobs(ctx,
1518
1663
 
1519
1664
  for job in jobs:
1520
1665
  try:
1521
- j_status = cl.get_job_status(job, verify_ssl)
1666
+ j_status = cl.get_job_status(job, None, verify_ssl)
1522
1667
  except Exception as e:
1523
1668
  click.secho(f"Failed to get status for job {job}, please make sure it exists in the workspace: {e}", fg='yellow', bold=True)
1524
1669
  continue
@@ -2823,6 +2968,7 @@ def run_bash_job(ctx,
2823
2968
  print('\tPlease, wait until job completion (max wait time of ' +
2824
2969
  f'{wait_time} seconds).\n')
2825
2970
  j_status = j.wait_job_completion(job_id=j_id,
2971
+ workspace_id=workspace_id,
2826
2972
  wait_time=wait_time,
2827
2973
  request_interval=request_interval,
2828
2974
  verbose=False,
@@ -2836,7 +2982,7 @@ def run_bash_job(ctx,
2836
2982
  print(f'\nJob status for job "{j_name}" (ID: {j_id}): {j_final_s}')
2837
2983
  sys.exit(1)
2838
2984
  else:
2839
- j_status = j.get_job_status(j_id, verify_ssl)
2985
+ j_status = j.get_job_status(j_id, workspace_id, verify_ssl)
2840
2986
  j_status_h = json.loads(j_status.content)["status"]
2841
2987
  print(f'\tYour current job status is: {j_status_h}')
2842
2988
  print('\tTo further check your job status you can either go to ' +
@@ -3191,6 +3337,7 @@ def run_bash_array_job(ctx,
3191
3337
  print('\tPlease, wait until job completion (max wait time of ' +
3192
3338
  f'{wait_time} seconds).\n')
3193
3339
  j_status = j.wait_job_completion(job_id=j_id,
3340
+ workspace_id=workspace_id,
3194
3341
  wait_time=wait_time,
3195
3342
  request_interval=request_interval,
3196
3343
  verbose=False,
@@ -3204,7 +3351,7 @@ def run_bash_array_job(ctx,
3204
3351
  print(f'\nJob status for job "{j_name}" (ID: {j_id}): {j_final_s}')
3205
3352
  sys.exit(1)
3206
3353
  else:
3207
- j_status = j.get_job_status(j_id, verify_ssl)
3354
+ j_status = j.get_job_status(j_id, workspace_id, verify_ssl)
3208
3355
  j_status_h = json.loads(j_status.content)["status"]
3209
3356
  print(f'\tYour current job status is: {j_status_h}')
3210
3357
  print('\tTo further check your job status you can either go to ' +
@@ -3317,6 +3464,25 @@ def list_files(ctx,
3317
3464
  is_folder = "folderType" in item or item.get("isDir", False)
3318
3465
  type_ = "folder" if is_folder else "file"
3319
3466
 
3467
+ # Enhanced type information
3468
+ if is_folder:
3469
+ folder_type = item.get("folderType")
3470
+ if folder_type == "VirtualFolder":
3471
+ type_ = "virtual folder"
3472
+ elif folder_type == "S3Folder":
3473
+ type_ = "s3 folder"
3474
+ elif folder_type == "AzureBlobFolder":
3475
+ type_ = "azure folder"
3476
+ else:
3477
+ type_ = "folder"
3478
+ else:
3479
+ # Check if file is managed by Lifebit (user uploaded)
3480
+ is_managed_by_lifebit = item.get("isManagedByLifebit", False)
3481
+ if is_managed_by_lifebit:
3482
+ type_ = "file (user uploaded)"
3483
+ else:
3484
+ type_ = "file (virtual copy)"
3485
+
3320
3486
  user = item.get("user", {})
3321
3487
  if isinstance(user, dict):
3322
3488
  name = user.get("name", "").strip()
@@ -0,0 +1 @@
1
+ __version__ = '2.64.1'
@@ -37,13 +37,15 @@ class Cloudos:
37
37
  apikey: str
38
38
  cromwell_token: str
39
39
 
40
- def get_job_status(self, j_id, verify=True):
40
+ def get_job_status(self, j_id, workspace_id=None, verify=True):
41
41
  """Get job status from CloudOS.
42
42
 
43
43
  Parameters
44
44
  ----------
45
45
  j_id : string
46
46
  The CloudOS job id of the job just launched.
47
+ workspace_id : string
48
+ The CloudOS workspace id from to check the job status.
47
49
  verify: [bool|string]
48
50
  Whether to use SSL verification or not. Alternatively, if
49
51
  a string is passed, it will be interpreted as the path to
@@ -60,21 +62,30 @@ class Cloudos:
60
62
  "Content-type": "application/json",
61
63
  "apikey": apikey
62
64
  }
63
- r = retry_requests_get("{}/api/v1/jobs/{}".format(cloudos_url,
64
- j_id),
65
- headers=headers, verify=verify)
66
- if r.status_code >= 400:
65
+ if workspace_id is not None:
66
+ url = f"{cloudos_url}/api/v1/jobs/{j_id}?teamId={workspace_id}"
67
+ else:
68
+ url = f"{cloudos_url}/api/v1/jobs/{j_id}"
69
+ r = retry_requests_get(url, headers=headers, verify=verify)
70
+ if r.status_code == 401:
71
+ raise NotAuthorisedException
72
+ elif r.status_code == 403:
73
+ # Handle 403 with more informative error message
74
+ self._handle_job_access_denied(j_id, workspace_id, verify)
75
+ elif r.status_code >= 400:
67
76
  raise BadRequestException(r)
68
77
  return r
69
78
 
70
- def wait_job_completion(self, job_id, wait_time=3600, request_interval=30, verbose=False,
79
+ def wait_job_completion(self, job_id, workspace_id, wait_time=3600, request_interval=30, verbose=False,
71
80
  verify=True):
72
81
  """Checks job status from CloudOS and wait for its complation.
73
82
 
74
83
  Parameters
75
84
  ----------
76
- j_id : string
85
+ job_id : string
77
86
  The CloudOS job id of the job just launched.
87
+ workspace_id : string
88
+ The CloudOS workspace id from to check the job status.
78
89
  wait_time : int
79
90
  Max time to wait (in seconds) to job completion.
80
91
  request_interval : int
@@ -98,7 +109,7 @@ class Cloudos:
98
109
  if request_interval > wait_time:
99
110
  request_interval = wait_time
100
111
  while elapsed < wait_time:
101
- j_status = self.get_job_status(job_id, verify)
112
+ j_status = self.get_job_status(job_id, workspace_id, verify)
102
113
  j_status_content = json.loads(j_status.content)
103
114
  j_status_h = j_status_content["status"]
104
115
  j_name = j_status_content["name"]
@@ -122,7 +133,7 @@ class Cloudos:
122
133
  print(f'\tYour current job "{j_name}" (ID: {job_id}) status is: {j_status_h}.')
123
134
  j_status_h_old = j_status_h
124
135
  time.sleep(request_interval)
125
- j_status = self.get_job_status(job_id, verify)
136
+ j_status = self.get_job_status(job_id, workspace_id, verify)
126
137
  j_status_content = json.loads(j_status.content)
127
138
  j_status_h = j_status_content["status"]
128
139
  j_name = j_status_content["name"]
@@ -202,21 +213,17 @@ class Cloudos:
202
213
  "Content-type": "application/json",
203
214
  "apikey": apikey
204
215
  }
205
- r = retry_requests_get(f"{cloudos_url}/api/v1/jobs/{j_id}", headers=headers, verify=verify)
206
- if r.status_code == 401:
207
- raise NotAuthorisedException
208
- elif r.status_code == 403:
209
- # Handle 403 with more informative error message
210
- self._handle_job_access_denied(j_id, workspace_id, verify)
211
- elif r.status_code >= 400:
212
- raise BadRequestException(r)
216
+ r = self.get_job_status(j_id, workspace_id, verify)
213
217
  r_json = r.json()
214
218
  job_workspace = r_json["team"]
215
219
  if job_workspace != workspace_id:
216
220
  raise ValueError("Workspace provided or configured is different from workspace where the job was executed")
221
+ if r_json["status"] =='initializing' or r_json["status"] =='scheduled':
222
+ raise ValueError("Working directories are not yet available. The job is still initializing.")
217
223
 
218
224
  if "resumeWorkDir" not in r_json:
219
- print("Working directories are not available. This may be because the analysis was run without resumable mode enabled, or because intermediate results have since been removed.")
225
+ raise ValueError("Working directories are not available. This may be because the analysis was run without resumable mode enabled, or because intermediate results have since been removed.")
226
+
220
227
  # Check if logs field exists, if not fall back to original folder-based approach
221
228
  elif "logs" in r_json:
222
229
  # Get workdir information from logs object using the same pattern as get_job_logs
@@ -322,38 +329,40 @@ class Cloudos:
322
329
  "Content-type": "application/json",
323
330
  "apikey": apikey
324
331
  }
325
- r = retry_requests_get(f"{cloudos_url}/api/v1/jobs/{j_id}", headers=headers, verify=verify)
326
- if r.status_code == 401:
327
- raise NotAuthorisedException
328
- elif r.status_code >= 400:
329
- raise BadRequestException(r)
332
+ r = self.get_job_status(j_id, workspace_id, verify)
330
333
  r_json = r.json()
331
- logs_obj = r_json["logs"]
334
+
332
335
  job_workspace = r_json["team"]
333
336
  if job_workspace != workspace_id:
334
337
  raise ValueError("Workspace provided or configured is different from workspace where the job was executed")
335
- cloud_name, cloud_meta, cloud_storage = find_cloud(self.cloudos_url, self.apikey, workspace_id, logs_obj)
336
- container_name = cloud_storage["container"]
337
- prefix_name = cloud_storage["prefix"]
338
- logs_bucket = logs_obj[container_name]
339
- logs_path = logs_obj[prefix_name]
340
- contents_obj = self.get_storage_contents(cloud_name, cloud_meta, logs_bucket, logs_path, workspace_id, verify)
341
- logs = {}
342
- cloude_scheme = cloud_storage["scheme"]
343
- storage_account_prefix = ''
344
- if cloude_scheme == 'az':
345
- storage_account_prefix = f'{workspace_id}.blob.core.windows.net/'
346
- for item in contents_obj:
347
- if not item["isDir"]:
348
- filename = item["name"]
349
- if filename == "stdout.txt":
350
- filename = "Nextflow standard output"
351
- if filename == ".nextflow.log":
352
- filename = "Nextflow log"
353
- if filename == "trace.txt":
354
- filename = "Trace file"
355
- logs[filename] = f"{cloude_scheme}://{storage_account_prefix}{logs_bucket}/{item['path']}"
356
- return logs
338
+ if r_json["status"] =='initializing' or r_json["status"] =='scheduled':
339
+ raise ValueError("Logs are not yet available. The job is still initializing.")
340
+ if "logs" not in r_json:
341
+ raise ValueError("ERROR: Logs are not available.")
342
+ else:
343
+ logs_obj = r_json["logs"]
344
+ cloud_name, cloud_meta, cloud_storage = find_cloud(self.cloudos_url, self.apikey, workspace_id, logs_obj)
345
+ container_name = cloud_storage["container"]
346
+ prefix_name = cloud_storage["prefix"]
347
+ logs_bucket = logs_obj[container_name]
348
+ logs_path = logs_obj[prefix_name]
349
+ contents_obj = self.get_storage_contents(cloud_name, cloud_meta, logs_bucket, logs_path, workspace_id, verify)
350
+ logs = {}
351
+ cloude_scheme = cloud_storage["scheme"]
352
+ storage_account_prefix = ''
353
+ if cloude_scheme == 'az':
354
+ storage_account_prefix = f'{workspace_id}.blob.core.windows.net/'
355
+ for item in contents_obj:
356
+ if not item["isDir"]:
357
+ filename = item["name"]
358
+ if filename == "stdout.txt":
359
+ filename = "Nextflow standard output"
360
+ if filename == ".nextflow.log":
361
+ filename = "Nextflow log"
362
+ if filename == "trace.txt":
363
+ filename = "Trace file"
364
+ logs[filename] = f"{cloude_scheme}://{storage_account_prefix}{logs_bucket}/{item['path']}"
365
+ return logs
357
366
 
358
367
  def get_job_results(self, j_id, workspace_id, verify=True):
359
368
  """
@@ -365,16 +374,11 @@ class Cloudos:
365
374
  "Content-type": "application/json",
366
375
  "apikey": apikey
367
376
  }
368
- status = self.get_job_status(j_id, verify).json()["status"]
377
+ status = self.get_job_status(j_id, workspace_id, verify).json()["status"]
369
378
  if status != JOB_COMPLETED:
370
379
  raise JoBNotCompletedException(j_id, status)
371
380
 
372
- r = retry_requests_get(f"{cloudos_url}/api/v1/jobs/{j_id}",
373
- headers=headers, verify=verify)
374
- if r.status_code == 401:
375
- raise NotAuthorisedException
376
- if r.status_code >= 400:
377
- raise BadRequestException(r)
381
+ r = self.get_job_status(j_id, workspace_id, verify)
378
382
  req_obj = r.json()
379
383
  job_workspace = req_obj["team"]
380
384
  if job_workspace != workspace_id:
@@ -9,6 +9,7 @@ from cloudos_cli.utils.errors import BadRequestException
9
9
  from cloudos_cli.utils.requests import retry_requests_get, retry_requests_put, retry_requests_post, retry_requests_delete
10
10
  import json
11
11
 
12
+
12
13
  @dataclass
13
14
  class Datasets(Cloudos):
14
15
  """Class for file explorer.
@@ -105,7 +106,7 @@ class Datasets(Cloudos):
105
106
  # Normalize response
106
107
  for item in datasets:
107
108
  item["folderType"] = True
108
- response ={
109
+ response = {
109
110
  "folders": datasets,
110
111
  "files": []
111
112
  }
@@ -144,7 +145,7 @@ class Datasets(Cloudos):
144
145
  r = retry_requests_get("{}/api/v1/datasets/{}/items?teamId={}".format(self.cloudos_url,
145
146
  folder_id,
146
147
  self.workspace_id),
147
- headers=headers, verify=self.verify)
148
+ headers=headers, verify=self.verify)
148
149
  if r.status_code >= 400:
149
150
  raise BadRequestException(r)
150
151
  return r.json()
@@ -175,7 +176,7 @@ class Datasets(Cloudos):
175
176
  s3_bucket_name,
176
177
  s3_relative_path,
177
178
  self.workspace_id),
178
- headers=headers, verify=self.verify)
179
+ headers=headers, verify=self.verify)
179
180
  if r.status_code >= 400:
180
181
  raise BadRequestException(r)
181
182
  raw = r.json()
@@ -218,11 +219,11 @@ class Datasets(Cloudos):
218
219
  r = retry_requests_get("{}/api/v1/folders/virtual/{}/items?teamId={}".format(self.cloudos_url,
219
220
  folder_id,
220
221
  self.workspace_id),
221
- headers=headers, verify=self.verify)
222
+ headers=headers, verify=self.verify)
222
223
  if r.status_code >= 400:
223
224
  raise BadRequestException(r)
224
225
  return r.json()
225
-
226
+
226
227
  def list_azure_container_content(self, container_name: str, storage_account_name: str, path: str):
227
228
  """
228
229
  List contents of an Azure Blob container path.
@@ -259,7 +260,7 @@ class Datasets(Cloudos):
259
260
  "blobPrefix": path_str,
260
261
  "blobContainerName": container_name,
261
262
  "blobStorageAccountName": storage_account_name,
262
- "kind": "Folder"
263
+ "kind": "Folder"
263
264
  })
264
265
  else:
265
266
  normalized["files"].append({
@@ -271,7 +272,7 @@ class Datasets(Cloudos):
271
272
  "blobStorageAccountName": storage_account_name,
272
273
  "sizeInBytes": item.get("size", 0),
273
274
  "updatedAt": item.get("lastModified"),
274
- "kind": "File"
275
+ "kind": "File"
275
276
  })
276
277
 
277
278
  return normalized
@@ -337,7 +338,7 @@ class Datasets(Cloudos):
337
338
  container_name = job_folder['blobContainerName']
338
339
  storage_account_name = job_folder['blobStorageAccountName']
339
340
  blob_prefix = job_folder['blobPrefix']
340
- # trailing slash is mandatory for azure, otherwise it will not list the content of thefolde, just the folder
341
+ # trailing slash is mandatory for azure, otherwise it will not list the content of thefolde, just the folder
341
342
  if not blob_prefix.endswith('/'):
342
343
  blob_prefix += '/'
343
344
 
@@ -355,7 +356,7 @@ class Datasets(Cloudos):
355
356
  raise ValueError(f"Folder '{job_name}' not found under dataset '{dataset_name}'")
356
357
 
357
358
  return folder_content
358
-
359
+
359
360
  def move_files_and_folders(self, source_id: str, source_kind: str, target_id: str, target_kind: str):
360
361
  """
361
362
  Move a file to another dataset in CloudOS.
@@ -432,7 +433,7 @@ class Datasets(Cloudos):
432
433
  if response.status_code >= 400:
433
434
  raise BadRequestException(response)
434
435
  return response
435
-
436
+
436
437
  def copy_item(self, item, destination_id, destination_kind):
437
438
  """Copy a file or folder (S3, Azure or Virtual) to a destination in CloudOS."""
438
439
  headers = {
@@ -454,7 +455,7 @@ class Datasets(Cloudos):
454
455
  elif item.get("folderType") == "S3Folder":
455
456
  payload = {
456
457
  "s3BucketName": item["s3BucketName"],
457
- "s3ObjectKey": item.get("s3ObjectKey") or item.get("s3Prefix"),
458
+ "s3Prefix": item.get("s3Prefix"),
458
459
  "name": item["name"],
459
460
  "parent": parent,
460
461
  "isManagedByLifebit": item.get("isManagedByLifebit", False)
@@ -500,7 +501,7 @@ class Datasets(Cloudos):
500
501
  if response.status_code >= 400:
501
502
  raise BadRequestException(response)
502
503
  return response
503
-
504
+
504
505
  def create_virtual_folder(self, name: str, parent_id: str, parent_kind: str):
505
506
  """
506
507
  Create a new virtual folder in CloudOS under a given parent.
@@ -542,7 +543,7 @@ class Datasets(Cloudos):
542
543
  if response.status_code >= 400:
543
544
  raise BadRequestException(response)
544
545
  return response
545
-
546
+
546
547
  def delete_item(self, item_id: str, kind: str):
547
548
  """
548
549
  Delete a file or folder in CloudOS.
@@ -573,4 +574,4 @@ class Datasets(Cloudos):
573
574
  response = retry_requests_delete(url, headers=headers, verify=self.verify)
574
575
  if response.status_code >= 400:
575
576
  raise BadRequestException(response)
576
- return response
577
+ return response
@@ -68,6 +68,7 @@ class Link(Cloudos):
68
68
  data = self.parse_file_explorer_path(folder)
69
69
  type_folder = "File Explorer"
70
70
  r = retry_requests_post(url, headers=headers, json=data, verify=self.verify)
71
+
71
72
  if r.status_code == 403:
72
73
  raise ValueError(f"Provided {type_folder} folder already exists with 'mounted' status")
73
74
  elif r.status_code == 401:
@@ -1,3 +1,38 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloudos_cli
3
+ Version: 2.64.1
4
+ Summary: Python package for interacting with CloudOS
5
+ Home-page: https://github.com/lifebit-ai/cloudos-cli
6
+ Author: David Piñeyro
7
+ Author-email: david.pineyro@lifebit.ai
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: click>=8.0.1
15
+ Requires-Dist: rich-click>=1.8.2
16
+ Requires-Dist: pandas>=1.3.4
17
+ Requires-Dist: numpy>=1.26.4
18
+ Requires-Dist: requests>=2.26.0
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest; extra == "test"
21
+ Requires-Dist: mock; extra == "test"
22
+ Requires-Dist: responses; extra == "test"
23
+ Requires-Dist: requests_mock; extra == "test"
24
+ Dynamic: author
25
+ Dynamic: author-email
26
+ Dynamic: classifier
27
+ Dynamic: description
28
+ Dynamic: description-content-type
29
+ Dynamic: home-page
30
+ Dynamic: license-file
31
+ Dynamic: provides-extra
32
+ Dynamic: requires-dist
33
+ Dynamic: requires-python
34
+ Dynamic: summary
35
+
1
36
  # cloudos-cli
2
37
 
3
38
  [![CI_tests](https://github.com/lifebit-ai/cloudos-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/lifebit-ai/cloudos-cli/actions/workflows/ci.yml)
@@ -608,6 +643,12 @@ Nextflow standard output: s3://path/to/location/of/logs/stdout.txt
608
643
  Trace file: s3://path/to/location/of/logs/trace.txt
609
644
  ```
610
645
 
646
+ You can also link the logs directory to an interactive session using the `--link` flag. This will mount the entire logs directory, providing access to all log files in your interactive session:
647
+
648
+ ```bash
649
+ cloudos job logs --profile my_profile --job-id "12345678910" --link --session-id your_session_id
650
+ ```
651
+
611
652
  #### Get Job Results
612
653
 
613
654
  The following command allows you to get the path where CloudOS stores the output files for a job. This can be used only on your user's jobs and for jobs with "completed" status.
@@ -621,6 +662,12 @@ Executing results...
621
662
  results: s3://path/to/location/of/results/results/
622
663
  ```
623
664
 
665
+ You can also link all result directories to an interactive session using the `--link` flag. This will mount all result directories from the job, providing direct access to output files in your interactive session:
666
+
667
+ ```bash
668
+ cloudos job results --profile my_profile --job-id "12345678910" --link --session-id your_session_id
669
+ ```
670
+
624
671
 
625
672
  #### Clone or Resume job
626
673
 
@@ -873,6 +920,15 @@ Finding working directory path...
873
920
  Working directory for job 68747bac9e7fe38ec6e022ad: az://123456789000.blob.core.windows.net/cloudos-987652349087/projects/455654676/jobs/54678856765/work
874
921
  ```
875
922
 
923
+ You can also link the working directory to an interactive session using the `--link` flag. This requires specifying a session ID either through the `--session-id` option or from a configured profile:
924
+
925
+ ```shell
926
+ cloudos job workdir \
927
+ --profile profile-name \
928
+ --job-id 62c83a1191fe06013b7ef355 \
929
+ --link --session-id your_session_id
930
+ ```
931
+
876
932
  #### List Jobs
877
933
 
878
934
  You can get a summary of workspace jobs in two different formats:
@@ -1352,7 +1408,7 @@ The output of this command is a list of files and folders present in the specifi
1352
1408
  > If the `<path>` is left empty, the command will return the list of folders present in the selected project.
1353
1409
 
1354
1410
  If you require more information on the files and folder listed, you can use the `--details` flag that will output a table containing the following columns:
1355
- - Type (folder or file)
1411
+ - Type (s3 folder, azure folder , virtual folder, file (user uploaded) or file (virtual copy))
1356
1412
  - Owner
1357
1413
  - Size (in human readable format)
1358
1414
  - Last updated
@@ -1741,7 +1797,7 @@ j_id = j.send_job(job_config)
1741
1797
  To check the status:
1742
1798
 
1743
1799
  ```python
1744
- j_status = j.get_job_status(j_id)
1800
+ j_status = j.get_job_status(j_id, workspace_id)
1745
1801
  j_status_h = json.loads(j_status.content)["status"]
1746
1802
  print(j_status_h)
1747
1803
  ```
@@ -1816,7 +1872,7 @@ print(c_status_h)
1816
1872
  j = jb.Job(cloudos_url, apikey, None, workspace_id, project_name, workflow_name, True, mainfile,
1817
1873
  importsfile)
1818
1874
  j_id = j.send_job(job_config, workflow_type='wdl', cromwell_id=json.loads(c_status.content)["_id"])
1819
- j_status = j.get_job_status(j_id)
1875
+ j_status = j.get_job_status(j_id, workspace_id)
1820
1876
  j_status_h = json.loads(j_status.content)["status"]
1821
1877
  print(j_status_h)
1822
1878
 
@@ -1 +0,0 @@
1
- __version__ = '2.62.0'
File without changes
File without changes
File without changes