Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pip install saagieapi==<version>
| >= 2023.02 | >= 2.6.0 |
| >= 2023.03 | >= 2.7.0 |
| >= 2023.04 | >= 2.9.0 |
| >= 2023.05 | >= 2.10.0 |

## Contributing

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Compatibility with your Saagie platform
>= 2023.02 >= 2.6.0
>= 2023.03 >= 2.7.0
>= 2023.04 >= 2.9.0
>= 2023.05 >= 2.10.0
=========================== ======================

Usage
Expand Down
9 changes: 9 additions & 0 deletions saagieapi/pipelines/gql_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,12 @@
)
}
"""

GQL_DUPLICATE_PIPELINE = """
mutation duplicatePipeline($pipelineId: UUID!) {
duplicatePipeline(originalPipelineId: $pipelineId) {
id
name
}
}
"""
29 changes: 29 additions & 0 deletions saagieapi/pipelines/pipelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -1778,3 +1778,32 @@ def delete_instances_by_date(
result = self.saagie_api.client.execute(query=gql(GQL_DELETE_PIPELINE_INSTANCE_BY_DATE), variable_values=params)
logging.info("✅ Instances of pipeline [%s] successfully deleted", pipeline_id)
return result

def duplicate(self, pipeline_id):
"""Duplicate a given pipeline

Parameters
----------
pipeline_id : str
UUID of your pipeline (see README on how to find it)

Returns
-------
dict
Dict of duplicate pipeline with its id and name

Examples
--------
>>> saagie_api.pipelines.duplicate(pipeline_id=pipeline_id)
{
'duplicatePipeline': {
'id': '29cf1b80-6b9c-47bc-a06c-c20897257097',
'name': 'Copy of my_pipeline 2'
}
}
"""
result = self.saagie_api.client.execute(
query=gql(GQL_DUPLICATE_PIPELINE), variable_values={"pipelineId": pipeline_id}
)
logging.info("✅ Pipeline [%s] successfully duplicated", pipeline_id)
return result
42 changes: 42 additions & 0 deletions saagieapi/storages/gql_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
volumes {
id
name
projectId
...volumeInformation @skip(if: $minimal)
}
}
Expand All @@ -43,6 +44,7 @@
createVolume(volume: $volume) {
id
name
projectId
description
size
creator
Expand Down Expand Up @@ -79,3 +81,43 @@
}
}
"""

GQL_MOVE_STORAGE = """
mutation moveVolumeMutation($volumeId: UUID!, $targetPlatformId: Int!, $targetProjectId: UUID!) {
moveVolume(volumeId: $volumeId, targetPlatformId: $targetPlatformId, targetProjectId: $targetProjectId)
}
"""

GQL_GET_STORAGE_INFO = """
fragment appVersionFieldInformation on AppVersion {
number
volumesWithPath {
path
volume {
id
}
}
}

query volumeInfoQuery($volumeId: UUID!) {
volume(id: $volumeId) {
id
name
projectId
size
description
creationDate
creator
linkedApp {
id
name
versions {
...appVersionFieldInformation
}
currentVersion {
...appVersionFieldInformation
}
}
}
}
"""
104 changes: 101 additions & 3 deletions saagieapi/storages/storages.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from typing import Dict, Optional

import deprecation
from gql import gql

from .gql_queries import *
Expand Down Expand Up @@ -120,6 +121,11 @@ def list_for_project(
query=gql(GQL_LIST_STORAGE_FOR_PROJECT), variable_values=params, pprint_result=pprint_result
)

@deprecation.deprecated(
details="This function is deprecated and will be removed in a future version. "
"Please use :func:`get()` instead.",
deprecated_in="2.10.0",
)
def get_info(self, project_id: str, storage_id: str) -> Dict:
"""Get storage information.

Expand All @@ -144,7 +150,7 @@ def get_info(self, project_id: str, storage_id: str) -> Dict:
--------
>>> saagieapi.storages.get_info(
... project_id="860b8dc8-e634-4c98-b2e7-f9ec32ab4771",
... job_id="f5fce22d-2152-4a01-8c6a-4c2eb4808b6d"
... storage_id="f5fce22d-2152-4a01-8c6a-4c2eb4808b6d"
... )
{
"id":"89bf5f86-3fc3-4bf6-879b-7ca8eafe6c4f",
Expand Down Expand Up @@ -183,6 +189,65 @@ def get_info(self, project_id: str, storage_id: str) -> Dict:

raise ValueError(f"❌ Storage '{storage_id}' not found in project '{project_id}'")

def get(self, storage_id: str) -> Dict:
"""Get storage information.

Note
----
You can only get storage information if you have at least the viewer role on
the project.

Parameters
----------
storage_id : str
UUID of your storage

Returns
-------
dict
Dict of storage information

