orgo 0.0.39__tar.gz → 0.0.41__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.
- {orgo-0.0.39 → orgo-0.0.41}/PKG-INFO +1 -1
- {orgo-0.0.39 → orgo-0.0.41}/pyproject.toml +1 -1
- orgo-0.0.41/src/orgo/__init__.py +12 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo/api/client.py +9 -9
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo/computer.py +41 -16
- orgo-0.0.41/src/orgo/template.py +347 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo.egg-info/PKG-INFO +1 -1
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo.egg-info/SOURCES.txt +1 -0
- orgo-0.0.39/src/orgo/__init__.py +0 -8
- {orgo-0.0.39 → orgo-0.0.41}/README.md +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/setup.cfg +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo/api/__init__.py +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo/forge.py +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo/project.py +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo/prompt.py +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo/utils/__init__.py +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo/utils/auth.py +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo.egg-info/dependency_links.txt +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo.egg-info/requires.txt +0 -0
- {orgo-0.0.39 → orgo-0.0.41}/src/orgo.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# src/orgo/__init__.py
|
|
2
|
+
"""Orgo SDK: Desktop infrastructure for AI agents"""
|
|
3
|
+
|
|
4
|
+
from .project import Project
|
|
5
|
+
from .computer import Computer
|
|
6
|
+
from .template import Template
|
|
7
|
+
|
|
8
|
+
# Aliases
|
|
9
|
+
Workspace = Project # Workspace is an alias for Project
|
|
10
|
+
Forge = Template # Forge is deprecated, use Template instead
|
|
11
|
+
|
|
12
|
+
__all__ = ["Project", "Workspace", "Computer", "Template", "Forge"]
|
|
@@ -78,12 +78,12 @@ class ApiClient:
|
|
|
78
78
|
return self._request("DELETE", f"projects/{project_id}")
|
|
79
79
|
|
|
80
80
|
# Computer methods
|
|
81
|
-
def create_computer(self, project_id: str, computer_name: str,
|
|
82
|
-
os: str = "linux", ram: int = 2, cpu: int = 2,
|
|
81
|
+
def create_computer(self, project_id: str, computer_name: str,
|
|
82
|
+
os: str = "linux", ram: int = 2, cpu: int = 2,
|
|
83
83
|
gpu: str = "none", image: Optional[str] = None) -> Dict[str, Any]:
|
|
84
|
-
"""Create a new computer within a project"""
|
|
84
|
+
"""Create a new computer within a workspace/project"""
|
|
85
85
|
data = {
|
|
86
|
-
"
|
|
86
|
+
"workspace_id": project_id, # API accepts both workspace_id and project_id
|
|
87
87
|
"name": computer_name,
|
|
88
88
|
"os": os,
|
|
89
89
|
"ram": ram,
|
|
@@ -92,7 +92,7 @@ class ApiClient:
|
|
|
92
92
|
}
|
|
93
93
|
if image:
|
|
94
94
|
data["image"] = image
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
return self._request("POST", "computers", data)
|
|
97
97
|
|
|
98
98
|
def list_computers(self, project_id: str) -> List[Dict[str, Any]]:
|
|
@@ -211,8 +211,8 @@ class ApiClient:
|
|
|
211
211
|
"name": name
|
|
212
212
|
})
|
|
213
213
|
|
|
214
|
-
def update_build(self, build_id: str, status: str,
|
|
215
|
-
error_message: Optional[str] = None,
|
|
214
|
+
def update_build(self, build_id: str, status: str,
|
|
215
|
+
error_message: Optional[str] = None,
|
|
216
216
|
build_log: Optional[str] = None) -> Dict[str, Any]:
|
|
217
217
|
"""Update build status"""
|
|
218
218
|
data = {
|
|
@@ -222,6 +222,6 @@ class ApiClient:
|
|
|
222
222
|
if error_message:
|
|
223
223
|
data["errorMessage"] = error_message
|
|
224
224
|
if build_log:
|
|
225
|
-
data["
|
|
226
|
-
|
|
225
|
+
data["logs"] = build_log
|
|
226
|
+
|
|
227
227
|
return self._request("POST", "builds/update", data)
|
|
@@ -66,24 +66,27 @@ class Computer:
|
|
|
66
66
|
computer.prompt("Open Firefox", provider="anthropic")
|
|
67
67
|
"""
|
|
68
68
|
|
|
69
|
-
def __init__(self,
|
|
69
|
+
def __init__(self,
|
|
70
70
|
project: Optional[Union[str, 'Project']] = None,
|
|
71
|
+
workspace: Optional[Union[str, 'Project']] = None, # Alias for project
|
|
71
72
|
name: Optional[str] = None,
|
|
72
73
|
computer_id: Optional[str] = None,
|
|
73
|
-
api_key: Optional[str] = None,
|
|
74
|
+
api_key: Optional[str] = None,
|
|
74
75
|
base_api_url: Optional[str] = None,
|
|
75
76
|
ram: Optional[Literal[1, 2, 4, 8, 16, 32, 64]] = None,
|
|
76
77
|
memory: Optional[Literal[1, 2, 4, 8, 16, 32, 64]] = None,
|
|
77
78
|
cpu: Optional[Literal[1, 2, 4, 8, 16]] = None,
|
|
78
79
|
os: Optional[Literal["linux", "windows"]] = None,
|
|
79
80
|
gpu: Optional[Literal["none", "a10", "l40s", "a100-40gb", "a100-80gb"]] = None,
|
|
80
|
-
|
|
81
|
+
template: Optional[Union[str, Any]] = None,
|
|
82
|
+
image: Optional[Union[str, Any]] = None, # Deprecated, use template
|
|
81
83
|
verbose: bool = True):
|
|
82
84
|
"""
|
|
83
85
|
Initialize an Orgo virtual computer.
|
|
84
|
-
|
|
86
|
+
|
|
85
87
|
Args:
|
|
86
|
-
project: Project name or
|
|
88
|
+
project: Project/workspace name or instance (creates if doesn't exist)
|
|
89
|
+
workspace: Alias for project (preferred name going forward)
|
|
87
90
|
name: Computer name (auto-generated if not provided)
|
|
88
91
|
computer_id: Connect to existing computer by ID
|
|
89
92
|
api_key: Orgo API key (defaults to ORGO_API_KEY env var)
|
|
@@ -92,9 +95,13 @@ class Computer:
|
|
|
92
95
|
cpu: CPU cores (1, 2, 4, 8, 16)
|
|
93
96
|
os: "linux" or "windows"
|
|
94
97
|
gpu: "none", "a10", "l40s", "a100-40gb", "a100-80gb"
|
|
95
|
-
|
|
98
|
+
template: Template name or Template object to use
|
|
99
|
+
image: Deprecated, use template instead
|
|
96
100
|
verbose: Show console output (default: True)
|
|
97
101
|
"""
|
|
102
|
+
# workspace is an alias for project
|
|
103
|
+
if workspace is not None and project is None:
|
|
104
|
+
project = workspace
|
|
98
105
|
self.api_key = api_key or operating_system.environ.get("ORGO_API_KEY")
|
|
99
106
|
self.base_api_url = base_api_url
|
|
100
107
|
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
@@ -102,17 +109,32 @@ class Computer:
|
|
|
102
109
|
|
|
103
110
|
if ram is None and memory is not None:
|
|
104
111
|
ram = memory
|
|
105
|
-
|
|
112
|
+
|
|
106
113
|
self.os = os or "linux"
|
|
107
114
|
self.ram = ram or 2
|
|
108
115
|
self.cpu = cpu or 2
|
|
109
116
|
self.gpu = gpu or "none"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
|
|
118
|
+
# Handle template parameter (preferred) or image (deprecated)
|
|
119
|
+
self.template = template or image
|
|
120
|
+
|
|
121
|
+
# If template is a Template object, get its image_ref or build it
|
|
122
|
+
if hasattr(self.template, 'image_ref'):
|
|
123
|
+
if self.template.image_ref:
|
|
124
|
+
self.template = self.template.image_ref
|
|
125
|
+
elif hasattr(self.template, 'build') and callable(self.template.build):
|
|
126
|
+
if self.verbose:
|
|
127
|
+
_print_info("Building template...")
|
|
128
|
+
self.template = self.template.build()
|
|
129
|
+
elif hasattr(self.template, 'name'):
|
|
130
|
+
# Template object with name but not built - use name for lookup
|
|
131
|
+
self.template = self.template.name
|
|
132
|
+
|
|
133
|
+
# Legacy: handle old Forge objects that might not have image_ref
|
|
134
|
+
elif hasattr(self.template, 'build') and callable(self.template.build):
|
|
113
135
|
if self.verbose:
|
|
114
|
-
_print_info("Building image
|
|
115
|
-
self.
|
|
136
|
+
_print_info("Building image...")
|
|
137
|
+
self.template = self.template.build()
|
|
116
138
|
|
|
117
139
|
if computer_id:
|
|
118
140
|
self.computer_id = computer_id
|
|
@@ -213,20 +235,23 @@ class Computer:
|
|
|
213
235
|
if self.gpu not in ["none", "a10", "l40s", "a100-40gb", "a100-80gb"]:
|
|
214
236
|
raise ValueError("gpu must be: 'none', 'a10', 'l40s', 'a100-40gb', or 'a100-80gb'")
|
|
215
237
|
|
|
216
|
-
# Resolve image if needed
|
|
217
|
-
image_ref = self.
|
|
238
|
+
# Resolve template to image reference if needed
|
|
239
|
+
image_ref = self.template
|
|
218
240
|
if image_ref and isinstance(image_ref, str) and not image_ref.startswith("registry.fly.io"):
|
|
241
|
+
# Template name provided - look up the latest build
|
|
219
242
|
try:
|
|
220
243
|
project_info = self.api.get_project(project_id)
|
|
221
|
-
org_id = project_info.get("
|
|
244
|
+
org_id = project_info.get("user_id", "orgo")
|
|
222
245
|
response = self.api.get_latest_build(org_id, project_id, image_ref)
|
|
223
246
|
if response and response.get("build"):
|
|
224
247
|
resolved = response.get("build", {}).get("imageRef")
|
|
225
248
|
if resolved:
|
|
226
249
|
image_ref = resolved
|
|
250
|
+
if self.verbose:
|
|
251
|
+
_print_info(f"Using template '{self.template}'")
|
|
227
252
|
except Exception as e:
|
|
228
253
|
if self.verbose:
|
|
229
|
-
logger.warning(f"
|
|
254
|
+
logger.warning(f"Template '{image_ref}' not found, using default image")
|
|
230
255
|
|
|
231
256
|
# Create the computer
|
|
232
257
|
try:
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Orgo Template - Pre-configure computer environments.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from orgo import Template, Computer
|
|
6
|
+
|
|
7
|
+
# Create a template with pre-installed software
|
|
8
|
+
template = Template("python-ml")
|
|
9
|
+
template.run("pip install numpy pandas scikit-learn")
|
|
10
|
+
template.build()
|
|
11
|
+
|
|
12
|
+
# Launch computers from the template
|
|
13
|
+
computer = Computer(template="python-ml")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import subprocess
|
|
18
|
+
import logging
|
|
19
|
+
from typing import Optional, List
|
|
20
|
+
|
|
21
|
+
from .api.client import ApiClient
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _print_info(message: str):
|
|
27
|
+
print(f"-> {message}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _print_success(message: str):
|
|
31
|
+
print(f"[ok] {message}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _print_error(message: str):
|
|
35
|
+
print(f"[error] {message}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Template:
|
|
39
|
+
"""
|
|
40
|
+
Create reusable computer templates with pre-installed software.
|
|
41
|
+
|
|
42
|
+
Templates let you define a base environment once and launch multiple
|
|
43
|
+
computers from it. This is useful for:
|
|
44
|
+
- Pre-installing dependencies (Python packages, apt packages, etc.)
|
|
45
|
+
- Cloning repositories
|
|
46
|
+
- Setting up configuration files
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
# Create and build a template
|
|
50
|
+
template = Template("data-science")
|
|
51
|
+
template.run("apt-get update && apt-get install -y python3-pip")
|
|
52
|
+
template.run("pip install numpy pandas matplotlib jupyter")
|
|
53
|
+
template.build()
|
|
54
|
+
|
|
55
|
+
# Launch a computer from the template
|
|
56
|
+
computer = Computer(template="data-science")
|
|
57
|
+
|
|
58
|
+
# Or pass the template object directly
|
|
59
|
+
computer = Computer(template=template)
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self,
|
|
63
|
+
name: str,
|
|
64
|
+
workspace: Optional[str] = None,
|
|
65
|
+
api_key: Optional[str] = None,
|
|
66
|
+
base_api_url: Optional[str] = None,
|
|
67
|
+
verbose: bool = True):
|
|
68
|
+
"""
|
|
69
|
+
Initialize a Template.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
name: Template name (used to reference it later)
|
|
73
|
+
workspace: Workspace to store the template in (auto-created if needed)
|
|
74
|
+
api_key: Orgo API key (defaults to ORGO_API_KEY env var)
|
|
75
|
+
base_api_url: Custom API URL
|
|
76
|
+
verbose: Show console output (default: True)
|
|
77
|
+
"""
|
|
78
|
+
self.name = name
|
|
79
|
+
self.verbose = verbose
|
|
80
|
+
self.api_key = api_key or os.environ.get("ORGO_API_KEY")
|
|
81
|
+
self.base_api_url = base_api_url
|
|
82
|
+
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
83
|
+
|
|
84
|
+
# Resolve workspace
|
|
85
|
+
workspace_name = workspace or "templates"
|
|
86
|
+
self._resolve_workspace(workspace_name)
|
|
87
|
+
|
|
88
|
+
# Build steps (starts with Orgo base image)
|
|
89
|
+
self._steps: List[str] = ["FROM registry.fly.io/orgo-image-repo:latest"]
|
|
90
|
+
self._image_ref: Optional[str] = None
|
|
91
|
+
|
|
92
|
+
if self.verbose:
|
|
93
|
+
_print_info(f"Template '{name}' initialized")
|
|
94
|
+
|
|
95
|
+
def _resolve_workspace(self, workspace_name: str):
|
|
96
|
+
"""Find or create workspace for templates"""
|
|
97
|
+
try:
|
|
98
|
+
workspace = self.api.get_project_by_name(workspace_name)
|
|
99
|
+
self._workspace_id = workspace["id"]
|
|
100
|
+
self._org_id = workspace.get("user_id", "orgo")
|
|
101
|
+
except Exception:
|
|
102
|
+
if self.verbose:
|
|
103
|
+
_print_info(f"Creating workspace '{workspace_name}'...")
|
|
104
|
+
workspace = self.api.create_project(workspace_name)
|
|
105
|
+
self._workspace_id = workspace["id"]
|
|
106
|
+
self._org_id = workspace.get("user_id", "orgo")
|
|
107
|
+
|
|
108
|
+
# =========================================================================
|
|
109
|
+
# Build Steps (Chainable)
|
|
110
|
+
# =========================================================================
|
|
111
|
+
|
|
112
|
+
def run(self, command: str) -> 'Template':
|
|
113
|
+
"""
|
|
114
|
+
Run a command during build (like apt-get, pip install, etc.)
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
command: Shell command to run
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Self for chaining
|
|
121
|
+
|
|
122
|
+
Example:
|
|
123
|
+
template.run("apt-get update")
|
|
124
|
+
template.run("pip install requests")
|
|
125
|
+
"""
|
|
126
|
+
self._steps.append(f"RUN {command}")
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def copy(self, src: str, dest: str) -> 'Template':
|
|
130
|
+
"""
|
|
131
|
+
Copy local files into the template.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
src: Local file or directory path
|
|
135
|
+
dest: Destination path in the template
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Self for chaining
|
|
139
|
+
|
|
140
|
+
Example:
|
|
141
|
+
template.copy("./config.json", "/app/config.json")
|
|
142
|
+
template.copy("./scripts", "/app/scripts")
|
|
143
|
+
"""
|
|
144
|
+
self._steps.append(f"COPY {src} {dest}")
|
|
145
|
+
return self
|
|
146
|
+
|
|
147
|
+
def env(self, key: str, value: str) -> 'Template':
|
|
148
|
+
"""
|
|
149
|
+
Set an environment variable.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
key: Environment variable name
|
|
153
|
+
value: Environment variable value
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Self for chaining
|
|
157
|
+
|
|
158
|
+
Example:
|
|
159
|
+
template.env("PYTHONPATH", "/app")
|
|
160
|
+
template.env("DEBUG", "true")
|
|
161
|
+
"""
|
|
162
|
+
self._steps.append(f"ENV {key}={value}")
|
|
163
|
+
return self
|
|
164
|
+
|
|
165
|
+
def workdir(self, path: str) -> 'Template':
|
|
166
|
+
"""
|
|
167
|
+
Set the working directory.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
path: Directory path
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Self for chaining
|
|
174
|
+
|
|
175
|
+
Example:
|
|
176
|
+
template.workdir("/app")
|
|
177
|
+
"""
|
|
178
|
+
self._steps.append(f"WORKDIR {path}")
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
def clone(self, repo_url: str, dest: str = "/repo") -> 'Template':
|
|
182
|
+
"""
|
|
183
|
+
Clone a git repository.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
repo_url: Git repository URL
|
|
187
|
+
dest: Destination directory (default: /repo)
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Self for chaining
|
|
191
|
+
|
|
192
|
+
Example:
|
|
193
|
+
template.clone("https://github.com/user/repo.git")
|
|
194
|
+
template.clone("https://github.com/user/repo.git", "/app")
|
|
195
|
+
"""
|
|
196
|
+
self._steps.append(f"RUN git clone {repo_url} {dest}")
|
|
197
|
+
return self
|
|
198
|
+
|
|
199
|
+
# =========================================================================
|
|
200
|
+
# Build
|
|
201
|
+
# =========================================================================
|
|
202
|
+
|
|
203
|
+
def build(self, context: str = ".") -> str:
|
|
204
|
+
"""
|
|
205
|
+
Build and publish the template.
|
|
206
|
+
|
|
207
|
+
This builds a Docker image with your configuration and pushes it
|
|
208
|
+
to Orgo's registry. After building, you can launch computers from
|
|
209
|
+
this template using Computer(template="name").
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
context: Build context directory (default: current directory)
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Image reference string
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
template.run("pip install pandas")
|
|
219
|
+
image_ref = template.build()
|
|
220
|
+
print(f"Built: {image_ref}")
|
|
221
|
+
"""
|
|
222
|
+
if len(self._steps) <= 1:
|
|
223
|
+
raise ValueError("No build steps defined. Use .run(), .copy(), etc. first.")
|
|
224
|
+
|
|
225
|
+
if self.verbose:
|
|
226
|
+
_print_info(f"Building template '{self.name}'...")
|
|
227
|
+
|
|
228
|
+
# Register build with API
|
|
229
|
+
try:
|
|
230
|
+
build_info = self.api.create_build(self._org_id, self._workspace_id, self.name)
|
|
231
|
+
build_data = build_info.get('build', {})
|
|
232
|
+
|
|
233
|
+
build_id = build_data.get('id')
|
|
234
|
+
tag = build_data.get('tag')
|
|
235
|
+
image_ref = build_data.get('imageRef')
|
|
236
|
+
|
|
237
|
+
if not build_id or not image_ref:
|
|
238
|
+
raise ValueError("Failed to register build")
|
|
239
|
+
|
|
240
|
+
if self.verbose:
|
|
241
|
+
_print_info(f"Build ID: {build_id}")
|
|
242
|
+
_print_info(f"Image: {image_ref}")
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
_print_error(f"Failed to register build: {e}")
|
|
246
|
+
raise
|
|
247
|
+
|
|
248
|
+
# Generate Dockerfile
|
|
249
|
+
dockerfile_path = f"Dockerfile.{tag}"
|
|
250
|
+
logs = []
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
with open(dockerfile_path, "w") as f:
|
|
254
|
+
f.write("\n".join(self._steps))
|
|
255
|
+
|
|
256
|
+
# Update status
|
|
257
|
+
self.api.update_build(build_id, "building")
|
|
258
|
+
|
|
259
|
+
# Setup remote builder
|
|
260
|
+
builder_name = "orgo-remote"
|
|
261
|
+
remote_url = "tcp://orgoforge.fly.dev:1234"
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
subprocess.run(
|
|
265
|
+
["docker", "buildx", "inspect", builder_name],
|
|
266
|
+
check=True,
|
|
267
|
+
stdout=subprocess.DEVNULL,
|
|
268
|
+
stderr=subprocess.DEVNULL
|
|
269
|
+
)
|
|
270
|
+
except subprocess.CalledProcessError:
|
|
271
|
+
if self.verbose:
|
|
272
|
+
_print_info("Setting up build environment...")
|
|
273
|
+
subprocess.run([
|
|
274
|
+
"docker", "buildx", "create",
|
|
275
|
+
"--name", builder_name,
|
|
276
|
+
"--driver", "remote",
|
|
277
|
+
remote_url,
|
|
278
|
+
"--use"
|
|
279
|
+
], check=True)
|
|
280
|
+
subprocess.run(["docker", "buildx", "inspect", "--bootstrap"], check=True)
|
|
281
|
+
|
|
282
|
+
# Build and push
|
|
283
|
+
cmd = [
|
|
284
|
+
"docker", "buildx", "build",
|
|
285
|
+
"--builder", builder_name,
|
|
286
|
+
"-t", image_ref,
|
|
287
|
+
"-f", dockerfile_path,
|
|
288
|
+
context,
|
|
289
|
+
"--push"
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
if self.verbose:
|
|
293
|
+
_print_info("Building image...")
|
|
294
|
+
|
|
295
|
+
process = subprocess.Popen(
|
|
296
|
+
cmd,
|
|
297
|
+
stdout=subprocess.PIPE,
|
|
298
|
+
stderr=subprocess.STDOUT,
|
|
299
|
+
text=True,
|
|
300
|
+
bufsize=1
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
while True:
|
|
304
|
+
line = process.stdout.readline()
|
|
305
|
+
if not line and process.poll() is not None:
|
|
306
|
+
break
|
|
307
|
+
if line:
|
|
308
|
+
if self.verbose:
|
|
309
|
+
print(line, end="")
|
|
310
|
+
logs.append(line)
|
|
311
|
+
|
|
312
|
+
if process.returncode != 0:
|
|
313
|
+
raise Exception(f"Build failed with exit code {process.returncode}")
|
|
314
|
+
|
|
315
|
+
# Success
|
|
316
|
+
self.api.update_build(build_id, "completed", build_log="".join(logs))
|
|
317
|
+
self._image_ref = image_ref
|
|
318
|
+
|
|
319
|
+
if self.verbose:
|
|
320
|
+
_print_success(f"Template '{self.name}' built successfully!")
|
|
321
|
+
|
|
322
|
+
return image_ref
|
|
323
|
+
|
|
324
|
+
except Exception as e:
|
|
325
|
+
_print_error(f"Build failed: {e}")
|
|
326
|
+
try:
|
|
327
|
+
self.api.update_build(build_id, "failed", error_message=str(e), build_log="".join(logs))
|
|
328
|
+
except:
|
|
329
|
+
pass
|
|
330
|
+
raise
|
|
331
|
+
|
|
332
|
+
finally:
|
|
333
|
+
if os.path.exists(dockerfile_path):
|
|
334
|
+
os.remove(dockerfile_path)
|
|
335
|
+
|
|
336
|
+
# =========================================================================
|
|
337
|
+
# Properties
|
|
338
|
+
# =========================================================================
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def image_ref(self) -> Optional[str]:
|
|
342
|
+
"""Get the built image reference (None if not built yet)."""
|
|
343
|
+
return self._image_ref
|
|
344
|
+
|
|
345
|
+
def __repr__(self):
|
|
346
|
+
status = "built" if self._image_ref else "not built"
|
|
347
|
+
return f"Template(name='{self.name}', status='{status}')"
|
orgo-0.0.39/src/orgo/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|