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.
- {cloudos_cli-2.62.0/cloudos_cli.egg-info → cloudos_cli-2.64.1}/PKG-INFO +25 -4
- cloudos_cli-2.62.0/PKG-INFO → cloudos_cli-2.64.1/README.md +24 -38
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/__main__.py +179 -13
- cloudos_cli-2.64.1/cloudos_cli/_version.py +1 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/clos.py +57 -53
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/datasets/datasets.py +15 -14
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/link/link.py +1 -0
- cloudos_cli-2.62.0/README.md → cloudos_cli-2.64.1/cloudos_cli.egg-info/PKG-INFO +59 -3
- cloudos_cli-2.62.0/cloudos_cli/_version.py +0 -1
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/LICENSE +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/configure/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/configure/configure.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/cost/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/cost/cost.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/datasets/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/import_wf/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/import_wf/import_wf.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/jobs/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/jobs/job.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/link/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/logging/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/logging/logger.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/procurement/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/procurement/images.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/queue/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/queue/queue.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/array_job.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/cloud.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/details.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/errors.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/last_wf.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/requests.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli/utils/resources.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/SOURCES.txt +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/dependency_links.txt +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/entry_points.txt +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/requires.txt +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/cloudos_cli.egg-info/top_level.txt +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/setup.cfg +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/setup.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/functions_for_pytest.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_cli_project_create.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_cost/__init__.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_cost/test_job_cost.py +0 -0
- {cloudos_cli-2.62.0 → cloudos_cli-2.64.1}/tests/test_logging/__init__.py +0 -0
- {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.
|
|
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
|
[](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':
|
|
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}
|
|
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}
|
|
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':
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if
|
|
354
|
-
filename = "
|
|
355
|
-
|
|
356
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
[](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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|