diff --git a/README.md b/README.md index e5a3dbec..51cbe782 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ pip install saagieapi== | >= 2023.02 | >= 2.6.0 | | >= 2023.03 | >= 2.7.0 | | >= 2023.04 | >= 2.9.0 | -| >= 2023.05 | >= 2.10.0 | +| >= 2023.05 | >= 2.10.0 | +| >= 2024.01 | >= 2.11.0 | ## Contributing diff --git a/docs/index.rst b/docs/index.rst index c87f7922..a1db9fb4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,6 +25,7 @@ Compatibility with your Saagie platform >= 2023.03 >= 2.7.0 >= 2023.04 >= 2.9.0 >= 2023.05 >= 2.10.0 +>= 2024.01 >= 2.11.0 =========================== ====================== Usage diff --git a/saagieapi/jobs/gql_queries.py b/saagieapi/jobs/gql_queries.py index 719ff940..42a1bef9 100644 --- a/saagieapi/jobs/gql_queries.py +++ b/saagieapi/jobs/gql_queries.py @@ -62,6 +62,7 @@ deletable reasons } + sourceUrl } category technology { @@ -231,13 +232,25 @@ """ GQL_CREATE_JOB = """ -mutation createJobMutation($projectId: UUID!, $name: String!, $description: String, $category: String!, - $isScheduled: Boolean!, $cronScheduling: Cron, $scheduleTimezone: TimeZone - $technologyId: UUID!, $extraTechnology: ExtraTechnologyInput, - $alerting: JobPipelineAlertingInput, $resources: JobResourceInput, - $releaseNote: String, $runtimeVersion: String, $commandLine: String, - $dockerInfo: JobDockerInput, $file: Upload) { - createJob(job: { +mutation createJobMutation($projectId: UUID!, + $name: String!, + $description: String, + $category: String!, + $isScheduled: Boolean!, + $cronScheduling: Cron, + $scheduleTimezone: TimeZone + $technologyId: UUID!, + $extraTechnology: ExtraTechnologyInput, + $alerting: JobPipelineAlertingInput, + $resources: JobResourceInput, + $releaseNote: String, + $runtimeVersion: String, + $commandLine: String, + $dockerInfo: JobDockerInput, + $file: Upload, + $sourceUrl: String) { + createJob( + job: { projectId: $projectId name: $name description: $description @@ -259,6 +272,7 @@ commandLine: $commandLine extraTechnology: $extraTechnology dockerInfo: $dockerInfo + sourceUrl: $sourceUrl } file: $file ){ @@ -273,9 +287,15 @@ """ GQL_UPGRADE_JOB = """ -mutation addJobVersionMutation($jobId: UUID!, $releaseNote: String, $runtimeVersion: String, $commandLine: String, - $extraTechnology: ExtraTechnologyInput, - $usePreviousArtifact: Boolean, $dockerInfo: JobDockerInput, $file: Upload) { +mutation addJobVersionMutation($jobId: UUID!, + $releaseNote: String, + $runtimeVersion: String, + $commandLine: String, + $extraTechnology: ExtraTechnologyInput, + $usePreviousArtifact: Boolean, + $dockerInfo: JobDockerInput, + $file: Upload, + $sourceUrl: String) { addJobVersion( jobId: $jobId jobVersion: { @@ -285,6 +305,7 @@ extraTechnology: $extraTechnology dockerInfo: $dockerInfo usePreviousArtifact: $usePreviousArtifact + sourceUrl: $sourceUrl } file: $file ){ @@ -355,6 +376,7 @@ deletable reasons } + sourceUrl } category technology { @@ -447,6 +469,7 @@ deletable reasons } + sourceUrl } category technology { diff --git a/saagieapi/jobs/jobs.py b/saagieapi/jobs/jobs.py index 1a1d5552..3020d162 100644 --- a/saagieapi/jobs/jobs.py +++ b/saagieapi/jobs/jobs.py @@ -581,6 +581,7 @@ def create( resources: Dict = None, emails: List = None, status_list: List = None, + source_url: str = "", ) -> Dict: """Create job in given project @@ -632,6 +633,8 @@ def create( Receive an email when the job status change to a specific status Each item of the list should be one of these following values: "REQUESTED", "QUEUED", "RUNNING", "FAILED", "KILLED", "KILLING", "SUCCEEDED", "UNKNOWN", "AWAITING", "SKIPPED" + source_url: str, optional + URL of the source code used for the job (link to the commit for example) Returns ------- @@ -657,7 +660,8 @@ def create( ... schedule_timezone='Europe/Paris', ... resources={"cpu": {"request": 0.5, "limit": 2.6}, "memory": {"request": 1.0}}, ... emails=['email1@saagie.io', 'email2@saagie.io'], - ... status_list=["FAILED", "KILLED"] + ... status_list=["FAILED", "KILLED"], + ... source_url="", ... ) { "data":{ @@ -723,6 +727,9 @@ def create( if resources: params["resources"] = resources + if source_url: + params["source_url"] = source_url + result = self.__launch_request(file, GQL_CREATE_JOB, params) logging.info("✅ Job [%s] successfully created", job_name) return result @@ -869,6 +876,7 @@ def upgrade( release_note: str = "", extra_technology: str = None, extra_technology_version: str = None, + source_url: str = "", ) -> Dict: """Upgrade a job @@ -893,6 +901,8 @@ def upgrade( None or the request will not work extra_technology_version: str (optional) Version of the extra technology. Leave to None when not needed + source_url: str (optional) + URL of the source code used for the job (link to the commit for example) Returns ------- @@ -952,6 +962,10 @@ def upgrade( # Add extra technology parameter if needed if extra_technology is not None: params["extraTechnology"] = {"language": extra_technology, "version": extra_technology_version} + + if source_url: + params["sourceUrl"] = source_url + result = self.__launch_request(file, GQL_UPGRADE_JOB, params) logging.info("✅ Job [%s] successfully upgraded", job_id) return result @@ -967,6 +981,7 @@ def upgrade_by_name( release_note: str = None, extra_technology: str = None, extra_technology_version: str = None, + source_url: str = "", ) -> Dict: """Upgrade a job @@ -990,6 +1005,8 @@ def upgrade_by_name( Extra technology when needed (spark jobs). If not needed, leave to None or the request will not work extra_technology_version: str (optional) Version of the extra technology. Leave to None when not needed + source_url: str (optional) + URL of the source code used for the job (link to the commit for example) Returns ------- @@ -1016,14 +1033,15 @@ def upgrade_by_name( } """ return self.upgrade( - self.get_id(job_name, project_name), - file, - use_previous_artifact, - runtime_version, - command_line, - release_note, - extra_technology, - extra_technology_version, + job_id=self.get_id(job_name, project_name), + file=file, + use_previous_artifact=use_previous_artifact, + runtime_version=runtime_version, + command_line=command_line, + release_note=release_note, + extra_technology=extra_technology, + extra_technology_version=extra_technology_version, + source_url=source_url, ) def create_or_upgrade( @@ -1047,6 +1065,7 @@ def create_or_upgrade( resources: Dict = None, emails: List = None, status_list: List = None, + source_url: str = "", ) -> Dict: """Create or upgrade a job @@ -1091,6 +1110,8 @@ def create_or_upgrade( Emails status_list: list (optional) Status list + source_url: str (optional) + URL of the source code used for the job (link to the commit for example) Returns ------- @@ -1118,7 +1139,8 @@ def create_or_upgrade( ... schedule_timezone='Europe/Paris', ... resources={"cpu": {"request": 0.5, "limit": 2.6}, "memory": {"request": 1.0}}, ... emails=['email1@saagie.io', 'email2@saagie.io'], - ... status_list=["FAILED", "KILLED"] + ... status_list=["FAILED", "KILLED"], + ... source_url="", ... ) { "data":{ @@ -1152,6 +1174,7 @@ def create_or_upgrade( release_note=release_note, extra_technology=extra_technology, extra_technology_version=extra_technology_version, + source_url=source_url, )["data"]["addJobVersion"] } @@ -1190,6 +1213,7 @@ def create_or_upgrade( "resources": resources, "emails": emails, "status_list": status_list, + "source_url": source_url, }.items() if v is not None # Remove None values from the dict } diff --git a/saagieapi/pipelines/gql_queries.py b/saagieapi/pipelines/gql_queries.py index 50154e32..c10ba86e 100644 --- a/saagieapi/pipelines/gql_queries.py +++ b/saagieapi/pipelines/gql_queries.py @@ -5,6 +5,7 @@ pipelines{ id name + alias } } } @@ -19,6 +20,7 @@ pipelines{ id name + alias description alerting{ emails @@ -106,6 +108,7 @@ graphPipeline(id: $id){ id name + alias description alerting{ emails @@ -199,6 +202,7 @@ graphPipelineByName(projectId: $projectId, name: $pipelineName) { id name + alias description alerting{ emails @@ -302,7 +306,8 @@ $isScheduled: Boolean, $cronScheduling: Cron, $scheduleTimezone:TimeZone, - $hasExecutionVariablesEnabled: Boolean) { + $hasExecutionVariablesEnabled: Boolean, + $alias: String) { editPipeline(pipeline: { id: $id @@ -313,10 +318,12 @@ cronScheduling: $cronScheduling scheduleTimezone: $scheduleTimezone hasExecutionVariablesEnabled: $hasExecutionVariablesEnabled + alias: $alias } ){ id name + alias description alerting{ emails @@ -380,21 +387,24 @@ $cronScheduling: Cron, $scheduleTimezone:TimeZone, $jobNodes: [JobNodeInput!], - $conditionNodes: [ConditionNodeInput!]) { - createGraphPipeline(pipeline: { - name: $name - description: $description - projectId: $projectId - releaseNote : $releaseNote - alerting: $alerting - isScheduled: $isScheduled - cronScheduling: $cronScheduling - scheduleTimezone: $scheduleTimezone - graph: { - jobNodes: $jobNodes - conditionNodes: $conditionNodes + $conditionNodes: [ConditionNodeInput!], + $alias: String!) { + createGraphPipeline( + pipeline: { + name: $name + description: $description + projectId: $projectId + releaseNote : $releaseNote + alerting: $alerting + isScheduled: $isScheduled + cronScheduling: $cronScheduling + scheduleTimezone: $scheduleTimezone + graph: { + jobNodes: $jobNodes + conditionNodes: $conditionNodes + } + alias: $alias } - } ) { id } @@ -518,8 +528,8 @@ """ GQL_DUPLICATE_PIPELINE = """ -mutation duplicatePipeline($pipelineId: UUID!) { - duplicatePipeline(originalPipelineId: $pipelineId) { +mutation duplicatePipeline($pipelineId: UUID!, $duplicateJobs: Boolean) { + duplicatePipeline(originalPipelineId: $pipelineId, duplicateJobs: $duplicateJobs) { id name } diff --git a/saagieapi/pipelines/pipelines.py b/saagieapi/pipelines/pipelines.py index 7891b212..f7060ea8 100644 --- a/saagieapi/pipelines/pipelines.py +++ b/saagieapi/pipelines/pipelines.py @@ -376,6 +376,7 @@ def get_info( "graphPipeline": { "id": "5d1999f5-fa70-47d9-9f41-55ad48333629", "name": "Pipeline A", + "alias": "Pipeline_A", "description": "My Pipeline A", "alerting": "NULL", "pipelineInstanceCount": 0, @@ -730,6 +731,7 @@ def create_graph( name: str, project_id: str, graph_pipeline: GraphPipeline, + alias: str, description: str = "", release_note: str = "", emails: List[str] = None, @@ -755,6 +757,8 @@ def create_graph( job_node1.add_next_node(job_node2) # Indicates that the job_node_1 is followed by job_node_2 graph_pipeline = GraphPipeline() graph_pipeline.add_root_node(job_node1) # Indicates the pipeline will start with job_node1 + alias: str + Alias of the pipeline description : str, optional Description of the pipeline release_note: str, optional @@ -827,6 +831,7 @@ def create_graph( "releaseNote": release_note, "jobNodes": graph_pipeline.list_job_nodes, "conditionNodes": graph_pipeline.list_conditions_nodes, + "alias": alias, } if cron_scheduling: @@ -871,7 +876,7 @@ def delete(self, pipeline_id: str) -> Dict: def upgrade(self, pipeline_id: str, graph_pipeline: GraphPipeline, release_note: str = "") -> Dict: """ - Create a pipeline in a given project + Upgrade a pipeline in a given project Parameters ---------- @@ -960,6 +965,7 @@ def edit( self, pipeline_id: str, name: str = None, + alias: str = None, description: str = None, emails: List[str] = None, status_list: List[str] = None, @@ -979,6 +985,9 @@ def edit( name : str, optional Pipeline name, if not filled, defaults to current value, else it will change the pipeline name + alias : str, optional + Alias of the pipeline + if not filled, defaults to current value, else it will change the alias of the pipeline description : str, optional Description of the pipeline if not filled, defaults to current value, else it will change the description of the pipeline @@ -1019,6 +1028,7 @@ def edit( "editPipeline":{ "id": "ca79c5c8-2e57-4a35-bcfc-5065f0ee901c", "name": "Amazing Pipeline 2", + "alias": "Amazing_Pipeline_2", "description": "", "alerting": None, "isScheduled": True, @@ -1033,6 +1043,7 @@ def edit( params = { "id": pipeline_id, "name": name or previous_pipeline_info["name"], + "alias": alias or previous_pipeline_info["alias"], "description": description or previous_pipeline_info["description"], } @@ -1068,6 +1079,7 @@ def edit( def create_or_upgrade( self, name: str, + alias: str, project_id: str, graph_pipeline: GraphPipeline, description: str = None, @@ -1085,6 +1097,8 @@ def create_or_upgrade( ---------- name : str Pipeline name + alias : str + Alias of the pipeline project_id : str UUID of your project (see README on how to find it) graph_pipeline : GraphPipeline @@ -1163,6 +1177,7 @@ def create_or_upgrade( "editPipeline": self.edit( pipeline_id=pipeline_id, name=name, + alias=alias, description=description, emails=emails, status_list=status_list, @@ -1182,6 +1197,7 @@ def create_or_upgrade( k: v for k, v in { "name": name, + "alias": alias, "project_id": project_id, "graph_pipeline": graph_pipeline, "description": description, @@ -1504,6 +1520,7 @@ def import_from_json(self, json_file: str, project_id: str) -> bool: graph_pipeline.list_conditions_nodes = parse_version_conditions(version) res = self.create_graph( name=pipeline_name, + alias=pipeline_info["alias"], project_id=project_id, graph_pipeline=graph_pipeline, description=pipeline_info["description"], @@ -1779,13 +1796,15 @@ def delete_instances_by_date( logging.info("✅ Instances of pipeline [%s] successfully deleted", pipeline_id) return result - def duplicate(self, pipeline_id): + def duplicate(self, pipeline_id, duplicate_jobs: bool = False) -> Dict: """Duplicate a given pipeline Parameters ---------- pipeline_id : str UUID of your pipeline (see README on how to find it) + duplicate_jobs : bool, optional + If True, duplicate the jobs of the pipeline, else only the pipeline Returns ------- diff --git a/saagieapi/storages/gql_queries.py b/saagieapi/storages/gql_queries.py index 99308ced..a491fa97 100644 --- a/saagieapi/storages/gql_queries.py +++ b/saagieapi/storages/gql_queries.py @@ -25,6 +25,9 @@ ...appVersionFieldInformation } } + originalVolumeId + duplicationStatus + isLocked } query projectQuery($id: UUID!, $minimal: Boolean!) { @@ -51,6 +54,9 @@ linkedApp { id } + originalVolumeId + duplicationStatus + isLocked } } """ @@ -118,6 +124,21 @@ ...appVersionFieldInformation } } + originalVolumeId + duplicationStatus + isLocked + } +} +""" + +GQL_DUPLICATE_STORAGE = """ +mutation duplicateVolume($volumeId: UUID!) { + duplicateVolume(originalVolumeId: $volumeId) { + id + name + originalVolumeId + duplicationStatus + isLocked } } """ diff --git a/saagieapi/storages/storages.py b/saagieapi/storages/storages.py index 0f1611d1..f6e93932 100644 --- a/saagieapi/storages/storages.py +++ b/saagieapi/storages/storages.py @@ -71,7 +71,10 @@ def list_for_project( "number":1, "volumesWithPath":[] } - } + }, + "originalVolumeId":"None", + "duplicationStatus":"None", + "isLocked":false }, { "id":"905d8441-8955-444f-a333-19d7c6fe6274", @@ -107,7 +110,10 @@ def list_for_project( } ] } - } + }, + "originalVolumeId":"None", + "duplicationStatus":"None", + "isLocked":false } ] } @@ -179,7 +185,10 @@ def get_info(self, project_id: str, storage_id: str) -> Dict: "number":1, "volumesWithPath":[] } - } + }, + "originalVolumeId":"None", + "duplicationStatus":"None", + "isLocked":false } """ # pylint: disable=line-too-long storages = self.list_for_project(project_id)["project"]["volumes"] @@ -241,7 +250,10 @@ def get(self, storage_id: str) -> Dict: "number":1, "volumesWithPath":[] } - } + }, + "originalVolumeId":"None", + "duplicationStatus":"None", + "isLocked":false } } """ # pylint: disable=line-too-long @@ -294,7 +306,10 @@ def create( "description":"storage description", "creationDate":"2022-09-12T13:52:19.523Z", "creator":"user.name", - "linkedApp":"None" + "linkedApp":"None", + "originalVolumeId":"None", + "duplicationStatus":"None", + "isLocked":false } } """ @@ -487,3 +502,33 @@ def move(self, storage_id: str, target_platform_id: int, target_project_id: str) "targetProjectId": target_project_id, }, ) + + def duplicate(self, storage_id: str): + """Duplicate a storage + + Parameters + ---------- + storage_id : str + UUID of your storage (see README on how to find it) + + Returns + ------- + dict + Dict of the duplicated storage with its new id + + Examples + -------- + >>> saagie_api.storages.duplicate(storage_id='fdb43a11-ccec-4b10-9690-2b83fbd7eb93') + { + 'duplicateVolume': { + id: '29cf1b80-6b9c-47bc-a06c-c20897257097', + name: 'storage name (copy)', + originalVolumeId: 'fdb43a11-ccec-4b10-9690-2b83fbd7eb93', + isLocked: true, + } + } + """ + return self.saagie_api.client.execute( + query=gql(GQL_DUPLICATE_STORAGE), + variable_values={"volumeId": storage_id}, + ) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 6ad830f2..109acc9e 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -164,6 +164,7 @@ def create_graph_pipeline(create_job, create_global_project): graph_pipeline.add_root_node(job_node1) name = "TEST_VIA_API" + alias = "ALIAS_TEST_VIA_API" description = "DESCRIPTION_TEST_VIA_API" cron_scheduling = "0 0 * * *" schedule_timezone = "Pacific/Fakaofo" @@ -172,6 +173,7 @@ def create_graph_pipeline(create_job, create_global_project): project_id=conf.project_id, graph_pipeline=graph_pipeline, name=name, + alias=alias, description=description, cron_scheduling=cron_scheduling, schedule_timezone=schedule_timezone, diff --git a/tests/integration/pipelines_integration_test.py b/tests/integration/pipelines_integration_test.py index 3db21a89..44183521 100644 --- a/tests/integration/pipelines_integration_test.py +++ b/tests/integration/pipelines_integration_test.py @@ -81,6 +81,7 @@ def test_edit_graph_pipeline(create_then_delete_graph_pipeline, create_global_pr pipeline_id, _ = create_then_delete_graph_pipeline pipeline_input = { "name": "test_edit_graph_pipeline", + "alias": "test_edit_alias_graph_pipeline", "description": "test_edit_graph_pipeline", "is_scheduled": True, "cron_scheduling": "0 0 * * *", @@ -91,6 +92,7 @@ def test_edit_graph_pipeline(create_then_delete_graph_pipeline, create_global_pr conf.saagie_api.pipelines.edit( pipeline_id, name=pipeline_input["name"], + alias=pipeline_input["alias"], description=pipeline_input["description"], is_scheduled=pipeline_input["is_scheduled"], cron_scheduling=pipeline_input["cron_scheduling"], @@ -100,6 +102,7 @@ def test_edit_graph_pipeline(create_then_delete_graph_pipeline, create_global_pr pipeline_info = conf.saagie_api.pipelines.get_info(pipeline_id) to_validate = { "name": pipeline_info["graphPipeline"]["name"], + "alias": pipeline_info["graphPipeline"]["alias"], "description": pipeline_info["graphPipeline"]["description"], "alerting": None, "is_scheduled": pipeline_info["graphPipeline"]["isScheduled"], @@ -240,6 +243,7 @@ def test_rollback_pipeline_version(create_then_delete_graph_pipeline, create_glo def test_create_or_upgrade_pipeline(delete_pipeline, create_job, create_global_project): conf = create_global_project pipeline_name = delete_pipeline + pipeline_alias = pipeline_name.replace(" ", "_") job_id = create_job job_node1 = JobNode(job_id) @@ -263,6 +267,7 @@ def test_create_or_upgrade_pipeline(delete_pipeline, create_job, create_global_p pipeline_create = conf.saagie_api.pipelines.create_or_upgrade( name=pipeline_name, + alias=pipeline_alias, project_id=conf.project_id, description="Description pipeline dev test", release_note="First release", @@ -280,6 +285,7 @@ def test_create_or_upgrade_pipeline(delete_pipeline, create_job, create_global_p pipeline_upgrade = conf.saagie_api.pipelines.create_or_upgrade( name=pipeline_name, + alias=pipeline_alias, project_id=conf.project_id, description="Description pipeline dev test", release_note="Second release", diff --git a/tests/integration/resources/import/pipelines/without-existing-jobs/pipeline.json b/tests/integration/resources/import/pipelines/without-existing-jobs/pipeline.json index 5aa51fa5..e25b84ca 100644 --- a/tests/integration/resources/import/pipelines/without-existing-jobs/pipeline.json +++ b/tests/integration/resources/import/pipelines/without-existing-jobs/pipeline.json @@ -1,6 +1,7 @@ { "id": "bd17ddcc-b932-4917-a2a4-17ea582bb3fe", "name": "Pipeline dev test 2", + "alias": "Pipeline_dev_test_2", "description": "Description pipeline dev test", "alerting": { "emails": [ diff --git a/tests/integration/resources/import/project/apps/from-catalog/app.json b/tests/integration/resources/import/project/apps/from-catalog/app.json index 59795e69..0ef8288f 100644 --- a/tests/integration/resources/import/project/apps/from-catalog/app.json +++ b/tests/integration/resources/import/project/apps/from-catalog/app.json @@ -38,7 +38,7 @@ "name": "storage Jupyter Notebook (0)", "creator": "guillaume.prevost", "description": "Autogenerated storage from app installation for \"/notebooks-dir\" path in \"Jupyter Notebook\" app.", - "size": "128 MB", + "size": "1 GB", "projectId": "a3a1d433-6c8b-4ae0-9769-db09e3c9e52a", "creationDate": "2022-08-26T13:12:05.363Z", "linkedApp": { @@ -77,7 +77,7 @@ "name": "storage Jupyter Notebook (0)", "creator": "guillaume.prevost", "description": "Autogenerated storage from app installation for \"/notebooks-dir\" path in \"Jupyter Notebook\" app.", - "size": "128 MB", + "size": "1 GB", "projectId": "a3a1d433-6c8b-4ae0-9769-db09e3c9e52a", "creationDate": "2022-08-26T13:12:05.363Z", "linkedApp": { diff --git a/tests/integration/resources/import/project/apps/from-scratch/app.json b/tests/integration/resources/import/project/apps/from-scratch/app.json index 76675a52..33d60336 100644 --- a/tests/integration/resources/import/project/apps/from-scratch/app.json +++ b/tests/integration/resources/import/project/apps/from-scratch/app.json @@ -41,7 +41,7 @@ "name": "storage Jupyter Notebook (1) (0)", "creator": "guillaume.prevost", "description": "Autogenerated storage from app installation for \"/notebooks-dir\" path in \"Jupyter Notebook (1)\" app.", - "size": "64 MB", + "size": "1 GB", "projectId": "a3a1d433-6c8b-4ae0-9769-db09e3c9e52a", "creationDate": "2022-08-29T08:18:29.962Z", "linkedApp": { @@ -81,7 +81,7 @@ "name": "test", "creator": "guillaume.prevost", "description": "test description", - "size": "128 MB", + "size": "1 GB", "projectId": "a3a1d433-6c8b-4ae0-9769-db09e3c9e52a", "creationDate": "2022-08-29T08:02:11.811Z", "linkedApp": { @@ -149,7 +149,7 @@ "name": "test", "creator": "guillaume.prevost", "description": "test description", - "size": "128 MB", + "size": "1 GB", "projectId": "a3a1d433-6c8b-4ae0-9769-db09e3c9e52a", "creationDate": "2022-08-29T08:02:11.811Z", "linkedApp": { @@ -190,7 +190,7 @@ "name": "test", "creator": "guillaume.prevost", "description": "test description", - "size": "128 MB", + "size": "1 GB", "projectId": "a3a1d433-6c8b-4ae0-9769-db09e3c9e52a", "creationDate": "2022-08-29T08:02:11.811Z", "linkedApp": { @@ -332,7 +332,7 @@ }, "linkedVolumes": [ { - "size": "128 MB" + "size": "1 GB" }, { "size": "64 MB" diff --git a/tests/integration/resources/import/project/pipelines/with-existing-jobs/pipeline.json b/tests/integration/resources/import/project/pipelines/with-existing-jobs/pipeline.json index d2ebd81f..747fc598 100644 --- a/tests/integration/resources/import/project/pipelines/with-existing-jobs/pipeline.json +++ b/tests/integration/resources/import/project/pipelines/with-existing-jobs/pipeline.json @@ -1,6 +1,7 @@ { "id": "bd17ddcc-b932-4917-a2a4-17ea582bb3fe", "name": "Pipeline dev test 2", + "alias": "Pipeline_dev_test_2", "description": "Description pipeline dev test", "alerting": { "emails": [ diff --git a/tests/integration/resources/import/project/pipelines/without-alerting/pipeline.json b/tests/integration/resources/import/project/pipelines/without-alerting/pipeline.json index e8a5c175..9b5e2199 100644 --- a/tests/integration/resources/import/project/pipelines/without-alerting/pipeline.json +++ b/tests/integration/resources/import/project/pipelines/without-alerting/pipeline.json @@ -1,6 +1,7 @@ { "id": "bd17ddcc-b932-4917-a2a4-17ea582bb3fe", "name": "Pipeline test without alerting", + "alias": "Pipeline_test_without_alerting", "description": "Description pipeline dev test", "alerting": null, "pipelineInstanceCount": 0, diff --git a/tests/integration/storages_integration_test.py b/tests/integration/storages_integration_test.py index 664bc5de..5d2ed748 100644 --- a/tests/integration/storages_integration_test.py +++ b/tests/integration/storages_integration_test.py @@ -13,7 +13,7 @@ def create_storage(create_global_project): storage = conf.saagie_api.storages.create( project_id=conf.project_id, storage_name=storage_name, - storage_size="128 MB", + storage_size="1 GB", storage_description="Be happy", ) return storage["createVolume"] @@ -65,7 +65,7 @@ def test_edit_storage(create_then_delete_storage, create_global_project): storage_input = { "id": storage["id"], "name": "storage new name", - "size": "128.0 MB", + "size": "1.0 GB", "description": "storage new description", } @@ -223,3 +223,16 @@ def test_get_storage_info(create_then_delete_storage, create_global_project): del result["volume"]["creationDate"] assert storage == result["volume"] + + @staticmethod + def test_duplicate_storage(create_then_delete_storage, create_global_project): + conf = create_global_project + + storage = create_then_delete_storage + + # needed to let the time to the storage to be created and ready in the orchestrator + time.sleep(5) + + result = conf.saagie_api.storages.duplicate(storage_id=storage["id"]) + + assert result["duplicateVolume"]["originalVolumeId"] == storage["id"] diff --git a/tests/unit/resources/schema.graphqls b/tests/unit/resources/schema.graphqls index 779eb544..f076c514 100644 --- a/tests/unit/resources/schema.graphqls +++ b/tests/unit/resources/schema.graphqls @@ -283,6 +283,14 @@ type Query { # This endpoint allows to retrieve a pipeline with versions in linear or graph format. graphPipelineByName(projectId: UUID!, name: String!): Pipeline + + # Get a pipeline's details from its projectId and alias. + # + # You can only get a pipeline's details if you have at least the viewer role in the project associated to the pipeline or in all projects. + # + # This endpoint allows to retrieve a pipeline with versions in linear or graph format. + graphPipelineByAlias(projectId: UUID!, name: String!): Pipeline! + # List users by realm. usersByRealm: [UserEmail] @@ -608,10 +616,15 @@ type Mutation { # You can only delete a pipeline if you have the global Super-Admin role. deletePipeline(id: UUID!): Boolean! - # Duplicate a pipeline in the same project. Its jobs is not duplicated and only current version of pipeline is duplicated. + # Duplicate a pipeline in the same project. Only current version of pipeline is duplicated. + # By default, pipeline's jobs are not duplicated. # + # **Fields validation:** + # - originalPipelineId: (required) must exist, must be deletable, must not be in migration state. + # - duplicateJobs: (optional, false by default) if true, duplicate all jobs (only each current version) associated with the current version of pipeline. + # You can only duplicate a pipeline if you have at least role editor on the project associated to the pipeline or on all projects. - duplicatePipeline(originalPipelineId: UUID!): Pipeline! + duplicatePipeline(originalPipelineId: UUID!, duplicateJobs: Boolean): Pipeline! # Move pipeline with its jobs from a project to another. The target can be in another platform. # Also move all jobs associated with this pipeline. @@ -710,7 +723,7 @@ type Mutation { # **Fields validation:** # - name: must be unique (case insensitive) in the project, shorter than 255 characters and cannot be empty. # - project: must exist. Use 'projects' query to list all projects. - # - size: size in MB, must be between 64MB and the maximum value set by your administrator. + # - size: size in MB, must be between the step (eq. minimum) and the maximum value set by your administrator. createVolume(volume: VolumeInput!): Volume! # Delete a volume. @@ -738,6 +751,11 @@ type Mutation { # UUID of the volume moved in the target moveVolume(volumeId: UUID!, targetPlatformId: Int!, targetProjectId: UUID!): UUID! + # Duplicate a volume in the same project. + # + # You can only duplicate a volume if you have at least role editor on the project associated to the volume or on all projects. + duplicateVolume(originalVolumeId: UUID!): Volume! + # Create a Connection related to a project. # **Fields validation:** # - name: must be unique (case insensitive) in the project, shorter than 255 characters and cannot be empty. @@ -838,6 +856,7 @@ input JobVersionInput { doesUseGPU: Boolean connectionId: UUID externalJobParams: [ExternalJobParamInput!] + sourceUrl: String } input ExternalJobParamInput { @@ -956,6 +975,7 @@ input SecurityGroupInput { input PipelineInput { name: String! + alias: String! description: String alerting: JobPipelineAlertingInput projectId: UUID! @@ -969,6 +989,7 @@ input PipelineInput { # Input to create a pipeline with a Directed Acyclic Graph input GraphPipelineInput { name: String! + alias: String! description: String alerting: JobPipelineAlertingInput # Enable or disable modification of variables in jobs during execution @@ -1044,6 +1065,7 @@ input NodePositionInput { input PipelineEditionInput { id: UUID! name: String + alias: String description: String alerting: JobPipelineAlertingInput # Enable or disable modification of variables in jobs during execution @@ -1176,6 +1198,10 @@ type Volume { migrationPlatformId: Int migrationProjectId: UUID migrationVolumeId: UUID + duplicationStatus: DuplicationStatus + duplicationTargetId: UUID + originalVolumeId: UUID + isLocked: Boolean } type Job { @@ -1533,6 +1559,7 @@ type JobVersion { doesUseGPU: Boolean @deprecated(reason: "Use Job.doesUseGPU instead") connectionId: UUID externalJobParams: [ExternalJobParam!] + sourceUrl: String } type JobVersionDeletableState { @@ -1661,6 +1688,7 @@ type SecurityGroup { type Pipeline { id: UUID! name: String! + alias: String! description: String alerting: JobPipelineAlerting # Indicates if modification of variables in jobs of this pipeline is enable or disable during execution @@ -1720,10 +1748,8 @@ type OtherPipelineWithSharedJob { type PipelineVersion { number: Int! releaseNote: String - # List of jobs in a linear pipeline - # - # This field is deprecated and used only for linear pipelines, use `graph` instead - jobs: [Job!] @deprecated(reason: "Use graph instead") + # Set of jobs in a linear pipeline (soon deprecated) or graph pipeline + jobs: [Job!] # Corresponds to the graph of jobs and conditions of a pipeline in graph format # For a linear pipeline, it returns a graph corresponding to the list of jobs. graph: PipelineGraph @@ -2086,6 +2112,12 @@ enum MigrationStatus { FAILED } +enum DuplicationStatus { + DUPLICATION_OUT, + DUPLICATION_IN, + FAILED +} + enum TriggerType { MANUAL_JOB, MANUAL_APP,