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
90 changes: 90 additions & 0 deletions saagieapi/apps/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,7 @@ def edit(
emails: List = None,
logins: List = None,
status_list: List = None,
resources: Dict = None,
) -> Dict:
"""Edit an app. \
Each optional parameter can be set to change the value of the corresponding field.
Expand All @@ -694,6 +695,8 @@ def edit(
Receive an email when the app status change to a specific status.
Each item of the list should be one of these following values: "STARTING","STARTED",
"ROLLING_BACK","UPGRADING","RECOVERING","RESTARTING","STOPPING","STOPPED","FAILED"
resources : dict, optional
Resources CPU, RAM & GPU limited and guaranteed for the app.

Returns
-------
Expand Down Expand Up @@ -728,6 +731,9 @@ def edit(
if emails or logins or status_list:
params["alerting"] = self.check_alerting(emails, logins, status_list)

if resources:
params["resources"] = resources

params = {"app": params}
result = self.saagie_api.client.execute(query=gql(GQL_EDIT_APP), variable_values=params)
logging.info("✅ App [%s] successfully edited", app_id)
Expand Down Expand Up @@ -1458,3 +1464,87 @@ def count_history_statuses(self, history_id, version_number, start_time):
}

return self.saagie_api.client.execute(query=gql(GQL_COUNT_HISTORY_APP_STATUS), variable_values=params)

def get_logs(
self,
app_id: str,
app_execution_id: str,
limit: int = None,
skip: int = None,
log_stream: str = None,
start_at: str = None,
):
"""Get logs of the app

Parameters
----------
app_id : str
UUID of your app
app_execution_id : str
UUID of the execution of your app
limit : int, optional
Number of log lines to retrieve
skip : int, optional
Number of log lines to skip
log_stream: str, optional
Stream of logs to follow. Values accepted :
[ENVVARS_STDOUT, ENVVARS_STDERR, ORCHESTRATION_STDOUT, ORCHESTRATION_STDERR, STDERR, STDOUT]
By default, all the streams are retrieved
start_at: str, optional
Get logs since a specific datetime.
Following formats accepted : "2024-04-09 10:00:00" and "2024-04-09T10:00:00"

Returns
-------
dict
Logs of the app

Examples
--------
>>> saagieapi.apps.get_logs(
... app_id="70e85ade-d6cc-4a90-8d7d-639adbd25e5d",
... app_execution_id="e3e31074-4a12-450e-96e4-0eae7801dfca",
... limit=2,
... skip=5,
... start_at="2024-04-09 10:00:00"
... )
{
"appLogs": {
"count": 25,
"content": [
{
"index": 5,
"value": "[I 2024-04-09 13:38:36.982 ServerApp] jupyterlab_git | extension was successfully linked.",
"containerId": "d7104fa7371c5ed6ef540fa8b0620a654a0e02c57136e29f0fcc03d16e36d74f",
"stream": "STDERR",
"recordAt": "2024-04-09T13:38:36.982473892Z"
},
{
"index": 6,
"value": "[W 2024-04-09 13:38:36.987 NotebookApp] 'ip' has moved from NotebookApp to ServerApp. This config will be passed to ServerApp. Be sure to update your config before our next release.",
"containerId": "d7104fa7371c5ed6ef540fa8b0620a654a0e02c57136e29f0fcc03d16e36d74f",
"stream": "STDERR",
"recordAt": "2024-04-09T13:38:36.987400105Z"
}
]
}
}
"""
params = {
"appId": app_id,
"appExecutionId": app_execution_id,
}

if limit:
params["limit"] = limit

if skip:
params["skip"] = skip

if log_stream:
params["stream"] = log_stream

if start_at:
params["recordAt"] = start_at

return self.saagie_api.client.execute(query=gql(GQL_GET_APP_LOG), variable_values=params)
16 changes: 16 additions & 0 deletions saagieapi/apps/gql_queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,19 @@
countAppHistoryStatuses(appHistoryId: $appHistoryId, versionNumber: $versionNumber, startTime: $startTime)
}
"""

GQL_GET_APP_LOG = """
query appLogs($appId: UUID!, $appExecutionId: UUID!, $limit: Int, $skip: Int, $stream: LogStream, $recordAt: String) {
appLogs(appId: $appId, appExecutionId: $appExecutionId, limit: $limit, skip: $skip, stream: $stream, recordAt: $recordAt)
{
count
content {
index
value
containerId
stream
recordAt
}
}
}
"""
65 changes: 49 additions & 16 deletions saagieapi/env_vars/env_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,19 @@ def handle_error(msg, project_id):
return False


