cloudos-cli 2.50.0__tar.gz → 2.53.0__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 (41) hide show
  1. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/PKG-INFO +47 -1
  2. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/README.md +46 -0
  3. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/__main__.py +211 -3
  4. cloudos_cli-2.53.0/cloudos_cli/_version.py +1 -0
  5. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/clos.py +162 -34
  6. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/jobs/job.py +243 -0
  7. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/procurement/images.py +1 -0
  8. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli.egg-info/PKG-INFO +47 -1
  9. cloudos_cli-2.50.0/cloudos_cli/_version.py +0 -1
  10. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/LICENSE +0 -0
  11. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/__init__.py +0 -0
  12. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/configure/__init__.py +0 -0
  13. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/configure/configure.py +0 -0
  14. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/datasets/__init__.py +0 -0
  15. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/datasets/datasets.py +0 -0
  16. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/import_wf/__init__.py +0 -0
  17. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/import_wf/import_wf.py +0 -0
  18. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/jobs/__init__.py +0 -0
  19. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/link/__init__.py +0 -0
  20. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/link/link.py +0 -0
  21. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/procurement/__init__.py +0 -0
  22. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/queue/__init__.py +0 -0
  23. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/queue/queue.py +0 -0
  24. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/utils/__init__.py +0 -0
  25. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/utils/array_job.py +0 -0
  26. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/utils/cloud.py +0 -0
  27. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/utils/details.py +0 -0
  28. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/utils/errors.py +0 -0
  29. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/utils/last_wf.py +0 -0
  30. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/utils/requests.py +0 -0
  31. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli/utils/resources.py +0 -0
  32. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli.egg-info/SOURCES.txt +0 -0
  33. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli.egg-info/dependency_links.txt +0 -0
  34. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli.egg-info/entry_points.txt +0 -0
  35. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli.egg-info/requires.txt +0 -0
  36. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/cloudos_cli.egg-info/top_level.txt +0 -0
  37. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/setup.cfg +0 -0
  38. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/setup.py +0 -0
  39. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/tests/__init__.py +0 -0
  40. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/tests/functions_for_pytest.py +0 -0
  41. {cloudos_cli-2.50.0 → cloudos_cli-2.53.0}/tests/test_cli_project_create.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudos_cli
3
- Version: 2.50.0
3
+ Version: 2.53.0
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
@@ -615,6 +615,52 @@ Aborting jobs...
615
615
  ```
616
616
 
617
617
 
618
+ #### Clone a job with optional parameter overrides
619
+
620
+ The `clone` command allows you to create a new job based on an existing job's configuration, with the ability to override specific parameters. This is useful for re-running jobs with slight modifications without having to specify all parameters from scratch.
621
+
622
+ Basic usage:
623
+ ```console
624
+ cloudos job clone \
625
+ --profile MY_PROFILE
626
+ --job-id "60a7b8c9d0e1f2g3h4i5j6k7"
627
+ ```
628
+
629
+ Clone with parameter overrides:
630
+ ```console
631
+ cloudos job clone \
632
+ --profile MY_PROFILE
633
+ --job-id "60a7b8c9d0e1f2g3h4i5j6k7" \
634
+ --job-queue "high-priority-queue" \
635
+ --cost-limit 50.0 \
636
+ --instance-type "c5.2xlarge" \
637
+ --job-name "cloned_analysis_v2" \
638
+ --nextflow-version "24.04.4" \
639
+ --git-branch "dev" \
640
+ --nextflow-profile "production" \
641
+ --do-not-save-logs true \
642
+ --accelerate-file-staging true \
643
+ --workflow-name "updated-workflow" \
644
+ -p "input=s3://new-bucket/input.csv" \
645
+ -p "output_dir=s3://new-bucket/results"
646
+ ```
647
+
648
+ Available override options:
649
+ - `--job-queue`: Specify a different job queue
650
+ - `--cost-limit`: Set a new cost limit (use -1 for no limit)
651
+ - `--instance-type`: Change the master instance type
652
+ - `--job-name`: Assign a custom name to the cloned job
653
+ - `--nextflow-version`: Use a different Nextflow version
654
+ - `--git-branch`: Switch to a different git branch
655
+ - `--nextflow-profile`: Change the Nextflow profile
656
+ - `--do-not-save-logs`: Enable/disable log saving
657
+ - `--accelerate-file-staging`: Enable/disable fusion filesystem
658
+ - `--workflow-name`: Use a different workflow
659
+ - `-p, --parameter`: Override or add parameters (can be used multiple times)
660
+
661
+ > [!NOTE]
662
+ > Parameters can be overridden or new ones can be added using `-p` option
663
+
618
664
  #### Executor support
619
665
 
620
666
  CloudOS supports [AWS batch](https://www.nextflow.io/docs/latest/executor.html?highlight=executors#aws-batch) executor by default.
@@ -580,6 +580,52 @@ Aborting jobs...
580
580
  ```
581
581
 
582
582
 
