orgo 0.0.38__py3-none-any.whl → 0.0.40__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.
- orgo/__init__.py +11 -8
- orgo/api/__init__.py +5 -5
- orgo/api/client.py +227 -227
- orgo/computer.py +471 -466
- orgo/forge.py +176 -176
- orgo/project.py +86 -86
- orgo/prompt.py +1015 -847
- orgo/utils/__init__.py +5 -5
- orgo/utils/auth.py +16 -16
- {orgo-0.0.38.dist-info → orgo-0.0.40.dist-info}/METADATA +47 -47
- orgo-0.0.40.dist-info/RECORD +13 -0
- {orgo-0.0.38.dist-info → orgo-0.0.40.dist-info}/WHEEL +1 -1
- orgo-0.0.38.dist-info/RECORD +0 -13
- {orgo-0.0.38.dist-info → orgo-0.0.40.dist-info}/top_level.txt +0 -0
orgo/forge.py
CHANGED
|
@@ -1,176 +1,176 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import subprocess
|
|
3
|
-
import logging
|
|
4
|
-
from typing import Optional, List, Union
|
|
5
|
-
import uuid
|
|
6
|
-
|
|
7
|
-
from .api.client import ApiClient
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
class Forge:
|
|
12
|
-
def __init__(self,
|
|
13
|
-
org_id: str,
|
|
14
|
-
project_id: str,
|
|
15
|
-
name: Optional[str] = None,
|
|
16
|
-
api_key: Optional[str] = None,
|
|
17
|
-
base_api_url: Optional[str] = None):
|
|
18
|
-
"""
|
|
19
|
-
Initialize Orgo Forge for building images.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
org_id: Organization ID
|
|
23
|
-
project_id: Project ID
|
|
24
|
-
name: Optional friendly name for the image (not used for registry tag currently, but for reference)
|
|
25
|
-
api_key: Orgo API key
|
|
26
|
-
base_api_url: Custom API URL
|
|
27
|
-
"""
|
|
28
|
-
self.org_id = org_id
|
|
29
|
-
self.project_id = project_id
|
|
30
|
-
self.name = name
|
|
31
|
-
self.api_key = api_key or os.environ.get("ORGO_API_KEY")
|
|
32
|
-
self.base_api_url = base_api_url
|
|
33
|
-
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
34
|
-
|
|
35
|
-
# Enforce base image
|
|
36
|
-
self.steps: List[str] = ["FROM registry.fly.io/orgo-image-repo:latest"]
|
|
37
|
-
|
|
38
|
-
def base(self, image: str) -> 'Forge':
|
|
39
|
-
"""
|
|
40
|
-
DEPRECATED: Base image is now enforced to ensure CUA functionality.
|
|
41
|
-
This method will log a warning and ignore the provided image.
|
|
42
|
-
"""
|
|
43
|
-
logger.warning("Forge.base() is deprecated. The base image is enforced to 'registry.fly.io/orgo-image-repo:latest'. Ignoring provided image.")
|
|
44
|
-
return self
|
|
45
|
-
|
|
46
|
-
def run(self, command: str) -> 'Forge':
|
|
47
|
-
"""Run a command (RUN instruction)"""
|
|
48
|
-
self.steps.append(f"RUN {command}")
|
|
49
|
-
return self
|
|
50
|
-
|
|
51
|
-
def copy(self, src: str, dest: str) -> 'Forge':
|
|
52
|
-
"""Copy files (COPY instruction)"""
|
|
53
|
-
self.steps.append(f"COPY {src} {dest}")
|
|
54
|
-
return self
|
|
55
|
-
|
|
56
|
-
def git_clone(self, url: str, dest: str) -> 'Forge':
|
|
57
|
-
"""Clone a git repository"""
|
|
58
|
-
# Ensure git is installed or use a base image with git
|
|
59
|
-
self.steps.append(f"RUN git clone {url} {dest}")
|
|
60
|
-
return self
|
|
61
|
-
|
|
62
|
-
def env(self, key: str, value: str) -> 'Forge':
|
|
63
|
-
"""Set environment variable (ENV instruction)"""
|
|
64
|
-
self.steps.append(f"ENV {key}={value}")
|
|
65
|
-
return self
|
|
66
|
-
|
|
67
|
-
def workdir(self, path: str) -> 'Forge':
|
|
68
|
-
"""Set working directory (WORKDIR instruction)"""
|
|
69
|
-
self.steps.append(f"WORKDIR {path}")
|
|
70
|
-
return self
|
|
71
|
-
|
|
72
|
-
def build(self, context_path: str = ".", push: bool = True) -> str:
|
|
73
|
-
"""
|
|
74
|
-
Execute the build.
|
|
75
|
-
|
|
76
|
-
1. Registers build with Orgo API to get unique tag.
|
|
77
|
-
2. Generates Dockerfile.
|
|
78
|
-
3. Runs docker build (requires docker CLI installed).
|
|
79
|
-
4. Pushes to registry.
|
|
80
|
-
5. Updates build status.
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
str: The full image reference (e.g. registry.fly.io/app:tag)
|
|
84
|
-
"""
|
|
85
|
-
if not self.steps:
|
|
86
|
-
raise ValueError("No build steps defined. Use .base() to start.")
|
|
87
|
-
|
|
88
|
-
# 1. Register build
|
|
89
|
-
logger.info("Registering build with Orgo API...")
|
|
90
|
-
try:
|
|
91
|
-
build_info = self.api.create_build(self.org_id, self.project_id, self.name)
|
|
92
|
-
build_data = build_info.get('build', {})
|
|
93
|
-
|
|
94
|
-
build_id = build_data.get('id')
|
|
95
|
-
tag = build_data.get('tag')
|
|
96
|
-
image_ref = build_data.get('imageRef')
|
|
97
|
-
# buildkit_url = build_data.get('buildkitUrl') # Not used for local docker build yet
|
|
98
|
-
|
|
99
|
-
if not build_id or not image_ref:
|
|
100
|
-
raise ValueError("Failed to get build ID or image ref from API")
|
|
101
|
-
|
|
102
|
-
logger.info(f"Build registered. ID: {build_id}, Tag: {tag}")
|
|
103
|
-
logger.info(f"Target Image: {image_ref}")
|
|
104
|
-
|
|
105
|
-
except Exception as e:
|
|
106
|
-
logger.error(f"Failed to register build: {e}")
|
|
107
|
-
raise e
|
|
108
|
-
|
|
109
|
-
# 2. Generate Dockerfile
|
|
110
|
-
dockerfile_path = f"Dockerfile.{tag}"
|
|
111
|
-
try:
|
|
112
|
-
with open(dockerfile_path, "w") as f:
|
|
113
|
-
f.write("\n".join(self.steps))
|
|
114
|
-
|
|
115
|
-
# 3. Execute Build
|
|
116
|
-
logger.info("Starting build...")
|
|
117
|
-
self.api.update_build(build_id, "building")
|
|
118
|
-
|
|
119
|
-
# Use remote builder
|
|
120
|
-
builder_name = "orgo-remote"
|
|
121
|
-
remote_url = "tcp://orgoforge.fly.dev:1234"
|
|
122
|
-
|
|
123
|
-
# Check if builder exists
|
|
124
|
-
try:
|
|
125
|
-
subprocess.run(["docker", "buildx", "inspect", builder_name], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
126
|
-
except subprocess.CalledProcessError:
|
|
127
|
-
logger.info(f"Creating remote builder '{builder_name}' pointing to {remote_url}...")
|
|
128
|
-
subprocess.run(["docker", "buildx", "create", "--name", builder_name, "--driver", "remote", remote_url, "--use"], check=True)
|
|
129
|
-
subprocess.run(["docker", "buildx", "inspect", "--bootstrap"], check=True)
|
|
130
|
-
|
|
131
|
-
cmd = ["docker", "buildx", "build", "--builder", builder_name, "-t", image_ref, "-f", dockerfile_path, context_path]
|
|
132
|
-
if push:
|
|
133
|
-
cmd.append("--push")
|
|
134
|
-
|
|
135
|
-
# Run build
|
|
136
|
-
process = subprocess.Popen(
|
|
137
|
-
cmd,
|
|
138
|
-
stdout=subprocess.PIPE,
|
|
139
|
-
stderr=subprocess.STDOUT,
|
|
140
|
-
text=True,
|
|
141
|
-
bufsize=1,
|
|
142
|
-
universal_newlines=True
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
logs = []
|
|
146
|
-
while True:
|
|
147
|
-
line = process.stdout.readline()
|
|
148
|
-
if not line and process.poll() is not None:
|
|
149
|
-
break
|
|
150
|
-
if line:
|
|
151
|
-
print(line, end="") # Stream to console
|
|
152
|
-
logs.append(line)
|
|
153
|
-
|
|
154
|
-
if process.returncode != 0:
|
|
155
|
-
raise Exception(f"Docker build failed with exit code {process.returncode}")
|
|
156
|
-
|
|
157
|
-
# 5. Report Success
|
|
158
|
-
full_log = "".join(logs)
|
|
159
|
-
self.api.update_build(build_id, "completed", build_log=full_log)
|
|
160
|
-
logger.info("Build completed successfully!")
|
|
161
|
-
|
|
162
|
-
return image_ref
|
|
163
|
-
|
|
164
|
-
except Exception as e:
|
|
165
|
-
logger.error(f"Build failed: {e}")
|
|
166
|
-
full_log = "".join(logs) if 'logs' in locals() else str(e)
|
|
167
|
-
try:
|
|
168
|
-
self.api.update_build(build_id, "failed", error_message=str(e), build_log=full_log)
|
|
169
|
-
except:
|
|
170
|
-
pass # Ignore error reporting failure
|
|
171
|
-
raise e
|
|
172
|
-
|
|
173
|
-
finally:
|
|
174
|
-
if os.path.exists(dockerfile_path):
|
|
175
|
-
os.remove(dockerfile_path)
|
|
176
|
-
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional, List, Union
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from .api.client import ApiClient
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
class Forge:
|
|
12
|
+
def __init__(self,
|
|
13
|
+
org_id: str,
|
|
14
|
+
project_id: str,
|
|
15
|
+
name: Optional[str] = None,
|
|
16
|
+
api_key: Optional[str] = None,
|
|
17
|
+
base_api_url: Optional[str] = None):
|
|
18
|
+
"""
|
|
19
|
+
Initialize Orgo Forge for building images.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
org_id: Organization ID
|
|
23
|
+
project_id: Project ID
|
|
24
|
+
name: Optional friendly name for the image (not used for registry tag currently, but for reference)
|
|
25
|
+
api_key: Orgo API key
|
|
26
|
+
base_api_url: Custom API URL
|
|
27
|
+
"""
|
|
28
|
+
self.org_id = org_id
|
|
29
|
+
self.project_id = project_id
|
|
30
|
+
self.name = name
|
|
31
|
+
self.api_key = api_key or os.environ.get("ORGO_API_KEY")
|
|
32
|
+
self.base_api_url = base_api_url
|
|
33
|
+
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
34
|
+
|
|
35
|
+
# Enforce base image
|
|
36
|
+
self.steps: List[str] = ["FROM registry.fly.io/orgo-image-repo:latest"]
|
|
37
|
+
|
|
38
|
+
def base(self, image: str) -> 'Forge':
|
|
39
|
+
"""
|
|
40
|
+
DEPRECATED: Base image is now enforced to ensure CUA functionality.
|
|
41
|
+
This method will log a warning and ignore the provided image.
|
|
42
|
+
"""
|
|
43
|
+
logger.warning("Forge.base() is deprecated. The base image is enforced to 'registry.fly.io/orgo-image-repo:latest'. Ignoring provided image.")
|
|
44
|
+
return self
|
|
45
|
+
|
|
46
|
+
def run(self, command: str) -> 'Forge':
|
|
47
|
+
"""Run a command (RUN instruction)"""
|
|
48
|
+
self.steps.append(f"RUN {command}")
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def copy(self, src: str, dest: str) -> 'Forge':
|
|
52
|
+
"""Copy files (COPY instruction)"""
|
|
53
|
+
self.steps.append(f"COPY {src} {dest}")
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def git_clone(self, url: str, dest: str) -> 'Forge':
|
|
57
|
+
"""Clone a git repository"""
|
|
58
|
+
# Ensure git is installed or use a base image with git
|
|
59
|
+
self.steps.append(f"RUN git clone {url} {dest}")
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def env(self, key: str, value: str) -> 'Forge':
|
|
63
|
+
"""Set environment variable (ENV instruction)"""
|
|
64
|
+
self.steps.append(f"ENV {key}={value}")
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
def workdir(self, path: str) -> 'Forge':
|
|
68
|
+
"""Set working directory (WORKDIR instruction)"""
|
|
69
|
+
self.steps.append(f"WORKDIR {path}")
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def build(self, context_path: str = ".", push: bool = True) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Execute the build.
|
|
75
|
+
|
|
76
|
+
1. Registers build with Orgo API to get unique tag.
|
|
77
|
+
2. Generates Dockerfile.
|
|
78
|
+
3. Runs docker build (requires docker CLI installed).
|
|
79
|
+
4. Pushes to registry.
|
|
80
|
+
5. Updates build status.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
str: The full image reference (e.g. registry.fly.io/app:tag)
|
|
84
|
+
"""
|
|
85
|
+
if not self.steps:
|
|
86
|
+
raise ValueError("No build steps defined. Use .base() to start.")
|
|
87
|
+
|
|
88
|
+
# 1. Register build
|
|
89
|
+
logger.info("Registering build with Orgo API...")
|
|
90
|
+
try:
|
|
91
|
+
build_info = self.api.create_build(self.org_id, self.project_id, self.name)
|
|
92
|
+
build_data = build_info.get('build', {})
|
|
93
|
+
|
|
94
|
+
build_id = build_data.get('id')
|
|
95
|
+
tag = build_data.get('tag')
|
|
96
|
+
image_ref = build_data.get('imageRef')
|
|
97
|
+
# buildkit_url = build_data.get('buildkitUrl') # Not used for local docker build yet
|
|
98
|
+
|
|
99
|
+
if not build_id or not image_ref:
|
|
100
|
+
raise ValueError("Failed to get build ID or image ref from API")
|
|
101
|
+
|
|
102
|
+
logger.info(f"Build registered. ID: {build_id}, Tag: {tag}")
|
|
103
|
+
logger.info(f"Target Image: {image_ref}")
|
|
104
|
+
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"Failed to register build: {e}")
|
|
107
|
+
raise e
|
|
108
|
+
|
|
109
|
+
# 2. Generate Dockerfile
|
|
110
|
+
dockerfile_path = f"Dockerfile.{tag}"
|
|
111
|
+
try:
|
|
112
|
+
with open(dockerfile_path, "w") as f:
|
|
113
|
+
f.write("\n".join(self.steps))
|
|
114
|
+
|
|
115
|
+
# 3. Execute Build
|
|
116
|
+
logger.info("Starting build...")
|
|
117
|
+
self.api.update_build(build_id, "building")
|
|
118
|
+
|
|
119
|
+
# Use remote builder
|
|
120
|
+
builder_name = "orgo-remote"
|
|
121
|
+
remote_url = "tcp://orgoforge.fly.dev:1234"
|
|
122
|
+
|
|
123
|
+
# Check if builder exists
|
|
124
|
+
try:
|
|
125
|
+
subprocess.run(["docker", "buildx", "inspect", builder_name], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
126
|
+
except subprocess.CalledProcessError:
|
|
127
|
+
logger.info(f"Creating remote builder '{builder_name}' pointing to {remote_url}...")
|
|
128
|
+
subprocess.run(["docker", "buildx", "create", "--name", builder_name, "--driver", "remote", remote_url, "--use"], check=True)
|
|
129
|
+
subprocess.run(["docker", "buildx", "inspect", "--bootstrap"], check=True)
|
|
130
|
+
|
|
131
|
+
cmd = ["docker", "buildx", "build", "--builder", builder_name, "-t", image_ref, "-f", dockerfile_path, context_path]
|
|
132
|
+
if push:
|
|
133
|
+
cmd.append("--push")
|
|
134
|
+
|
|
135
|
+
# Run build
|
|
136
|
+
process = subprocess.Popen(
|
|
137
|
+
cmd,
|
|
138
|
+
stdout=subprocess.PIPE,
|
|
139
|
+
stderr=subprocess.STDOUT,
|
|
140
|
+
text=True,
|
|
141
|
+
bufsize=1,
|
|
142
|
+
universal_newlines=True
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
logs = []
|
|
146
|
+
while True:
|
|
147
|
+
line = process.stdout.readline()
|
|
148
|
+
if not line and process.poll() is not None:
|
|
149
|
+
break
|
|
150
|
+
if line:
|
|
151
|
+
print(line, end="") # Stream to console
|
|
152
|
+
logs.append(line)
|
|
153
|
+
|
|
154
|
+
if process.returncode != 0:
|
|
155
|
+
raise Exception(f"Docker build failed with exit code {process.returncode}")
|
|
156
|
+
|
|
157
|
+
# 5. Report Success
|
|
158
|
+
full_log = "".join(logs)
|
|
159
|
+
self.api.update_build(build_id, "completed", build_log=full_log)
|
|
160
|
+
logger.info("Build completed successfully!")
|
|
161
|
+
|
|
162
|
+
return image_ref
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error(f"Build failed: {e}")
|
|
166
|
+
full_log = "".join(logs) if 'logs' in locals() else str(e)
|
|
167
|
+
try:
|
|
168
|
+
self.api.update_build(build_id, "failed", error_message=str(e), build_log=full_log)
|
|
169
|
+
except:
|
|
170
|
+
pass # Ignore error reporting failure
|
|
171
|
+
raise e
|
|
172
|
+
|
|
173
|
+
finally:
|
|
174
|
+
if os.path.exists(dockerfile_path):
|
|
175
|
+
os.remove(dockerfile_path)
|
|
176
|
+
|
orgo/project.py
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
"""Project class for managing Orgo projects"""
|
|
2
|
-
import os as operating_system # Renamed to avoid any potential conflicts
|
|
3
|
-
import uuid
|
|
4
|
-
from typing import Dict, List, Any, Optional
|
|
5
|
-
|
|
6
|
-
from .api.client import ApiClient
|
|
7
|
-
|
|
8
|
-
class Project:
|
|
9
|
-
def __init__(self,
|
|
10
|
-
name: Optional[str] = None,
|
|
11
|
-
api_key: Optional[str] = None,
|
|
12
|
-
base_api_url: Optional[str] = None):
|
|
13
|
-
"""
|
|
14
|
-
Initialize an Orgo project.
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
name: Project name. If exists, connects to it. If not, creates it.
|
|
18
|
-
api_key: Orgo API key (defaults to ORGO_API_KEY env var)
|
|
19
|
-
base_api_url: Custom API URL (optional)
|
|
20
|
-
"""
|
|
21
|
-
self.api_key = api_key or operating_system.environ.get("ORGO_API_KEY")
|
|
22
|
-
self.base_api_url = base_api_url
|
|
23
|
-
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
24
|
-
|
|
25
|
-
if name:
|
|
26
|
-
self.name = name
|
|
27
|
-
else:
|
|
28
|
-
# Generate a unique name if not provided
|
|
29
|
-
self.name = f"project-{uuid.uuid4().hex[:8]}"
|
|
30
|
-
|
|
31
|
-
# Try to get existing project or create new one
|
|
32
|
-
self._initialize_project()
|
|
33
|
-
|
|
34
|
-
def _initialize_project(self):
|
|
35
|
-
"""Get existing project or create new one"""
|
|
36
|
-
try:
|
|
37
|
-
# Try to get existing project
|
|
38
|
-
project = self.api.get_project_by_name(self.name)
|
|
39
|
-
self.id = project.get("id")
|
|
40
|
-
self._info = project
|
|
41
|
-
except Exception:
|
|
42
|
-
# Project doesn't exist, create it
|
|
43
|
-
project = self.api.create_project(self.name)
|
|
44
|
-
self.id = project.get("id")
|
|
45
|
-
self._info = project
|
|
46
|
-
|
|
47
|
-
def status(self) -> Dict[str, Any]:
|
|
48
|
-
"""Get project status"""
|
|
49
|
-
return self.api.get_project(self.id)
|
|
50
|
-
|
|
51
|
-
def start(self) -> Dict[str, Any]:
|
|
52
|
-
"""Start all computers in the project"""
|
|
53
|
-
return self.api.start_project(self.id)
|
|
54
|
-
|
|
55
|
-
def stop(self) -> Dict[str, Any]:
|
|
56
|
-
"""Stop all computers in the project"""
|
|
57
|
-
return self.api.stop_project(self.id)
|
|
58
|
-
|
|
59
|
-
def restart(self) -> Dict[str, Any]:
|
|
60
|
-
"""Restart all computers in the project"""
|
|
61
|
-
return self.api.restart_project(self.id)
|
|
62
|
-
|
|
63
|
-
def destroy(self) -> Dict[str, Any]:
|
|
64
|
-
"""Delete the project and all its computers"""
|
|
65
|
-
return self.api.delete_project(self.id)
|
|
66
|
-
|
|
67
|
-
def list_computers(self) -> List[Dict[str, Any]]:
|
|
68
|
-
"""List all computers in this project"""
|
|
69
|
-
return self.api.list_computers(self.id)
|
|
70
|
-
|
|
71
|
-
def get_computer(self, computer_name: str = None) -> Optional[Dict[str, Any]]:
|
|
72
|
-
"""Get a specific computer in this project by name, or the first one if no name specified"""
|
|
73
|
-
computers = self.list_computers()
|
|
74
|
-
if not computers:
|
|
75
|
-
return None
|
|
76
|
-
|
|
77
|
-
if computer_name:
|
|
78
|
-
for computer in computers:
|
|
79
|
-
if computer.get("name") == computer_name:
|
|
80
|
-
return computer
|
|
81
|
-
return None
|
|
82
|
-
else:
|
|
83
|
-
# Return first computer if no name specified
|
|
84
|
-
return computers[0]
|
|
85
|
-
|
|
86
|
-
def __repr__(self):
|
|
1
|
+
"""Project class for managing Orgo projects"""
|
|
2
|
+
import os as operating_system # Renamed to avoid any potential conflicts
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Dict, List, Any, Optional
|
|
5
|
+
|
|
6
|
+
from .api.client import ApiClient
|
|
7
|
+
|
|
8
|
+
class Project:
|
|
9
|
+
def __init__(self,
|
|
10
|
+
name: Optional[str] = None,
|
|
11
|
+
api_key: Optional[str] = None,
|
|
12
|
+
base_api_url: Optional[str] = None):
|
|
13
|
+
"""
|
|
14
|
+
Initialize an Orgo project.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
name: Project name. If exists, connects to it. If not, creates it.
|
|
18
|
+
api_key: Orgo API key (defaults to ORGO_API_KEY env var)
|
|
19
|
+
base_api_url: Custom API URL (optional)
|
|
20
|
+
"""
|
|
21
|
+
self.api_key = api_key or operating_system.environ.get("ORGO_API_KEY")
|
|
22
|
+
self.base_api_url = base_api_url
|
|
23
|
+
self.api = ApiClient(self.api_key, self.base_api_url)
|
|
24
|
+
|
|
25
|
+
if name:
|
|
26
|
+
self.name = name
|
|
27
|
+
else:
|
|
28
|
+
# Generate a unique name if not provided
|
|
29
|
+
self.name = f"project-{uuid.uuid4().hex[:8]}"
|
|
30
|
+
|
|
31
|
+
# Try to get existing project or create new one
|
|
32
|
+
self._initialize_project()
|
|
33
|
+
|
|
34
|
+
def _initialize_project(self):
|
|
35
|
+
"""Get existing project or create new one"""
|
|
36
|
+
try:
|
|
37
|
+
# Try to get existing project
|
|
38
|
+
project = self.api.get_project_by_name(self.name)
|
|
39
|
+
self.id = project.get("id")
|
|
40
|
+
self._info = project
|
|
41
|
+
except Exception:
|
|
42
|
+
# Project doesn't exist, create it
|
|
43
|
+
project = self.api.create_project(self.name)
|
|
44
|
+
self.id = project.get("id")
|
|
45
|
+
self._info = project
|
|
46
|
+
|
|
47
|
+
def status(self) -> Dict[str, Any]:
|
|
48
|
+
"""Get project status"""
|
|
49
|
+
return self.api.get_project(self.id)
|
|
50
|
+
|
|
51
|
+
def start(self) -> Dict[str, Any]:
|
|
52
|
+
"""Start all computers in the project"""
|
|
53
|
+
return self.api.start_project(self.id)
|
|
54
|
+
|
|
55
|
+
def stop(self) -> Dict[str, Any]:
|
|
56
|
+
"""Stop all computers in the project"""
|
|
57
|
+
return self.api.stop_project(self.id)
|
|
58
|
+
|
|
59
|
+
def restart(self) -> Dict[str, Any]:
|
|
60
|
+
"""Restart all computers in the project"""
|
|
61
|
+
return self.api.restart_project(self.id)
|
|
62
|
+
|
|
63
|
+
def destroy(self) -> Dict[str, Any]:
|
|
64
|
+
"""Delete the project and all its computers"""
|
|
65
|
+
return self.api.delete_project(self.id)
|
|
66
|
+
|
|
67
|
+
def list_computers(self) -> List[Dict[str, Any]]:
|
|
68
|
+
"""List all computers in this project"""
|
|
69
|
+
return self.api.list_computers(self.id)
|
|
70
|
+
|
|
71
|
+
def get_computer(self, computer_name: str = None) -> Optional[Dict[str, Any]]:
|
|
72
|
+
"""Get a specific computer in this project by name, or the first one if no name specified"""
|
|
73
|
+
computers = self.list_computers()
|
|
74
|
+
if not computers:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
if computer_name:
|
|
78
|
+
for computer in computers:
|
|
79
|
+
if computer.get("name") == computer_name:
|
|
80
|
+
return computer
|
|
81
|
+
return None
|
|
82
|
+
else:
|
|
83
|
+
# Return first computer if no name specified
|
|
84
|
+
return computers[0]
|
|
85
|
+
|
|
86
|
+
def __repr__(self):
|
|
87
87
|
return f"Project(name='{self.name}', id='{self.id}')"
|