def check_scope(scope, project_id, pipeline_id):
if scope not in ("GLOBAL", "PROJECT", "PIPELINE"):
raise ValueError("❌ 'scope' must be one of GLOBAL, PROJECT or PIPELINE")
def check_scope(scope, project_id, pipeline_id, app_id):
if scope not in ("GLOBAL", "PROJECT", "PIPELINE", "APP"):
raise ValueError("❌ 'scope' must be one of GLOBAL, PROJECT, PIPELINE or APP")

if scope == "PROJECT" and project_id is None:
raise ValueError("❌ 'project_id' must be provided for scope PROJECT")

if scope == "PIPELINE" and pipeline_id is None:
raise ValueError("❌ 'pipeline_id' must be provided for scope PIPELINE")

if scope == "APP" and app_id is None:
raise ValueError("❌ 'app_id' must be provided for scope APP")


class EnvVars:
# pylint: disable=singleton-comparison
Expand Down Expand Up @@ -977,6 +980,7 @@ def list(
scope: str,
project_id: str = None,
pipeline_id: str = None,
app_id: str = None,
scope_only: bool = False,
pprint_result: Optional[bool] = None,
) -> Dict:
Expand All @@ -992,6 +996,8 @@ def list(
UUID of your project (see README on how to find it)
pipeline_id : str, optional
UUID of your pipeline (see README on how to find it)
app_id : str, optional
UUID of your app (see README on how to find it)
scope_only : bool, optional
Whether to return only the environment variables of the given scope
pprint_result : bool, optional
Expand Down Expand Up @@ -1031,7 +1037,7 @@ def list(
}
]
"""
check_scope(scope, project_id, pipeline_id)
check_scope(scope, project_id, pipeline_id, app_id)

if scope == "GLOBAL":
res = self.saagie_api.client.execute(query=gql(GQL_LIST_GLOBAL_ENV_VARS), pprint_result=pprint_result)[
Expand All @@ -1049,6 +1055,12 @@ def list(
variable_values={"pipelineId": pipeline_id},
pprint_result=pprint_result,
)["pipelineEnvironmentVariables"]
elif scope == "APP":
res = self.saagie_api.client.execute(
query=gql(GQL_LIST_APP_ENV_VARS),
variable_values={"appId": app_id},
pprint_result=pprint_result,
)["appEnvironmentVariables"]

return [env for env in res if env["scope"] == scope] if scope_only else res

Expand All @@ -1058,6 +1070,7 @@ def get(
name: str,
project_id: str = None,
pipeline_id: str = None,
app_id: str = None,
scope_only: bool = False,
pprint_result: Optional[bool] = None,
) -> Dict:
Expand All @@ -1076,6 +1089,8 @@ def get(
UUID of your project (see README on how to find it)
pipeline_id : str, optional
UUID of your pipeline (see README on how to find it)
app_id : str, optional
UUID of your app (see README on how to find it)
scope_only : bool, optional
Whether to return only the environment variables of the given scope
pprint_result : bool, optional
Expand All @@ -1101,9 +1116,9 @@ def get(
'invalidReasons': None
}
"""
check_scope(scope, project_id, pipeline_id)
check_scope(scope, project_id, pipeline_id, app_id)

env_vars = self.list(scope, project_id, pipeline_id, scope_only, pprint_result)
env_vars = self.list(scope, project_id, pipeline_id, app_id, scope_only, pprint_result)

return next((d for d in env_vars if d["name"] == name), None)

Expand All @@ -1116,6 +1131,7 @@ def create(
is_password: bool = False,
project_id: str = None,
pipeline_id: str = None,
app_id: str = None,
) -> Dict:
"""Create an environment variable in a given scope

Expand All @@ -1135,6 +1151,8 @@ def create(
UUID of your project (see README on how to find it)
pipeline_id : str, optional
UUID of your pipeline (see README on how to find it)
app_id : str, optional
UUID of your app (see README on how to find it)

Returns
-------
Expand All @@ -1158,7 +1176,7 @@ def create(
}
"""

check_scope(scope, project_id, pipeline_id)
check_scope(scope, project_id, pipeline_id, app_id)