583
+ #### Clone a job with optional parameter overrides
584
+
585
+ The `clone` command allows you to create a new job based on an existing job's configuration, with the ability to override specific parameters. This is useful for re-running jobs with slight modifications without having to specify all parameters from scratch.
586
+
587
+ Basic usage:
588
+ ```console
589
+ cloudos job clone \
590
+ --profile MY_PROFILE
591
+ --job-id "60a7b8c9d0e1f2g3h4i5j6k7"
592
+ ```
593
+
594
+ Clone with parameter overrides:
595
+ ```console
596
+ cloudos job clone \
597
+ --profile MY_PROFILE
598
+ --job-id "60a7b8c9d0e1f2g3h4i5j6k7" \
599
+ --job-queue "high-priority-queue" \
600
+ --cost-limit 50.0 \
601
+ --instance-type "c5.2xlarge" \
602
+ --job-name "cloned_analysis_v2" \
603
+ --nextflow-version "24.04.4" \
604
+ --git-branch "dev" \
605
+ --nextflow-profile "production" \
606
+ --do-not-save-logs true \
607
+ --accelerate-file-staging true \
608
+ --workflow-name "updated-workflow" \
609
+ -p "input=s3://new-bucket/input.csv" \
610
+ -p "output_dir=s3://new-bucket/results"
611
+ ```
612
+
613
+ Available override options:
614
+ - `--job-queue`: Specify a different job queue
615
+ - `--cost-limit`: Set a new cost limit (use -1 for no limit)
616
+ - `--instance-type`: Change the master instance type
617
+ - `--job-name`: Assign a custom name to the cloned job
618
+ - `--nextflow-version`: Use a different Nextflow version
619
+ - `--git-branch`: Switch to a different git branch
620
+ - `--nextflow-profile`: Change the Nextflow profile
621
+ - `--do-not-save-logs`: Enable/disable log saving
622
+ - `--accelerate-file-staging`: Enable/disable fusion filesystem
623
+ - `--workflow-name`: Use a different workflow
624
+ - `-p, --parameter`: Override or add parameters (can be used multiple times)
625
+
626
+ > [!NOTE]
627
+ > Parameters can be overridden or new ones can be added using `-p` option
628
+
583
629
  #### Executor support
584
630
 
585
631
  CloudOS supports [AWS batch](https://www.nextflow.io/docs/latest/executor.html?highlight=executors#aws-batch) executor by default.
@@ -101,7 +101,8 @@ def run_cloudos_cli(ctx, debug):
101
101
  'list': shared_config,
102
102
  'logs': shared_config,
103
103
  'results': shared_config,
104
- 'details': shared_config
104
+ 'details': shared_config,
105
+ 'clone': shared_config
105
106
  },
106
107
  'workflow': {
107
108
  'list': shared_config,
@@ -162,7 +163,8 @@ def run_cloudos_cli(ctx, debug):
162
163
  'list': shared_config,
163
164
  'logs': shared_config,
164
165
  'results': shared_config,
165
- 'details': shared_config
166
+ 'details': shared_config,
167
+ 'clone': shared_config
166
168
  },