Examples
--------
>>> saagieapi.storages.get(
... storage_id="f5fce22d-2152-4a01-8c6a-4c2eb4808b6d"
... )
{
"volume":
{
"id":"89bf5f86-3fc3-4bf6-879b-7ca8eafe6c4f",
"name":"storage Jupyter Notebook (0)",
"size":"64 MB",
"description":"Autogenerated storage from app installation for \"/notebooks-dir\" path in \"Jupyter Notebook\" app.",
"creationDate":"2022-08-26T13:12:05.363Z",
"creator":"user.name",
"linkedApp":{
"id":"6871e9a2-2c06-45fe-bf8d-6356090f1d1d",
"name":"Jupyter Notebook",
"versions":[
{
"number":1,
"volumesWithPath":[
{
"path":"/notebooks-dir",
"volume":{
"id":"89bf5f86-3fc3-4bf6-879b-7ca8eafe6c4f"
}
}
]
}
],
"currentVersion":{
"number":1,
"volumesWithPath":[]
}
}
}
}
""" # pylint: disable=line-too-long

return self.saagie_api.client.execute(query=gql(GQL_GET_STORAGE_INFO), variable_values={"volumeId": storage_id})

def create(
self,
project_id: str,
Expand Down Expand Up @@ -337,7 +402,7 @@ def delete(self, storage_id: str, project_id: str) -> Dict:
}
}
"""
storage_info = self.get_info(project_id, storage_id)
storage_info = self.get(storage_id=storage_id)["volume"]
if storage_info["linkedApp"] is not None and "currentVersion" in storage_info["linkedApp"]:
for volume in storage_info["linkedApp"]["currentVersion"]["volumesWithPath"]:
if volume["volume"]["id"] == storage_id:
Expand Down Expand Up @@ -380,7 +445,7 @@ def unlink(self, storage_id: str, project_id: str) -> Dict:
}
}
"""
storage_info = self.get_info(project_id, storage_id)
storage_info = self.get(storage_id=storage_id)["volume"]
if storage_info["linkedApp"] is not None and "currentVersion" in storage_info["linkedApp"].keys():
for volume in storage_info["linkedApp"]["currentVersion"]["volumesWithPath"]:
if volume["volume"]["id"] == storage_id:
Expand All @@ -389,3 +454,36 @@ def unlink(self, storage_id: str, project_id: str) -> Dict:
result = self.saagie_api.client.execute(query=gql(GQL_UNLINK_STORAGE), variable_values={"id": storage_id})
logging.info("✅ Storage [%s] successfully unlinked", storage_id)
return result

def move(self, storage_id: str, target_platform_id: int, target_project_id: str):
"""Move a storage to another project in the same platform or another one

Parameters
----------
storage_id : str
UUID of your storage (see README on how to find it)
target_platform_id : int
Id of the platform to move the storage to
target_project_id : str
UUID of the project to move the storage to

Returns
-------
dict
Dict of the moved storage with its new id

Examples
--------
>>> saagie_api.storages.move(storage_id=storage_id, target_platform_id=1, target_project_id=project_id)
{
'moveVolume': '29cf1b80-6b9c-47bc-a06c-c20897257097',
}
"""
return self.saagie_api.client.execute(
query=gql(GQL_MOVE_STORAGE),
variable_values={
"volumeId": storage_id,
"targetPlatformId": target_platform_id,
"targetProjectId": target_project_id,
},
)
11 changes: 11 additions & 0 deletions tests/integration/pipelines_integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,3 +406,14 @@ def test_get_info_by_name(create_global_project, create_then_delete_graph_pipeli
result = conf.saagie_api.pipelines.get_info_by_name(pipeline_name="TEST_VIA_API", project_id=conf.project_id)

assert result["graphPipelineByName"]["id"] == pipeline_id

@staticmethod
def test_duplicate_pipeline(create_global_project, create_then_delete_graph_pipeline):
conf = create_global_project
pipeline_id, _ = create_then_delete_graph_pipeline

result = conf.saagie_api.pipelines.duplicate(pipeline_id=pipeline_id)

conf.saagie_api.pipelines.delete(pipeline_id=result["duplicatePipeline"]["id"])

assert "duplicatePipeline" in result
57 changes: 57 additions & 0 deletions tests/integration/storages_integration_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import time
from datetime import datetime

import pytest
Expand Down Expand Up @@ -166,3 +167,59 @@ def test_unlink_used_storage(create_global_project):
with pytest.raises(ValueError) as vale:
conf.saagie_api.storages.unlink(storage_id, conf.project_id)
assert str(vale.value).startswith(f"❌ Storage '{storage_id}' is currently used by an App. Unlink impossible.")

@staticmethod
def test_move_storage(create_global_project, create_storage):
conf = create_global_project

storage = create_storage

res_proj = conf.saagie_api.projects.create(
name="test_move_storage",
group=conf.group,
role="Manager",
jobs_technologies_allowed={"saagie": ["python", "spark", "bash"]},
description="test_move_storage",
)

project_id = res_proj["createProject"]["id"]

# Waiting for the project to be ready
project_status = conf.saagie_api.projects.get_info(project_id=project_id)["project"]["status"]
waiting_time = 0

# Safety: wait for 5min max for project initialisation
project_creation_timeout = 400
while project_status != "READY" and waiting_time <= project_creation_timeout:
time.sleep(10)
project_status = conf.saagie_api.projects.get_info(project_id)["project"]["status"]
waiting_time += 10
if project_status != "READY":
raise TimeoutError(
f"Project creation is taking longer than usual, "
f"aborting integration tests after {project_creation_timeout} seconds"
)

result = conf.saagie_api.storages.move(
storage_id=storage["id"],
target_platform_id=1,
target_project_id=project_id,
)

res = conf.saagie_api.storages.get(storage_id=result["moveVolume"])

conf.saagie_api.projects.delete(project_id)

# assert result == {"moveVolume": storage["id"]}
assert res["volume"]["name"] == storage["name"]

@staticmethod
def test_get_storage_info(create_then_delete_storage, create_global_project):
conf = create_global_project

storage = create_then_delete_storage

result = conf.saagie_api.storages.get(storage_id=storage["id"])
del result["volume"]["creationDate"]

assert storage == result["volume"]
4 changes: 4 additions & 0 deletions tests/unit/pipelines_unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,7 @@ def test_count_deletable_instances_by_status(self):
def test_count_deletable_instances_by_date(self):
query = gql(GQL_COUNT_DELETABLE_PIPELINE_INSTANCE_BY_DATE)
self.client.validate(query)

def test_duplicate_pipeline(self):
query = gql(GQL_DUPLICATE_PIPELINE)
self.client.validate(query)
Loading