params = {
"envVar": {
Expand All @@ -1170,10 +1188,12 @@ def create(
},
}

if scope == "PIPELINE":
params["entityId"] = pipeline_id
elif scope == "PROJECT":
if scope == "PROJECT":
params["entityId"] = project_id
elif scope == "PIPELINE":
params["entityId"] = pipeline_id
elif scope == "APP":
params["entityId"] = app_id

result = self.saagie_api.client.execute(query=gql(GQL_CREATE_ENV_VAR), variable_values=params)
logging.info("✅ Environment variable [%s] successfully created", name)
Expand All @@ -1189,6 +1209,7 @@ def update(
is_password: bool = None,
project_id: str = None,
pipeline_id: str = None,
app_id: str = None,
) -> Dict:
"""
Update environment variable with provided function variables if it exists
Expand All @@ -1211,6 +1232,8 @@ def update(
UUID of your project (see README on how to find it)
pipeline_id : str, optional
UUID of your pipeline (see README on how to find it)
app_id : str, optional
UUID of your app (see README on how to find it)

Returns
-------
Expand Down Expand Up @@ -1239,9 +1262,9 @@ def update(
}
"""

check_scope(scope, project_id, pipeline_id)
check_scope(scope, project_id, pipeline_id, app_id)

existing_env_var = self.list(scope, project_id, pipeline_id, scope_only=True, pprint_result=False)
existing_env_var = self.list(scope, project_id, pipeline_id, app_id, scope_only=True, pprint_result=False)

var = next((d for d in existing_env_var if d["name"] == name), None)
if var is None:
Expand All @@ -1255,6 +1278,8 @@ def update(
params["entityId"] = project_id
elif scope == "PIPELINE":
params["entityId"] = pipeline_id
elif scope == "APP":
params["entityId"] = app_id

params["envVar"].pop("isValid", None)
params["envVar"].pop("overriddenValues", None)
Expand Down Expand Up @@ -1282,6 +1307,7 @@ def create_or_update(
is_password: bool = None,
project_id: str = None,
pipeline_id: str = None,
app_id: str = None,
) -> Dict:
"""
Create a new environment variable or update it if it already exists
Expand All @@ -1302,6 +1328,8 @@ def create_or_update(
UUID of your project (see README on how to find it)
pipeline_id : str, optional
UUID of your pipeline (see README on how to find it)
app_id : str, optional
UUID of your app (see README on how to find it)

Returns
-------
Expand All @@ -1324,9 +1352,9 @@ def create_or_update(
}
"""

check_scope(scope, project_id, pipeline_id)
check_scope(scope, project_id, pipeline_id, app_id)

existing_env_var = self.list(scope, project_id, pipeline_id, scope_only=True, pprint_result=False)
existing_env_var = self.list(scope, project_id, pipeline_id, app_id, scope_only=True, pprint_result=False)

var = next((d for d in existing_env_var if d["name"] == name), None)

Expand All @@ -1340,6 +1368,7 @@ def create_or_update(
"is_password": is_password,
"project_id": project_id,
"pipeline_id": pipeline_id,
"app_id": app_id,
}
args2 = {k: v for k, v in args.items() if v is not None}
return self.create(**args2)
Expand All @@ -1352,6 +1381,7 @@ def create_or_update(
is_password=is_password,
project_id=project_id,
pipeline_id=pipeline_id,
app_id=app_id,
)

def delete(
Expand All @@ -1360,6 +1390,7 @@ def delete(
name: str,
project_id: str = None,
pipeline_id: str = None,
app_id: str = None,
) -> Dict:
"""Delete a given environment variable inside a given scope

Expand All @@ -1373,6 +1404,8 @@ def delete(
UUID of your project (see README on how to find it)
pipeline_id : str, optional
UUID of your pipeline (see README on how to find it)
app_id : str, optional
UUID of your app (see README on how to find it)

Returns
-------
Expand All @@ -1392,9 +1425,9 @@ def delete(
"deleteEnvironmentVariable": True
}
"""
check_scope(scope, project_id, pipeline_id)
check_scope(scope, project_id, pipeline_id, app_id)

existing_env_var = self.list(scope, project_id, pipeline_id, scope_only=True, pprint_result=False)
existing_env_var = self.list(scope, project_id, pipeline_id, app_id, scope_only=True, pprint_result=False)

var = next((d for d in existing_env_var if d["name"] == name), None)
if var is None:
Expand Down
Loading