167
169
  'workflow': {
168
170
  'list': shared_config,
@@ -1225,6 +1227,24 @@ def job_details(ctx,
1225
1227
  @click.option('--archived',
1226
1228
  help=('When this flag is used, only archived jobs list is collected.'),
1227
1229
  is_flag=True)
1230
+ @click.option('--filter-status',
1231
+ help='Filter jobs by status (e.g., completed, running, failed, aborted).')
1232
+ @click.option('--filter-job-name',
1233
+ help='Filter jobs by job name ( case insensitive ).')
1234
+ @click.option('--filter-project',
1235
+ help='Filter jobs by project name.')
1236
+ @click.option('--filter-workflow',
1237
+ help='Filter jobs by workflow/pipeline name.')
1238
+ @click.option('--last',
1239
+ help=('When workflows are duplicated, use the latest imported workflow (by date).'),
1240
+ is_flag=True)
1241
+ @click.option('--filter-job-id',
1242
+ help='Filter jobs by specific job ID.')
1243
+ @click.option('--filter-only-mine',
1244
+ help='Filter to show only jobs belonging to the current user.',
1245
+ is_flag=True)
1246
+ @click.option('--filter-queue',
1247
+ help='Filter jobs by queue name. Only applies to jobs running in batch environment. Non-batch jobs are preserved in results.')
1228
1248
  @click.option('--verbose',
1229
1249
  help='Whether to print information messages or not.',
1230
1250
  is_flag=True)
@@ -1246,6 +1266,15 @@ def list_jobs(ctx,
1246
1266
  last_n_jobs,
1247
1267
  page,
1248
1268
  archived,
1269
+ filter_status,
1270
+ filter_job_name,
1271
+ filter_project,
1272
+ filter_workflow,
1273
+ last,
1274
+ filter_job_id,
1275
+ filter_only_mine,
1276
+ #filter_owner,
1277
+ filter_queue,
1249
1278
  verbose,
1250
1279
  disable_ssl_verification,
1251
1280
  ssl_cert,
@@ -1300,7 +1329,15 @@ def list_jobs(ctx,
1300
1329
  print("[ERROR] last-n-jobs value was not valid. Please use a positive int or 'all'")
1301
1330
  raise
1302
1331
 
1303
- my_jobs_r = cl.get_job_list(workspace_id, last_n_jobs, page, archived, verify_ssl)
1332
+ my_jobs_r = cl.get_job_list(workspace_id, last_n_jobs, page, archived, verify_ssl,
1333
+ filter_status=filter_status,
1334
+ filter_job_name=filter_job_name,
1335
+ filter_project=filter_project,
1336
+ filter_workflow=filter_workflow,
1337
+ filter_job_id=filter_job_id,
1338
+ filter_only_mine=filter_only_mine,
1339
+ filter_queue=filter_queue,
1340
+ last=last)
1304
1341
  if len(my_jobs_r) == 0:
1305
1342
  if ctx.get_parameter_source('page') == click.core.ParameterSource.DEFAULT:
1306
1343
  print('\t[Message] A total of 0 jobs collected. This is likely because your workspace ' +
@@ -1419,6 +1456,177 @@ def abort_jobs(ctx,
1419
1456
  print(f"\tJob '{job}' aborted successfully.")
1420
1457
 
1421
1458
 
1459
+ @job.command('clone')
1460
+ @click.option('-k',
1461
+ '--apikey',
1462
+ help='Your CloudOS API key',
1463
+ required=True)
1464
+ @click.option('-c',
1465
+ '--cloudos-url',
1466
+ help=(f'The CloudOS url you are trying to access to. Default={CLOUDOS_URL}.'),
1467
+ default=CLOUDOS_URL,
1468
+ required=True)
1469
+ @click.option('--workspace-id',
1470
+ help='The specific CloudOS workspace id.',
1471
+ required=True)
1472
+ @click.option('--project-name',
1473
+ help='The name of a CloudOS project.')
1474
+ @click.option('-p',
1475
+ '--parameter',
1476
+ multiple=True,
1477
+ help=('A single parameter to pass to the job call. It should be in the ' +
1478
+ 'following form: parameter_name=parameter_value. E.g.: ' +
1479
+ '-p input=s3://path_to_my_file. You can use this option as many ' +
1480
+ 'times as parameters you want to include.'))
1481
+ @click.option('--nextflow-profile',
1482
+ help=('A comma separated string indicating the nextflow profile/s ' +
1483
+ 'to use with your job.'))
1484
+ @click.option('--nextflow-version',
1485
+ help=('Nextflow version to use when executing the workflow in CloudOS. ' +
1486
+ 'Default=22.10.8.'),
1487
+ type=click.Choice(['22.10.8', '24.04.4', '22.11.1-edge', 'latest']))
1488
+ @click.option('--git-branch',
1489
+ help=('The branch to run for the selected pipeline. ' +
1490
+ 'If not specified it defaults to the last commit ' +
1491
+ 'of the default branch.'))
1492
+ @click.option('--job-name',
1493
+ help='The name of the job. If not set, will take the name of the cloned job.')
1494
+ @click.option('--do-not-save-logs',
1495
+ help=('Avoids process log saving. If you select this option, your job process ' +
1496
+ 'logs will not be stored.'),
1497
+ is_flag=True)
1498
+ @click.option('--job-queue',
1499
+ help=('Name of the job queue to use with a batch job. ' +
1500
+ 'In Azure workspaces, this option is ignored.'))
1501
+ @click.option('--instance-type',
1502
+ help=('The type of compute instance to use as master node. ' +
1503
+ 'Default=c5.xlarge(aws)|Standard_D4as_v4(azure).'))
1504
+ @click.option('--cost-limit',
1505
+ help='Add a cost limit to your job. Default=30.0 (For no cost limit please use -1).',
1506
+ type=float)
1507
+ @click.option('--job-id',
1508
+ help='The CloudOS job id of the job to be cloned.',
1509
+ required=True)
1510
+ @click.option('--accelerate-file-staging',
1511
+ help='Enables AWS S3 mountpoint for quicker file staging.',
1512
+ is_flag=True)
1513
+ @click.option('--resumable',
1514
+ help='Whether to make the job able to be resumed or not.',
1515
+ is_flag=True)
1516
+ @click.option('--verbose',
1517
+ help='Whether to print information messages or not.',
1518
+ is_flag=True)
1519
+ @click.option('--disable-ssl-verification',
1520
+ help=('Disable SSL certificate verification. Please, remember that this option is ' +
1521
+ 'not generally recommended for security reasons.'),
1522
+ is_flag=True)
1523
+ @click.option('--ssl-cert',
1524
+ help='Path to your SSL certificate file.')
1525
+ @click.option('--profile',
1526
+ help='Profile to use from the config file',
1527
+ default=None)
1528
+ @click.pass_context
1529
+ def clone_job(ctx,
1530
+ apikey,
1531
+ cloudos_url,
1532
+ workspace_id,
1533
+ project_name,
1534
+ parameter,
1535
+ nextflow_profile,
1536
+ nextflow_version,
1537
+ git_branch,
1538
+ job_name,
1539
+ do_not_save_logs,
1540
+ job_queue,
1541
+ instance_type,
1542
+ cost_limit,
1543
+ job_id,
1544
+ accelerate_file_staging,
1545
+ resumable,
1546
+ verbose,
1547
+ disable_ssl_verification,
1548
+ ssl_cert,
1549
+ profile):
1550
+ """Clone an existing job with optional parameter overrides."""
1551
+ profile = profile or ctx.default_map['job']['clone']['profile']
1552
+
1553
+ # Create a dictionary with required and non-required params
1554
+ required_dict = {
1555
+ 'apikey': True,
1556
+ 'workspace_id': True,
1557
+ 'workflow_name': False,
1558
+ 'project_name': False,
1559
+ 'procurement_id': False
1560
+ }
1561
+
1562
+ # Determine if the user provided all required parameters
1563
+ config_manager = ConfigurationProfile()
1564
+ user_options = (
1565
+ config_manager.load_profile_and_validate_data(
1566
+ ctx,
1567
+ INIT_PROFILE,
1568
+ CLOUDOS_URL,
1569
+ profile=profile,
1570
+ required_dict=required_dict,
1571
+ apikey=apikey,
1572
+ cloudos_url=cloudos_url,
1573
+ workspace_id=workspace_id,
1574
+ project_name=project_name
1575
+ )
1576
+ )
1577
+ apikey = user_options['apikey']
1578
+ cloudos_url = user_options['cloudos_url']
1579
+ workspace_id = user_options['workspace_id']
1580
+
1581
+ verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
1582
+
1583
+ print('Cloning job...')
1584
+ if verbose:
1585
+ print('\t...Preparing objects')
1586
+
1587
+ # Create Job object (set dummy values for project_name and workflow_name, since they come from the cloned job)
1588
+ job_obj = jb.Job(cloudos_url, apikey, None, workspace_id, None, None, workflow_id=1234, project_id="None",
1589
+ mainfile=None, importsfile=None,verify=verify_ssl)
1590
+
1591
+ if verbose:
1592
+ print('\tThe following Job object was created:')
1593
+ print('\t' + str(job_obj) + '\n')
1594
+ print(f'\tCloning job {job_id} in workspace: {workspace_id}')
1595
+
1596
+ try:
1597
+ # Clone the job with provided overrides
1598
+ cloned_job_id = job_obj.clone_job(
1599
+ source_job_id=job_id,
1600
+ queue_name=job_queue,
1601
+ cost_limit=cost_limit,
1602
+ master_instance=instance_type,
1603
+ job_name=job_name,
1604
+ nextflow_version=nextflow_version,
1605
+ branch=git_branch,
1606
+ profile=nextflow_profile,
1607
+ do_not_save_logs=do_not_save_logs,
1608
+ use_fusion=accelerate_file_staging,
1609
+ resumable=resumable,
1610
+ # only when explicitly setting --project-name will be overridden, else using the original project
1611
+ project_name=project_name if ctx.get_parameter_source("project_name") == click.core.ParameterSource.COMMANDLINE else None,
1612
+ parameters=list(parameter) if parameter else None,
1613
+ verify=verify_ssl
1614
+ )
1615
+ if verbose:
1616
+ print(f'\tCloned job ID: {cloned_job_id}')
1617
+
1618
+ print(f"Job successfully cloned. New job ID: {cloned_job_id}")
1619
+
1620
+ except BadRequestException as e:
1621
+ if verbose:
1622
+ print(f'\tError details: {e}')
1623
+ raise ValueError(f"Failed to clone job: {e}")
1624
+ except Exception as e:
1625
+ if verbose:
1626
+ print(f'\tError details: {e}')
1627
+ raise ValueError(f"An error occurred while cloning the job: {e}")
1628
+
1629
+
1422
1630
  @workflow.command('list')
1423
1631
  @click.option('-k',
1424
1632
  '--apikey',
@@ -0,0 +1 @@
1
+ __version__ = '2.53.0'
@@ -357,8 +357,13 @@ class Cloudos:
357
357
  return r
358
358
 
359
359
  def get_job_list(self, workspace_id, last_n_jobs=30, page=1, archived=False,
360
- verify=True):
361
- """Get jobs from a CloudOS workspace.
360
+ verify=True, filter_status=None, filter_job_name=None,
361
+ filter_project=None, filter_workflow=None, filter_job_id=None,
362
+ filter_only_mine=False, filter_owner=None, filter_queue=None, last=False):
363
+ """Get jobs from a CloudOS workspace with optional filtering.
364
+
365
+ Fetches jobs page by page, applies all filters after fetching.
366
+ Stops when enough jobs are collected or no more jobs are available.
362
367
 
363
368
  Parameters
364
369
  ----------
@@ -368,52 +373,170 @@ class Cloudos:
368
373
  How many of the last jobs from the user to retrieve. You can specify a
369
374
  very large int or 'all' to get all user's jobs.
370
375
  page : int
371
- Response page to get.
376
+ Response page to get (ignored when using filters - starts from page 1).
372
377
  archived : bool
373
378
  When True, only the archived jobs are retrieved.
374
379
  verify: [bool|string]
375
380
  Whether to use SSL verification or not. Alternatively, if
376
381
  a string is passed, it will be interpreted as the path to
377
382
  the SSL certificate file.
383
+ filter_status : string, optional
384
+ Filter jobs by status (e.g., 'completed', 'running', 'failed').
385
+ filter_job_name : string, optional
386
+ Filter jobs by name.
387
+ filter_project : string, optional
388
+ Filter jobs by project name (will be resolved to project ID).
389
+ filter_workflow : string, optional
390
+ Filter jobs by workflow name (will be resolved to workflow ID).
391
+ filter_job_id : string, optional
392
+ Filter jobs by specific job ID.
393
+ filter_only_mine : bool, optional
394
+ Filter to show only jobs belonging to the current user.
395
+ filter_queue : string, optional
396
+ Filter jobs by queue name (will be resolved to queue ID).
397
+ Only applies to jobs running in batch environment.
398
+ Non-batch jobs are preserved in results as they don't use queues.
399
+ last : bool, optional
400
+ When workflows are duplicated, use the latest imported workflow (by date).
378
401
 
379
402
  Returns
380
403
  -------
381
404
  r : list
382
405
  A list of dicts, each corresponding to a jobs from the user and the workspace.
383
406
  """
407
+ if not workspace_id or not isinstance(workspace_id, str):
408
+ raise ValueError("Invalid workspace_id: must be a non-empty string")
409
+
410
+ if last_n_jobs != 'all' and (not isinstance(last_n_jobs, int) or last_n_jobs <= 0):
411
+ raise ValueError("last_n_jobs must be a positive integer or 'all'")
412
+
413
+ # Validate filter_status values
414
+ if filter_status:
415
+ valid_statuses = ['completed', 'running', 'failed', 'aborted', 'queued', 'pending', 'initializing']
416
+ if filter_status.lower() not in valid_statuses:
417
+ raise ValueError(f"Invalid filter_status '{filter_status}'. Valid values: {', '.join(valid_statuses)}")
418
+
384
419
  headers = {
385
420
  "Content-type": "application/json",
386
421
  "apikey": self.apikey
387
422
  }
388
- if archived:
389
- archived_status = "true"
390
- else:
391
- archived_status = "false"
392
- r = retry_requests_get("{}/api/v2/jobs?teamId={}&page={}&archived.status={}".format(
393
- self.cloudos_url, workspace_id, page, archived_status),
394
- headers=headers, verify=verify)
395
- if r.status_code >= 400:
396
- raise BadRequestException(r)
397
- content = json.loads(r.content)
398
- n_jobs = len(content['jobs'])
399
- if last_n_jobs == 'all':
400
- jobs_to_get = n_jobs
401
- elif last_n_jobs > 0:
402
- jobs_to_get = last_n_jobs - n_jobs
403
- else:
404
- raise TypeError("[ERROR] Please select an int > 0 or 'all' for 'last_n_jobs'")
405
- if jobs_to_get == 0 or n_jobs == 0:
406
- return content['jobs']
407
- if jobs_to_get > 0:
408
- if last_n_jobs == 'all':
409
- next_to_get = 'all'
410
- else:
411
- next_to_get = jobs_to_get
412
- return content['jobs'] + self.get_job_list(workspace_id, last_n_jobs=next_to_get,
413
- page=page+1, archived=archived,
414
- verify=verify)
415
- if jobs_to_get < 0:
416
- return content['jobs'][:jobs_to_get]
423
+
424
+ # Build query parameters for server-side filtering
425
+ params = {
426
+ "teamId": workspace_id,
427
+ "archived.status": str(archived).lower(),
428
+ "limit": 100, # Use a reasonable page size
429
+ "page": 1 # Always start from page 1
430
+ }
431
+
432
+ # --- Resolve IDs once (before pagination loop) ---
433
+
434
+ # Add simple server-side filters
435
+ if filter_status:
436
+ params["status"] = filter_status.lower()
437
+ if filter_job_name:
438
+ params["name"] = filter_job_name
439
+ if filter_job_id:
440
+ params["id"] = filter_job_id
441
+
442
+ # Resolve project name to ID
443
+ if filter_project:
444
+ try:
445
+ project_id = self.get_project_id_from_name(workspace_id, filter_project, verify=verify)
446
+ if project_id:
447
+ params["project.id"] = project_id
448
+ else:
449
+ raise ValueError(f"Project '{filter_project}' not found.")
450
+ except Exception as e:
451
+ raise ValueError(f"Error resolving project '{filter_project}': {str(e)}")
452
+
453
+ # Resolve workflow name to ID
454
+ if filter_workflow:
455
+ try:
456
+ workflow_content = self.get_workflow_content(workspace_id, filter_workflow, verify=verify, last=last)
457
+ if workflow_content and workflow_content.get("workflows"):
458
+ # Extract the first (and should be only) workflow from the list
459
+ workflow = workflow_content["workflows"][0]
460
+ workflow_id = workflow.get("_id")
461
+ if workflow_id:
462
+ params["workflow.id"] = workflow_id
463
+ else:
464
+ raise ValueError(f"Workflow '{filter_workflow}' not found.")
465
+ else:
466
+ raise ValueError(f"Workflow '{filter_workflow}' not found.")
467
+ except Exception as e:
468
+ raise ValueError(f"Error resolving workflow '{filter_workflow}': {str(e)}")
469
+
470
+ # Get current user ID for filter_only_mine
471
+ if filter_only_mine:
472
+ try:
473
+ user_info = self.get_user_info(verify=verify)
474
+ user_id = user_info.get("id") or user_info.get("_id")
475
+ if user_id:
476
+ params["user.id"] = user_id
477
+ else:
478
+ raise ValueError("Could not retrieve current user information.")
479
+ except Exception as e:
480
+ raise ValueError(f"Error getting current user info: {str(e)}")
481
+
482
+
483
+ # --- Fetch jobs page by page ---
484
+ all_jobs = []
485
+ current_page = 1
486
+
487
+ while True:
488
+ params["page"] = current_page
489
+
490
+ r = retry_requests_get(f"{self.cloudos_url}/api/v2/jobs", params=params, headers=headers, verify=verify)
491
+ if r.status_code >= 400:
492
+ raise BadRequestException(r)
493
+
494
+ content = r.json()
495
+ page_jobs = content.get('jobs', [])
496
+
497
+ # No jobs returned, we've reached the end
498
+ if not page_jobs:
499
+ break
500
+
501
+ all_jobs.extend(page_jobs)
502
+
503
+ # Check if we have enough jobs or reached the last page
504
+ if last_n_jobs != 'all' and len(all_jobs) >= last_n_jobs:
505
+ break
506
+ if len(page_jobs) < params["limit"]:
507
+ break # Last page (fewer jobs than requested page size)
508
+
509
+ current_page += 1
510
+
511
+ # --- Local queue filtering (not supported by API) ---
512
+ if filter_queue:
513
+ try:
514
+ batch_jobs=[job for job in all_jobs if job.get("batch", {})]
515
+ if batch_jobs:
516
+ from cloudos_cli.queue.queue import Queue
517
+ queue_api = Queue(self.cloudos_url, self.apikey, self.cromwell_token, workspace_id, verify)
518
+ queues = queue_api.get_job_queues()
519
+
520
+ queue_id = None
521
+ for queue in queues:
522
+ if queue.get("label") == filter_queue or queue.get("name") == filter_queue:
523
+ queue_id = queue.get("id") or queue.get("_id")
524
+ break
525
+
526
+ if not queue_id:
527
+ raise ValueError(f"Queue with name '{filter_queue}' not found in workspace '{workspace_id}'")
528
+
529
+ all_jobs = [job for job in all_jobs if job.get("batch", {}).get("jobQueue", {}).get("id") == queue_id]
530
+ else:
531
+ raise ValueError(f"The environment is not a batch environment so queues do not exist. Please remove the --filter-queue option.")
532
+ except Exception as e:
533
+ raise ValueError(f"Error filtering by queue '{filter_queue}': {str(e)}")
534
+
535
+ # --- Apply limit after all filtering ---
536
+ if last_n_jobs != 'all' and isinstance(last_n_jobs, int) and last_n_jobs > 0:
537
+ all_jobs = all_jobs[:last_n_jobs]
538
+
539
+ return all_jobs
417
540
 
418
541
  @staticmethod
419
542
  def process_job_list(r, all_fields=False):
@@ -460,7 +583,13 @@ class Cloudos:
460
583
  if all_fields:
461
584
  df = df_full
462
585
  else:
463
- df = df_full.loc[:, COLUMNS]
586
+ # Only select columns that actually exist in the DataFrame
587
+ existing_columns = [col for col in COLUMNS if col in df_full.columns]
588
+ if existing_columns:
589
+ df = df_full.loc[:, existing_columns]
590
+ else:
591
+ # If none of the predefined columns exist, raise missing error
592
+ raise ValueError(f"None of the predefined columns {COLUMNS} exist in retrieved columns:{list(df_full.columns)}")
464
593
  return df
465
594
 
466
595
  def reorder_job_list(self, my_jobs_df, filename='my_jobs.csv'):
@@ -1199,7 +1328,6 @@ class Cloudos:
1199
1328
  response = retry_requests_get(url, headers=headers, verify=verify)
1200
1329
  # return all content
1201
1330
  content = json.loads(response.content)
1202
-
1203
1331
  if response.status_code >= 400:
1204
1332
  raise BadRequestException(response)
1205
1333
 
@@ -921,3 +921,246 @@ class Job(Cloudos):
921
921
  "parameterKind": "textValue",
922
922
  "textValue": f"{rest}"
923
923
  }
924
+
925
+ def get_job_request_payload(self, job_id, verify=True):
926
+ """Get the original request payload for a job.
927
+
928
+ Parameters
929
+ ----------
930
+ job_id : str
931
+ The CloudOS job ID to get the payload for.
932
+ verify : [bool|string]
933
+ Whether to use SSL verification or not. Alternatively, if
934
+ a string is passed, it will be interpreted as the path to
935
+ the SSL certificate file.
936
+
937
+ Returns
938
+ -------
939
+ dict
940
+ The original job request payload.
941
+ """
942
+ headers = {
943
+ "Content-type": "application/json",
944
+ "apikey": self.apikey
945
+ }
946
+ url = f"{self.cloudos_url}/api/v1/jobs/{job_id}/request-payload?teamId={self.workspace_id}"
947
+ r = retry_requests_get(url, headers=headers, verify=verify)
948
+ if r.status_code >= 400:
949
+ raise BadRequestException(r)
950
+ return json.loads(r.content)
951
+
952
+ def update_parameter_value(self, parameters, param_name, new_value):
953
+ """Update a parameter value in the parameters list.
954
+
955
+ Parameters
956
+ ----------
957
+ parameters : list
958
+ List of parameter dictionaries.
959
+ param_name : str
960
+ Name of the parameter to update.
961
+ new_value : str
962
+ New value for the parameter.
963
+
964
+ Returns
965
+ -------
966
+ bool
967
+ True if parameter was found and updated, False otherwise.
968
+ """
969
+ for param in parameters:
970
+ if param.get('name') == param_name:
971
+ # Handle different parameter kinds
972
+ if param.get('parameterKind') == 'textValue':
973
+ param['textValue'] = new_value
974
+ elif param.get('parameterKind') == 'dataItem':
975
+ # For data items, we need to process the value to get file/folder ID
976
+ # This is a simplified version - in practice you'd need more logic
977
+ if new_value.startswith('s3://') or new_value.startswith('az://'):
978
+ param['textValue'] = new_value
979
+ param['parameterKind'] = 'textValue'
980
+ else:
981
+ # Try to process as file/data item
982
+ processed_param = self.docker_workflow_param_processing(f"--{param_name}={new_value}", self.project_name)
983
+ param.update(processed_param)
984
+ return True
985
+ return False
986
+
987
+ def clone_job(self,
988
+ source_job_id,
989
+ queue_name=None,
990
+ cost_limit=None,
991
+ master_instance=None,
992
+ job_name=None,
993
+ nextflow_version=None,
994
+ branch=None,
995
+ profile=None,
996
+ do_not_save_logs=None,
997
+ use_fusion=None,
998
+ resumable=None,
999
+ project_name=None,
1000
+ parameters=None,
1001
+ verify=True):
1002
+ """Clone an existing job with optional parameter overrides.
1003
+
1004
+ Parameters
1005
+ ----------
1006
+ source_job_id : str
1007
+ The CloudOS job ID to clone from.
1008
+ queue_name : str, optional
1009
+ Name of the job queue to use.
1010
+ cost_limit : float, optional
1011
+ Job cost limit override.
1012
+ master_instance : str, optional
1013
+ Master instance type override.
1014
+ job_name : str, optional
1015
+ New job name.
1016
+ nextflow_version : str, optional
1017
+ Nextflow version override.
1018
+ branch : str, optional
1019
+ Git branch override.
1020
+ profile : str, optional
1021
+ Nextflow profile override.
1022
+ do_not_save_logs : bool, optional
1023
+ Whether to save logs override.
1024
+ use_fusion : bool, optional
1025
+ Whether to use fusion filesystem override.
1026
+ resumable : bool, optional
1027
+ Whether to make the job resumable or not.
1028
+ project_name : str, optional
1029
+ Project name override (will look up new project ID).
1030
+ parameters : list, optional
1031
+ List of parameter overrides in format ['param1=value1', 'param2=value2'].
1032
+ verify : [bool|string]
1033
+ Whether to use SSL verification or not. Alternatively, if
1034
+ a string is passed, it will be interpreted as the path to
1035
+ the SSL certificate file.
1036
+
1037
+ Returns
1038
+ -------
1039
+ str
1040
+ The CloudOS job ID of the cloned job.
1041
+ """
1042
+ # Get the original job payload
1043
+ original_payload = self.get_job_request_payload(source_job_id, verify=verify)
1044
+
1045
+ # Create a copy of the payload for modification
1046
+ cloned_payload = json.loads(json.dumps(original_payload))
1047
+
1048
+ # remove unwanted fields
1049
+ del cloned_payload['_id']
1050
+ del cloned_payload['resourceId']
1051
+
1052
+ # Override job name if provided
1053
+ if job_name:
1054
+ cloned_payload['name'] = job_name
1055
+
1056
+ # Override cost limit if provided
1057
+ if cost_limit is not None:
1058
+ if 'execution' not in cloned_payload:
1059
+ cloned_payload['execution'] = {}
1060
+ cloned_payload['execution']['computeCostLimit'] = cost_limit
1061
+
1062
+ # Override master instance if provided
1063
+ if master_instance:
1064
+ if 'masterInstance' not in cloned_payload:
1065
+ cloned_payload['masterInstance'] = {'requestedInstance': {}}
1066
+ cloned_payload['masterInstance']['requestedInstance']['type'] = master_instance
1067
+
1068
+ # Override nextflow version if provided (only for non-docker workflows)
1069
+ if nextflow_version and 'nextflowVersion' in cloned_payload:
1070
+ cloned_payload['nextflowVersion'] = nextflow_version
1071
+ elif nextflow_version and cloned_payload['executionPlatform'] == 'azure' and\
1072
+ nextflow_version not in ['22.11.1-edge', 'latest']:
1073
+ print("[Message]: Azure workspace only uses Nextflow version 22.11.1-edge, option '--nextflow-version' is ignored.\n")
1074
+
1075
+ # Override branch if provided
1076
+ if branch:
1077
+ if 'revision' not in cloned_payload:
1078
+ cloned_payload['revision'] = {}
1079
+ cloned_payload['revision']['revisionType'] = 'branch'
1080
+ cloned_payload['revision']['branch'] = branch
1081
+ # Clear other revision types
1082
+ cloned_payload['revision'].pop('commit', None)
1083
+ cloned_payload['revision'].pop('tag', None)
1084
+
1085
+ # Override profile if provided
1086
+ if profile:
1087
+ cloned_payload['profile'] = profile
1088
+
1089
+ # Override save logs if provided
1090
+ if do_not_save_logs:
1091
+ cloned_payload['saveProcessLogs'] = do_not_save_logs
1092
+
1093
+ # Override use fusion if provided
1094
+ if use_fusion and cloned_payload['executionPlatform'] != 'azure':
1095
+ cloned_payload['usesFusionFileSystem'] = use_fusion
1096
+ elif use_fusion and cloned_payload['executionPlatform'] == 'azure':
1097
+ print("[Message]: Azure workspace does not use fusion filesystem, option '--accelerate-file-staging' is ignored.\n")
1098
+
1099
+ # Override resumable if provided
1100
+ if resumable:
1101
+ cloned_payload['resumable'] = resumable
1102
+
1103
+ # Handle job queue override
1104
+ if queue_name:
1105
+ if cloned_payload['executionPlatform'] != 'azure':
1106
+ try:
1107
+ from cloudos_cli.queue.queue import Queue
1108
+ queue_api = Queue(self.cloudos_url, self.apikey, self.cromwell_token, self.workspace_id, verify)
1109
+ queues = queue_api.get_job_queues()
1110
+
1111
+ queue_id = None
1112
+ for queue in queues:
1113
+ if queue.get("label") == queue_name or queue.get("name") == queue_name:
1114
+ queue_id = queue.get("id") or queue.get("_id")
1115
+ break
1116
+ cloned_payload['batch']['jobQueue'] = queue_id
1117
+ if not queue_id:
1118
+ raise ValueError(f"Queue with name '{queue_name}' not found in workspace '{self.workspace_id}'")
1119
+ except Exception as e:
1120
+ raise ValueError(f"Error filtering by queue '{queue_name}': {str(e)}")
1121
+ else:
1122
+ print("[Message]: Azure workspace does not use job queues, option '--job-queue' is ignored.\n")
1123
+
1124
+ # Handle parameter overrides
1125
+ if parameters:
1126
+ cloned_parameters = cloned_payload.get('parameters', [])
1127
+ for param_override in parameters:
1128
+ param_name, param_value = param_override.split('=', 1)
1129
+ param_name = param_name.lstrip('-') # Remove leading dashes
1130
+ if not self.update_parameter_value(cloned_parameters, param_name, param_value):
1131
+ # Parameter not found, add as new parameter
1132
+ # Determine workflow type to set proper prefix and format
1133
+ prefix = "--" if param_override.startswith('--') else ("-" if param_override.startswith('-') else "")
1134
+ new_param = {
1135
+ "prefix": prefix,
1136
+ "name": param_name,
1137
+ "parameterKind": "textValue",
1138
+ "textValue": param_value
1139
+ }
1140
+ cloned_parameters.append(new_param)
1141
+ cloned_payload['parameters'] = cloned_parameters
1142
+
1143
+ # setup project name
1144
+ if project_name:
1145
+ # get project ID
1146
+ project_id = self.get_project_id_from_name(self.workspace_id, project_name, verify=verify)
1147
+ cloned_payload['project'] = project_id
1148
+
1149
+ # Send the cloned job
1150
+ headers = {
1151
+ "Content-type": "application/json",
1152
+ "apikey": self.apikey
1153
+ }
1154
+
1155
+ r = retry_requests_post(f"{self.cloudos_url}/api/v2/jobs?teamId={self.workspace_id}",
1156
+ data=json.dumps(cloned_payload),
1157
+ headers=headers,
1158
+ verify=verify)
1159
+
1160
+ if r.status_code >= 400:
1161
+ raise BadRequestException(r)
1162
+
1163
+ j_id = json.loads(r.content)["jobId"]
1164
+ print('\tJob successfully cloned and launched to CloudOS, please check the ' +
1165
+ f"following link: {self.cloudos_url}/app/advanced-analytics/analyses/{j_id}\n")
1166
+ return j_id
@@ -113,6 +113,7 @@ class Images(Cloudos):
113
113
  payload = {
114
114
  "organisationId": organisation_id,
115
115
  "imageType": image_type,
116
+ "imageName": image_name,
116
117
  "provider": provider,
117
118
  "region": region,
118
119
  "imageId": image_id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudos_cli
3
- Version: 2.50.0
3
+ Version: 2.53.0
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
@@ -615,6 +615,52 @@ Aborting jobs...
615
615
  ```
616
616
 
617
617
 
618
+ #### Clone a job with optional parameter overrides
619
+
620
+ The `clone` command allows you to create a new job based on an existing job's configuration, with the ability to override specific parameters. This is useful for re-running jobs with slight modifications without having to specify all parameters from scratch.
621
+
622
+ Basic usage:
623
+ ```console
624
+ cloudos job clone \
625
+ --profile MY_PROFILE
626
+ --job-id "60a7b8c9d0e1f2g3h4i5j6k7"
627
+ ```
628
+
629
+ Clone with parameter overrides:
630
+ ```console
631
+ cloudos job clone \
632
+ --profile MY_PROFILE
633
+ --job-id "60a7b8c9d0e1f2g3h4i5j6k7" \
634
+ --job-queue "high-priority-queue" \
635
+ --cost-limit 50.0 \
636
+ --instance-type "c5.2xlarge" \
637
+ --job-name "cloned_analysis_v2" \
638
+ --nextflow-version "24.04.4" \
639
+ --git-branch "dev" \
640
+ --nextflow-profile "production" \
641
+ --do-not-save-logs true \
642
+ --accelerate-file-staging true \
643
+ --workflow-name "updated-workflow" \
644
+ -p "input=s3://new-bucket/input.csv" \
645
+ -p "output_dir=s3://new-bucket/results"
646
+ ```
647
+
648
+ Available override options:
649
+ - `--job-queue`: Specify a different job queue
650
+ - `--cost-limit`: Set a new cost limit (use -1 for no limit)
651
+ - `--instance-type`: Change the master instance type
652
+ - `--job-name`: Assign a custom name to the cloned job
653
+ - `--nextflow-version`: Use a different Nextflow version
654
+ - `--git-branch`: Switch to a different git branch
655
+ - `--nextflow-profile`: Change the Nextflow profile
656
+ - `--do-not-save-logs`: Enable/disable log saving
657
+ - `--accelerate-file-staging`: Enable/disable fusion filesystem
658
+ - `--workflow-name`: Use a different workflow
659
+ - `-p, --parameter`: Override or add parameters (can be used multiple times)
660
+
661
+ > [!NOTE]
662
+ > Parameters can be overridden or new ones can be added using `-p` option
663
+
618
664
  #### Executor support
619
665
 
620
666
  CloudOS supports [AWS batch](https://www.nextflow.io/docs/latest/executor.html?highlight=executors#aws-batch) executor by default.
@@ -1 +0,0 @@
1
- __version__ = '2.50.0'
File without changes
File without changes
File without changes