truefoundry 0.2.10__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
Potentially problematic release.
This version of truefoundry might be problematic. Click here for more details.
- truefoundry/__init__.py +1 -0
- truefoundry/autodeploy/cli.py +31 -18
- truefoundry/deploy/__init__.py +112 -1
- truefoundry/deploy/auto_gen/models.py +1714 -0
- truefoundry/deploy/builder/__init__.py +134 -0
- truefoundry/deploy/builder/builders/__init__.py +22 -0
- truefoundry/deploy/builder/builders/dockerfile.py +57 -0
- truefoundry/deploy/builder/builders/tfy_notebook_buildpack/__init__.py +46 -0
- truefoundry/deploy/builder/builders/tfy_notebook_buildpack/dockerfile_template.py +66 -0
- truefoundry/deploy/builder/builders/tfy_python_buildpack/__init__.py +44 -0
- truefoundry/deploy/builder/builders/tfy_python_buildpack/dockerfile_template.py +158 -0
- truefoundry/deploy/builder/docker_service.py +168 -0
- truefoundry/deploy/cli/cli.py +21 -26
- truefoundry/deploy/cli/commands/__init__.py +18 -0
- truefoundry/deploy/cli/commands/apply_command.py +52 -0
- truefoundry/deploy/cli/commands/build_command.py +45 -0
- truefoundry/deploy/cli/commands/build_logs_command.py +89 -0
- truefoundry/deploy/cli/commands/create_command.py +75 -0
- truefoundry/deploy/cli/commands/delete_command.py +77 -0
- truefoundry/deploy/cli/commands/deploy_command.py +102 -0
- truefoundry/deploy/cli/commands/get_command.py +216 -0
- truefoundry/deploy/cli/commands/list_command.py +171 -0
- truefoundry/deploy/cli/commands/login_command.py +33 -0
- truefoundry/deploy/cli/commands/logout_command.py +20 -0
- truefoundry/deploy/cli/commands/logs_command.py +134 -0
- truefoundry/deploy/cli/commands/patch_application_command.py +81 -0
- truefoundry/deploy/cli/commands/patch_command.py +70 -0
- truefoundry/deploy/cli/commands/redeploy_command.py +41 -0
- truefoundry/deploy/cli/commands/terminate_comand.py +44 -0
- truefoundry/deploy/cli/commands/trigger_command.py +145 -0
- truefoundry/deploy/cli/config.py +10 -0
- truefoundry/deploy/cli/console.py +5 -0
- truefoundry/deploy/cli/const.py +12 -0
- truefoundry/deploy/cli/display_util.py +118 -0
- truefoundry/deploy/cli/util.py +129 -0
- truefoundry/deploy/core/__init__.py +7 -0
- truefoundry/deploy/core/login.py +9 -0
- truefoundry/deploy/core/logout.py +5 -0
- truefoundry/deploy/function_service/__init__.py +3 -0
- truefoundry/deploy/function_service/__main__.py +27 -0
- truefoundry/deploy/function_service/app.py +92 -0
- truefoundry/deploy/function_service/build.py +45 -0
- truefoundry/deploy/function_service/remote/__init__.py +6 -0
- truefoundry/deploy/function_service/remote/context.py +3 -0
- truefoundry/deploy/function_service/remote/method.py +67 -0
- truefoundry/deploy/function_service/remote/remote.py +144 -0
- truefoundry/deploy/function_service/route.py +137 -0
- truefoundry/deploy/function_service/service.py +113 -0
- truefoundry/deploy/function_service/utils.py +53 -0
- truefoundry/deploy/io/__init__.py +0 -0
- truefoundry/deploy/io/output_callback.py +23 -0
- truefoundry/deploy/io/rich_output_callback.py +27 -0
- truefoundry/deploy/json_util.py +7 -0
- truefoundry/deploy/lib/__init__.py +0 -0
- truefoundry/deploy/lib/auth/auth_service_client.py +181 -0
- truefoundry/deploy/lib/auth/credential_file_manager.py +115 -0
- truefoundry/deploy/lib/auth/credential_provider.py +131 -0
- truefoundry/deploy/lib/auth/servicefoundry_session.py +59 -0
- truefoundry/deploy/lib/clients/__init__.py +0 -0
- truefoundry/deploy/lib/clients/servicefoundry_client.py +746 -0
- truefoundry/deploy/lib/clients/shell_client.py +13 -0
- truefoundry/deploy/lib/clients/utils.py +41 -0
- truefoundry/deploy/lib/const.py +43 -0
- truefoundry/deploy/lib/dao/__init__.py +0 -0
- truefoundry/deploy/lib/dao/application.py +263 -0
- truefoundry/deploy/lib/dao/apply.py +80 -0
- truefoundry/deploy/lib/dao/version.py +33 -0
- truefoundry/deploy/lib/dao/workspace.py +71 -0
- truefoundry/deploy/lib/exceptions.py +26 -0
- truefoundry/deploy/lib/logs_utils.py +43 -0
- truefoundry/deploy/lib/messages.py +12 -0
- truefoundry/deploy/lib/model/__init__.py +0 -0
- truefoundry/deploy/lib/model/entity.py +400 -0
- truefoundry/deploy/lib/session.py +158 -0
- truefoundry/deploy/lib/util.py +90 -0
- truefoundry/deploy/lib/win32.py +129 -0
- truefoundry/deploy/v2/__init__.py +0 -0
- truefoundry/deploy/v2/lib/__init__.py +3 -0
- truefoundry/deploy/v2/lib/deploy.py +283 -0
- truefoundry/deploy/v2/lib/deploy_workflow.py +295 -0
- truefoundry/deploy/v2/lib/deployable_patched_models.py +86 -0
- truefoundry/deploy/v2/lib/models.py +53 -0
- truefoundry/deploy/v2/lib/patched_models.py +479 -0
- truefoundry/deploy/v2/lib/source.py +267 -0
- truefoundry/langchain/__init__.py +12 -1
- truefoundry/langchain/deprecated.py +302 -0
- truefoundry/langchain/truefoundry_chat.py +130 -0
- truefoundry/langchain/truefoundry_embeddings.py +171 -0
- truefoundry/langchain/truefoundry_llm.py +106 -0
- truefoundry/langchain/utils.py +85 -0
- truefoundry/logger.py +17 -0
- truefoundry/pydantic_v1.py +5 -0
- truefoundry/python_deploy_codegen.py +132 -0
- truefoundry/version.py +6 -0
- truefoundry/workflow/__init__.py +19 -0
- truefoundry/workflow/container_task.py +12 -0
- truefoundry/workflow/example/deploy.sh +1 -0
- truefoundry/workflow/example/hello_world_package/workflow.py +20 -0
- truefoundry/workflow/example/package/test_workflow.py +152 -0
- truefoundry/workflow/example/truefoundry.yaml +9 -0
- truefoundry/workflow/example/workflow.yaml +116 -0
- truefoundry/workflow/map_task.py +45 -0
- truefoundry/workflow/python_task.py +32 -0
- truefoundry/workflow/task.py +50 -0
- truefoundry/workflow/workflow.py +114 -0
- {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/METADATA +27 -7
- truefoundry-0.3.0.dist-info/RECORD +136 -0
- truefoundry/deploy/cli/deploy.py +0 -165
- truefoundry/deploy/cli/version.py +0 -6
- truefoundry-0.2.10.dist-info/RECORD +0 -38
- {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/WHEEL +0 -0
- {truefoundry-0.2.10.dist-info → truefoundry-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Dict, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
import requirements
|
|
7
|
+
from flytekit.configuration import (
|
|
8
|
+
SERIALIZED_CONTEXT_ENV_VAR,
|
|
9
|
+
ImageConfig,
|
|
10
|
+
SerializationSettings,
|
|
11
|
+
)
|
|
12
|
+
from flytekit.configuration import Image as FlytekitImage
|
|
13
|
+
from flytekit.models.launch_plan import LaunchPlan as FlyteLaunchPlan
|
|
14
|
+
from flytekit.tools.repo import serialize as serialize_workflow
|
|
15
|
+
from flytekit.tools.translator import TaskSpec as FlyteTaskSpec
|
|
16
|
+
from flytekit.tools.translator import WorkflowSpec as FlyteWorkflowSpec
|
|
17
|
+
from google.protobuf.json_format import MessageToDict
|
|
18
|
+
|
|
19
|
+
from truefoundry.deploy.auto_gen import models as auto_gen_models
|
|
20
|
+
from truefoundry.deploy.lib.clients.servicefoundry_client import (
|
|
21
|
+
ServiceFoundryServiceClient,
|
|
22
|
+
)
|
|
23
|
+
from truefoundry.deploy.lib.dao.workspace import get_workspace_by_fqn
|
|
24
|
+
from truefoundry.deploy.lib.model.entity import Deployment
|
|
25
|
+
from truefoundry.deploy.v2.lib.source import (
|
|
26
|
+
local_source_to_remote_source,
|
|
27
|
+
)
|
|
28
|
+
from truefoundry.logger import logger
|
|
29
|
+
from truefoundry.pydantic_v1 import ValidationError
|
|
30
|
+
from truefoundry.workflow.workflow import (
|
|
31
|
+
TRUEFOUNDRY_LAUNCH_PLAN_NAME,
|
|
32
|
+
execution_config_store,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _handle_code_upload_for_workflow(
|
|
37
|
+
workflow: auto_gen_models.Workflow, workspace_fqn: str
|
|
38
|
+
) -> auto_gen_models.Workflow:
|
|
39
|
+
new_workflow = workflow.copy(deep=True)
|
|
40
|
+
new_workflow.source = local_source_to_remote_source(
|
|
41
|
+
local_source=workflow.source,
|
|
42
|
+
workspace_fqn=workspace_fqn,
|
|
43
|
+
component_name=workflow.name,
|
|
44
|
+
)
|
|
45
|
+
return new_workflow
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _is_tfy_workflow_package(package: requirements.parser.Requirement) -> bool:
|
|
49
|
+
return package.name == "truefoundry" and "workflow" in package.extras
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _is_tfy_wf_present_in_pip_packages_or_requirements_file(
|
|
53
|
+
pip_packages: List[str],
|
|
54
|
+
project_root_path: str,
|
|
55
|
+
requirements_path: Optional[str] = None,
|
|
56
|
+
) -> bool:
|
|
57
|
+
for package in pip_packages:
|
|
58
|
+
parsed_package = requirements.parser.Requirement.parse(package)
|
|
59
|
+
if _is_tfy_workflow_package(parsed_package):
|
|
60
|
+
return True
|
|
61
|
+
if requirements_path:
|
|
62
|
+
requirements_file_absolute_path = os.path.join(
|
|
63
|
+
project_root_path, requirements_path
|
|
64
|
+
)
|
|
65
|
+
if not os.path.exists(requirements_file_absolute_path):
|
|
66
|
+
raise FileNotFoundError(
|
|
67
|
+
f"requirements file not found at {requirements_file_absolute_path}. requirements file path should be relative to project root path."
|
|
68
|
+
)
|
|
69
|
+
with open(requirements_file_absolute_path, "r") as file:
|
|
70
|
+
for package in requirements.parse(file):
|
|
71
|
+
if _is_tfy_workflow_package(package):
|
|
72
|
+
return True
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _is_tfy_wf_present_in_task_python_build(
|
|
77
|
+
task_image_spec: Dict, project_root_path: str
|
|
78
|
+
) -> bool:
|
|
79
|
+
pip_packages = task_image_spec["pip_packages"] or []
|
|
80
|
+
requirements_path = task_image_spec.get("requirements_path")
|
|
81
|
+
return _is_tfy_wf_present_in_pip_packages_or_requirements_file(
|
|
82
|
+
pip_packages=pip_packages,
|
|
83
|
+
project_root_path=project_root_path,
|
|
84
|
+
requirements_path=requirements_path,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _is_dynamic_task(flyte_task: FlyteTaskSpec) -> bool:
|
|
89
|
+
envs: Dict[str:str] = flyte_task.template.container.env or {}
|
|
90
|
+
return SERIALIZED_CONTEXT_ENV_VAR in envs.keys()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# this function does validation that num_workflows = 1, this also validates task_config is passed correctly.
|
|
94
|
+
# This is verified by pydantic but doing it here also as error messages are not clear in pydantic
|
|
95
|
+
def _validate_workflow_entities( # noqa: C901
|
|
96
|
+
workflow_entities: List[Union[FlyteWorkflowSpec, FlyteLaunchPlan, FlyteTaskSpec]],
|
|
97
|
+
project_root_path: str,
|
|
98
|
+
):
|
|
99
|
+
workflow_objs: List[FlyteWorkflowSpec] = []
|
|
100
|
+
launch_plans: List[FlyteLaunchPlan] = []
|
|
101
|
+
tasks: List[FlyteTaskSpec] = []
|
|
102
|
+
for entity in workflow_entities:
|
|
103
|
+
if isinstance(entity, FlyteWorkflowSpec):
|
|
104
|
+
workflow_objs.append(entity)
|
|
105
|
+
elif isinstance(entity, FlyteLaunchPlan):
|
|
106
|
+
launch_plans.append(entity)
|
|
107
|
+
elif isinstance(entity, FlyteTaskSpec):
|
|
108
|
+
tasks.append(entity)
|
|
109
|
+
else:
|
|
110
|
+
raise ValueError(f"Invalid entity found in workflow: {entity}")
|
|
111
|
+
if len(workflow_objs) != 1:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
f"Workflow file must have exactly one workflow object. Found {len(workflow_objs)}"
|
|
114
|
+
)
|
|
115
|
+
if len(launch_plans) > 2:
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"Workflow file must have exactly one launch plan. Found {len(launch_plans) - 1}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
error_message_to_use_truefoundry_decorators = """Invalid task definition for task: {}, Please use valid truefoundry decorator/class and pass task_config for tasks.
|
|
121
|
+
You can import truefoundry task decorators using:
|
|
122
|
+
`from truefoundry.workflow import task, ContainerTask, map_task`
|
|
123
|
+
You can pass task config using `task_config` parameter in the task definition. Task config should be one of the following:
|
|
124
|
+
`PythonTaskConfig`, or `ContainerTaskConfig`. You can import these using:
|
|
125
|
+
`from truefoundry.workflow import PythonTaskConfig, ContainerTaskConfig`
|
|
126
|
+
"""
|
|
127
|
+
tasks_without_truefoundry_worflow_package = []
|
|
128
|
+
for task in tasks:
|
|
129
|
+
if _is_dynamic_task(task):
|
|
130
|
+
raise ValueError("Dynamic workflows are not supported yet.")
|
|
131
|
+
if not task.template.custom:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
error_message_to_use_truefoundry_decorators.format(
|
|
134
|
+
task.template.id.name
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
task_image_spec = task.template.custom["truefoundry"]["image"]
|
|
138
|
+
if task_image_spec["type"] == "task-python-build":
|
|
139
|
+
is_tfy_wf_present_in_task_python_build = (
|
|
140
|
+
_is_tfy_wf_present_in_task_python_build(
|
|
141
|
+
task_image_spec=task_image_spec, project_root_path=project_root_path
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
if not is_tfy_wf_present_in_task_python_build:
|
|
145
|
+
tasks_without_truefoundry_worflow_package.append(task.template.id.name)
|
|
146
|
+
try:
|
|
147
|
+
auto_gen_models.FlyteTaskCustom.validate(task.template.custom)
|
|
148
|
+
except ValidationError:
|
|
149
|
+
raise ValueError(
|
|
150
|
+
error_message_to_use_truefoundry_decorators.format(
|
|
151
|
+
task.template.id.name
|
|
152
|
+
)
|
|
153
|
+
) from None
|
|
154
|
+
if len(tasks_without_truefoundry_worflow_package) > 0:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
rf"truefoundry\[workflow] package is required dependency to run workflows, add it in pip_packages for tasks: {', '.join(tasks_without_truefoundry_worflow_package)}"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# validate that all inputs have default values for cron workflows
|
|
160
|
+
for launch_plan in launch_plans:
|
|
161
|
+
if (
|
|
162
|
+
execution_config_store.get(launch_plan.spec.workflow_id.name)
|
|
163
|
+
and launch_plan.id.name == TRUEFOUNDRY_LAUNCH_PLAN_NAME
|
|
164
|
+
):
|
|
165
|
+
workflow_inputs = launch_plan.spec.default_inputs.parameters
|
|
166
|
+
for input in workflow_inputs:
|
|
167
|
+
if workflow_inputs[input].required:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
f"All inputs must have default for a cron workflow. Input {input} is required in workflow but default value is not provided"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _get_relative_package_path_from_filepath(
|
|
174
|
+
project_root_path: str, filepath: str
|
|
175
|
+
) -> str:
|
|
176
|
+
"""
|
|
177
|
+
This function returns the relative package path from the project root path for a given file path.
|
|
178
|
+
e.g. if project_root_path = /home/user/project and filepath = /home/user/project/src/module.py
|
|
179
|
+
then the function will return src.module
|
|
180
|
+
"""
|
|
181
|
+
relative_file_path = os.path.relpath(filepath, project_root_path)
|
|
182
|
+
path = Path(relative_file_path)
|
|
183
|
+
package_path = str(path.with_suffix("")).replace(os.path.sep, ".")
|
|
184
|
+
|
|
185
|
+
return package_path
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _generate_manifest_for_workflow(
|
|
189
|
+
workflow: auto_gen_models.Workflow,
|
|
190
|
+
):
|
|
191
|
+
settings = SerializationSettings(
|
|
192
|
+
# We are adding these defaults to avoid validation errors in flyte objects
|
|
193
|
+
image_config=ImageConfig(default_image=FlytekitImage(name="", tag="", fqn="")),
|
|
194
|
+
python_interpreter=sys.executable,
|
|
195
|
+
)
|
|
196
|
+
source_absolute_path = os.path.abspath(workflow.source.project_root_path)
|
|
197
|
+
|
|
198
|
+
workflow_file_absolute_path = os.path.join(
|
|
199
|
+
source_absolute_path, workflow.workflow_file_path
|
|
200
|
+
)
|
|
201
|
+
if not os.path.exists(workflow_file_absolute_path):
|
|
202
|
+
raise FileNotFoundError(
|
|
203
|
+
f"Workflow file not found at {workflow_file_absolute_path}. Workflow file path should be relative to project root path."
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
package_path = _get_relative_package_path_from_filepath(
|
|
207
|
+
project_root_path=source_absolute_path, filepath=workflow_file_absolute_path
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
workflow_entities = serialize_workflow(
|
|
211
|
+
pkgs=[package_path], settings=settings, local_source_root=source_absolute_path
|
|
212
|
+
)
|
|
213
|
+
_validate_workflow_entities(workflow_entities, source_absolute_path)
|
|
214
|
+
|
|
215
|
+
workflow.flyte_entities = []
|
|
216
|
+
for entity in workflow_entities:
|
|
217
|
+
if isinstance(entity, FlyteLaunchPlan):
|
|
218
|
+
workflow_name = entity.spec.workflow_id.name
|
|
219
|
+
|
|
220
|
+
# this is the case when someone has a cron schedule. and this line is for handling default launch plan in this case.
|
|
221
|
+
if (
|
|
222
|
+
execution_config_store.get(workflow_name)
|
|
223
|
+
and workflow_name == entity.id.name
|
|
224
|
+
):
|
|
225
|
+
continue
|
|
226
|
+
# this is the case when someone does not have a cron schedule. and this line is for handling default launch plan in this case.
|
|
227
|
+
elif entity.id.name == workflow_name:
|
|
228
|
+
entity._id._name = TRUEFOUNDRY_LAUNCH_PLAN_NAME
|
|
229
|
+
# this the case when some workflow doesn't have cron schedule, neither it is default launch plan
|
|
230
|
+
elif entity.id.name != TRUEFOUNDRY_LAUNCH_PLAN_NAME:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"Creating launch plans is not allowed. Found launch plan with name {entity.id.name}"
|
|
233
|
+
)
|
|
234
|
+
message_dict = MessageToDict(entity.to_flyte_idl())
|
|
235
|
+
# proto message to dict conversion converts all int to float. so we need this hack
|
|
236
|
+
if (
|
|
237
|
+
message_dict.get("template")
|
|
238
|
+
and message_dict["template"].get("custom")
|
|
239
|
+
and message_dict["template"]["custom"].get("truefoundry")
|
|
240
|
+
):
|
|
241
|
+
parsed_model = auto_gen_models.FlyteTaskCustom.parse_obj(
|
|
242
|
+
message_dict["template"]["custom"]
|
|
243
|
+
)
|
|
244
|
+
message_dict["template"]["custom"]["truefoundry"] = parsed_model.truefoundry
|
|
245
|
+
|
|
246
|
+
workflow.flyte_entities.append(message_dict)
|
|
247
|
+
|
|
248
|
+
# this step is just to verify if pydantic model is still valid after adding flyte_entities
|
|
249
|
+
auto_gen_models.Workflow.validate({**workflow.dict()})
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _validate_workspace_fqn(
|
|
253
|
+
workflow: auto_gen_models.Workflow,
|
|
254
|
+
workspace_fqn: Optional[str] = None,
|
|
255
|
+
):
|
|
256
|
+
if not workspace_fqn:
|
|
257
|
+
raise ValueError(
|
|
258
|
+
"No Workspace FQN was provided. "
|
|
259
|
+
"Pass it explicitly using `--workspace-fqn` argument on CLI or `workspace_fqn` argument of `deploy_workflow`."
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def deploy_workflow(
|
|
264
|
+
workflow: auto_gen_models.Workflow,
|
|
265
|
+
workspace_fqn: str,
|
|
266
|
+
wait: bool = True,
|
|
267
|
+
) -> Deployment:
|
|
268
|
+
_generate_manifest_for_workflow(workflow)
|
|
269
|
+
_validate_workspace_fqn(workflow, workspace_fqn)
|
|
270
|
+
|
|
271
|
+
# we need to rest the execution config store as it is a global variable and we don't want to keep the cron execution config for next workflow
|
|
272
|
+
# this is only needed for notebook environment
|
|
273
|
+
execution_config_store.reset()
|
|
274
|
+
|
|
275
|
+
workspace_id = get_workspace_by_fqn(workspace_fqn).id
|
|
276
|
+
|
|
277
|
+
logger.info(
|
|
278
|
+
f"Deploying workflow {workflow.name} to workspace {workspace_fqn} ({workspace_id})"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
workflow = _handle_code_upload_for_workflow(
|
|
282
|
+
workflow=workflow, workspace_fqn=workspace_fqn
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
client = ServiceFoundryServiceClient()
|
|
286
|
+
deployment = client.deploy_application(
|
|
287
|
+
workspace_id=workspace_id, application=workflow
|
|
288
|
+
)
|
|
289
|
+
logger.info(
|
|
290
|
+
"🚀 Deployment started for application '%s'. Deployment FQN is '%s'.",
|
|
291
|
+
workflow.name,
|
|
292
|
+
deployment.fqn,
|
|
293
|
+
)
|
|
294
|
+
deployment_url = f"{client.base_url.strip('/')}/applications/{deployment.applicationId}?tab=deployments"
|
|
295
|
+
logger.info("You can find the application on the dashboard:- '%s'", deployment_url)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Literal, Union
|
|
2
|
+
|
|
3
|
+
from truefoundry.deploy.auto_gen import models
|
|
4
|
+
from truefoundry.deploy.lib.model.entity import Deployment
|
|
5
|
+
from truefoundry.deploy.v2.lib.deploy import deploy_component
|
|
6
|
+
from truefoundry.pydantic_v1 import BaseModel, Field, conint
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DeployablePatchedModelBase(BaseModel):
|
|
10
|
+
class Config:
|
|
11
|
+
extra = "forbid"
|
|
12
|
+
|
|
13
|
+
def deploy(self, workspace_fqn: str, wait: bool = True) -> Deployment:
|
|
14
|
+
return deploy_component(component=self, workspace_fqn=workspace_fqn, wait=wait)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Service(models.Service, DeployablePatchedModelBase):
|
|
18
|
+
type: Literal["service"] = "service"
|
|
19
|
+
resources: models.Resources = Field(default_factory=models.Resources)
|
|
20
|
+
# This is being patched because cue export marks this as a "number"
|
|
21
|
+
replicas: Union[conint(ge=0, le=100), models.ServiceAutoscaling] = Field(
|
|
22
|
+
1,
|
|
23
|
+
description="+label=Replicas\n+usage=Replicas of service you want to run\n+icon=fa-clone\n+sort=3",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Job(models.Job, DeployablePatchedModelBase):
|
|
28
|
+
type: Literal["job"] = "job"
|
|
29
|
+
resources: models.Resources = Field(default_factory=models.Resources)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Notebook(models.Notebook, DeployablePatchedModelBase):
|
|
33
|
+
type: Literal["notebook"] = "notebook"
|
|
34
|
+
resources: models.Resources = Field(default_factory=models.Resources)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Codeserver(models.Codeserver, DeployablePatchedModelBase):
|
|
38
|
+
type: Literal["codeserver"] = "codeserver"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Helm(models.Helm, DeployablePatchedModelBase):
|
|
42
|
+
type: Literal["helm"] = "helm"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Volume(models.Volume, DeployablePatchedModelBase):
|
|
46
|
+
type: Literal["volume"] = "volume"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ApplicationSet(models.ApplicationSet, DeployablePatchedModelBase):
|
|
50
|
+
type: Literal["application-set"] = "application-set"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AsyncService(models.AsyncService, DeployablePatchedModelBase):
|
|
54
|
+
type: Literal["async-service"] = "async-service"
|
|
55
|
+
replicas: Union[conint(ge=0, le=100), models.AsyncServiceAutoscaling] = 1
|
|
56
|
+
resources: models.Resources = Field(default_factory=models.Resources)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SSHServer(models.SSHServer, DeployablePatchedModelBase):
|
|
60
|
+
type: Literal["ssh-server"] = "ssh-server"
|
|
61
|
+
resources: models.Resources = Field(default_factory=models.Resources)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Application(models.Application, DeployablePatchedModelBase):
|
|
65
|
+
def deploy(self, workspace_fqn: str, wait: bool = True) -> Deployment:
|
|
66
|
+
if isinstance(self.__root__, models.Workflow):
|
|
67
|
+
from truefoundry.deploy.v2.lib.deploy_workflow import deploy_workflow
|
|
68
|
+
|
|
69
|
+
return deploy_workflow(
|
|
70
|
+
workflow=self.__root__,
|
|
71
|
+
workspace_fqn=workspace_fqn,
|
|
72
|
+
wait=wait,
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
return deploy_component(
|
|
76
|
+
component=self.__root__, workspace_fqn=workspace_fqn, wait=wait
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Workflow(models.Workflow, DeployablePatchedModelBase):
|
|
81
|
+
type: Literal["workflow"] = "workflow"
|
|
82
|
+
|
|
83
|
+
def deploy(self, workspace_fqn: str, wait: bool = True) -> Deployment:
|
|
84
|
+
from truefoundry.deploy.v2.lib.deploy_workflow import deploy_workflow
|
|
85
|
+
|
|
86
|
+
return deploy_workflow(workflow=self, workspace_fqn=workspace_fqn, wait=wait)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
from truefoundry.pydantic_v1 import BaseModel, Extra, create_model
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BuildResponse(BaseModel):
|
|
8
|
+
id: str
|
|
9
|
+
name: str
|
|
10
|
+
# TODO: make status an enum
|
|
11
|
+
status: str
|
|
12
|
+
# TODO: should we just make these fields
|
|
13
|
+
# snake-case and add camelCase aliases?
|
|
14
|
+
deploymentId: str
|
|
15
|
+
componentName: str
|
|
16
|
+
createdAt: datetime.datetime
|
|
17
|
+
updatedAt: datetime.datetime
|
|
18
|
+
imageUri: Optional[str]
|
|
19
|
+
failureReason: Optional[str]
|
|
20
|
+
getLogsUrl: str
|
|
21
|
+
tailLogsUrl: str
|
|
22
|
+
logsStartTs: int
|
|
23
|
+
|
|
24
|
+
class Config:
|
|
25
|
+
extra = Extra.allow
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class AppDeploymentStatusResponse(BaseModel):
|
|
29
|
+
state: create_model(
|
|
30
|
+
"State",
|
|
31
|
+
isTerminalState=(bool, ...),
|
|
32
|
+
type=(str, ...),
|
|
33
|
+
transitions=(List[str], ...),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
id: str
|
|
37
|
+
status: str
|
|
38
|
+
message: Optional[str]
|
|
39
|
+
transition: Optional[str]
|
|
40
|
+
|
|
41
|
+
class Config:
|
|
42
|
+
extra = Extra.allow
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DeploymentFqnResponse(BaseModel):
|
|
46
|
+
deploymentId: str
|
|
47
|
+
applicationId: str
|
|
48
|
+
workspaceId: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ApplicationFqnResponse(BaseModel):
|
|
52
|
+
applicationId: str
|
|
53
|
+
workspaceId: str
|