comfygit-deploy 0.3.4__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.
- comfygit_deploy/__init__.py +3 -0
- comfygit_deploy/cli.py +374 -0
- comfygit_deploy/commands/__init__.py +5 -0
- comfygit_deploy/commands/custom.py +218 -0
- comfygit_deploy/commands/dev.py +356 -0
- comfygit_deploy/commands/instances.py +506 -0
- comfygit_deploy/commands/runpod.py +203 -0
- comfygit_deploy/commands/worker.py +266 -0
- comfygit_deploy/config.py +122 -0
- comfygit_deploy/providers/__init__.py +11 -0
- comfygit_deploy/providers/custom.py +238 -0
- comfygit_deploy/providers/runpod.py +549 -0
- comfygit_deploy/startup/__init__.py +1 -0
- comfygit_deploy/startup/scripts.py +210 -0
- comfygit_deploy/worker/__init__.py +12 -0
- comfygit_deploy/worker/mdns.py +154 -0
- comfygit_deploy/worker/native_manager.py +438 -0
- comfygit_deploy/worker/server.py +511 -0
- comfygit_deploy/worker/state.py +268 -0
- comfygit_deploy-0.3.4.dist-info/METADATA +38 -0
- comfygit_deploy-0.3.4.dist-info/RECORD +23 -0
- comfygit_deploy-0.3.4.dist-info/WHEEL +4 -0
- comfygit_deploy-0.3.4.dist-info/entry_points.txt +2 -0
comfygit_deploy/cli.py
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"""CLI entry point for comfygit-deploy.
|
|
2
|
+
|
|
3
|
+
Provides command-line interface for deploying ComfyUI environments
|
|
4
|
+
to cloud providers (RunPod) and self-hosted workers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from . import __version__
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
14
|
+
"""Create the argument parser with all subcommands."""
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="cg-deploy",
|
|
17
|
+
description="ComfyGit Deploy - Remote deployment and worker management",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--version",
|
|
21
|
+
action="version",
|
|
22
|
+
version=f"%(prog)s {__version__}",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
26
|
+
|
|
27
|
+
# =========================================================================
|
|
28
|
+
# RunPod commands
|
|
29
|
+
# =========================================================================
|
|
30
|
+
runpod_parser = subparsers.add_parser("runpod", help="RunPod operations")
|
|
31
|
+
runpod_subparsers = runpod_parser.add_subparsers(
|
|
32
|
+
dest="runpod_command", help="RunPod subcommands"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# runpod config
|
|
36
|
+
config_parser = runpod_subparsers.add_parser("config", help="Configure RunPod API key")
|
|
37
|
+
config_parser.add_argument("--api-key", help="Set RunPod API key")
|
|
38
|
+
config_parser.add_argument("--show", action="store_true", help="Show current config")
|
|
39
|
+
config_parser.add_argument("--clear", action="store_true", help="Clear stored API key")
|
|
40
|
+
|
|
41
|
+
# runpod gpus
|
|
42
|
+
gpus_parser = runpod_subparsers.add_parser("gpus", help="List available GPUs with pricing")
|
|
43
|
+
gpus_parser.add_argument("--region", help="Filter by region/data center")
|
|
44
|
+
|
|
45
|
+
# runpod volumes
|
|
46
|
+
runpod_subparsers.add_parser("volumes", help="List network volumes")
|
|
47
|
+
|
|
48
|
+
# runpod regions
|
|
49
|
+
runpod_subparsers.add_parser("regions", help="List data centers")
|
|
50
|
+
|
|
51
|
+
# runpod deploy
|
|
52
|
+
deploy_parser = runpod_subparsers.add_parser("deploy", help="Deploy environment to RunPod")
|
|
53
|
+
deploy_parser.add_argument("import_source", help="Git URL or local path to import")
|
|
54
|
+
deploy_parser.add_argument("--gpu", required=True, help="GPU type (e.g., 'RTX 4090')")
|
|
55
|
+
deploy_parser.add_argument("--volume", help="Network volume ID to attach")
|
|
56
|
+
deploy_parser.add_argument("--name", help="Deployment name")
|
|
57
|
+
deploy_parser.add_argument("--branch", "-b", help="Git branch/tag to use")
|
|
58
|
+
deploy_parser.add_argument(
|
|
59
|
+
"--cloud-type",
|
|
60
|
+
choices=["SECURE", "COMMUNITY"],
|
|
61
|
+
default="SECURE",
|
|
62
|
+
help="Cloud type (default: SECURE)",
|
|
63
|
+
)
|
|
64
|
+
deploy_parser.add_argument(
|
|
65
|
+
"--pricing-type",
|
|
66
|
+
choices=["ON_DEMAND", "SPOT"],
|
|
67
|
+
default="ON_DEMAND",
|
|
68
|
+
help="Pricing type (default: ON_DEMAND)",
|
|
69
|
+
)
|
|
70
|
+
deploy_parser.add_argument("--spot-bid", type=float, help="Spot bid price (for SPOT pricing)")
|
|
71
|
+
|
|
72
|
+
# =========================================================================
|
|
73
|
+
# Instance commands
|
|
74
|
+
# =========================================================================
|
|
75
|
+
instances_parser = subparsers.add_parser("instances", help="List all instances")
|
|
76
|
+
instances_parser.add_argument(
|
|
77
|
+
"--provider",
|
|
78
|
+
choices=["runpod", "custom"],
|
|
79
|
+
help="Filter by provider",
|
|
80
|
+
)
|
|
81
|
+
instances_parser.add_argument(
|
|
82
|
+
"--status",
|
|
83
|
+
choices=["running", "stopped"],
|
|
84
|
+
help="Filter by status",
|
|
85
|
+
)
|
|
86
|
+
instances_parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
87
|
+
|
|
88
|
+
# start
|
|
89
|
+
start_parser = subparsers.add_parser("start", help="Start a stopped instance")
|
|
90
|
+
start_parser.add_argument("instance_id", help="Instance ID to start")
|
|
91
|
+
|
|
92
|
+
# stop
|
|
93
|
+
stop_parser = subparsers.add_parser("stop", help="Stop a running instance")
|
|
94
|
+
stop_parser.add_argument("instance_id", help="Instance ID to stop")
|
|
95
|
+
|
|
96
|
+
# terminate
|
|
97
|
+
terminate_parser = subparsers.add_parser("terminate", help="Terminate an instance")
|
|
98
|
+
terminate_parser.add_argument("instance_id", help="Instance ID to terminate")
|
|
99
|
+
terminate_parser.add_argument("--force", action="store_true", help="Force termination")
|
|
100
|
+
terminate_parser.add_argument("--keep-env", action="store_true", help="Keep environment directory")
|
|
101
|
+
|
|
102
|
+
# logs
|
|
103
|
+
logs_parser = subparsers.add_parser("logs", help="View instance logs")
|
|
104
|
+
logs_parser.add_argument("instance_id", help="Instance ID")
|
|
105
|
+
logs_parser.add_argument("--follow", "-f", action="store_true", help="Follow log output")
|
|
106
|
+
logs_parser.add_argument("--lines", "-n", type=int, default=100, help="Number of lines")
|
|
107
|
+
|
|
108
|
+
# open
|
|
109
|
+
open_parser = subparsers.add_parser("open", help="Open ComfyUI URL in browser")
|
|
110
|
+
open_parser.add_argument("instance_id", help="Instance ID")
|
|
111
|
+
|
|
112
|
+
# wait
|
|
113
|
+
wait_parser = subparsers.add_parser("wait", help="Wait for instance to be ready")
|
|
114
|
+
wait_parser.add_argument("instance_id", help="Instance ID")
|
|
115
|
+
wait_parser.add_argument("--timeout", type=int, default=300, help="Timeout in seconds")
|
|
116
|
+
|
|
117
|
+
# =========================================================================
|
|
118
|
+
# Custom worker commands (Phase 2)
|
|
119
|
+
# =========================================================================
|
|
120
|
+
custom_parser = subparsers.add_parser("custom", help="Custom worker operations")
|
|
121
|
+
custom_subparsers = custom_parser.add_subparsers(
|
|
122
|
+
dest="custom_command", help="Custom worker subcommands"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# custom scan
|
|
126
|
+
scan_parser = custom_subparsers.add_parser("scan", help="Scan for workers via mDNS")
|
|
127
|
+
scan_parser.add_argument("--timeout", type=int, default=5, help="Scan timeout")
|
|
128
|
+
|
|
129
|
+
# custom add
|
|
130
|
+
add_parser = custom_subparsers.add_parser("add", help="Add a custom worker")
|
|
131
|
+
add_parser.add_argument("name", help="Worker name")
|
|
132
|
+
add_parser.add_argument("--host", help="Worker host/IP")
|
|
133
|
+
add_parser.add_argument("--port", type=int, default=9090, help="Worker port")
|
|
134
|
+
add_parser.add_argument("--api-key", help="Worker API key")
|
|
135
|
+
add_parser.add_argument(
|
|
136
|
+
"--discovered", action="store_true", help="Add from last scan results"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
# custom remove
|
|
140
|
+
remove_parser = custom_subparsers.add_parser("remove", help="Remove a custom worker")
|
|
141
|
+
remove_parser.add_argument("name", help="Worker name")
|
|
142
|
+
|
|
143
|
+
# custom list
|
|
144
|
+
custom_subparsers.add_parser("list", help="List registered workers")
|
|
145
|
+
|
|
146
|
+
# custom test
|
|
147
|
+
test_parser = custom_subparsers.add_parser("test", help="Test worker connection")
|
|
148
|
+
test_parser.add_argument("name", help="Worker name")
|
|
149
|
+
|
|
150
|
+
# custom deploy
|
|
151
|
+
custom_deploy_parser = custom_subparsers.add_parser(
|
|
152
|
+
"deploy", help="Deploy to custom worker"
|
|
153
|
+
)
|
|
154
|
+
custom_deploy_parser.add_argument("worker_name", help="Worker to deploy to")
|
|
155
|
+
custom_deploy_parser.add_argument("import_source", help="Git URL or local path")
|
|
156
|
+
custom_deploy_parser.add_argument("--branch", "-b", help="Git branch/tag")
|
|
157
|
+
custom_deploy_parser.add_argument(
|
|
158
|
+
"--mode",
|
|
159
|
+
choices=["docker", "native"],
|
|
160
|
+
default="docker",
|
|
161
|
+
help="Deployment mode",
|
|
162
|
+
)
|
|
163
|
+
custom_deploy_parser.add_argument("--name", help="Environment name")
|
|
164
|
+
|
|
165
|
+
# =========================================================================
|
|
166
|
+
# Worker commands (runs on GPU machine, Phase 2)
|
|
167
|
+
# =========================================================================
|
|
168
|
+
worker_parser = subparsers.add_parser("worker", help="Worker server management")
|
|
169
|
+
worker_subparsers = worker_parser.add_subparsers(
|
|
170
|
+
dest="worker_command", help="Worker subcommands"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# worker setup
|
|
174
|
+
setup_parser = worker_subparsers.add_parser("setup", help="One-time worker setup")
|
|
175
|
+
setup_parser.add_argument("--api-key", help="Set worker API key")
|
|
176
|
+
setup_parser.add_argument("--workspace", help="Workspace path")
|
|
177
|
+
|
|
178
|
+
# worker up
|
|
179
|
+
up_parser = worker_subparsers.add_parser("up", help="Start worker server")
|
|
180
|
+
up_parser.add_argument("--port", type=int, default=9090, help="Server port")
|
|
181
|
+
up_parser.add_argument("--host", default="0.0.0.0", help="Bind host")
|
|
182
|
+
up_parser.add_argument(
|
|
183
|
+
"--mode",
|
|
184
|
+
choices=["docker", "native"],
|
|
185
|
+
default="docker",
|
|
186
|
+
help="Instance mode",
|
|
187
|
+
)
|
|
188
|
+
up_parser.add_argument("--broadcast", action="store_true", help="Enable mDNS broadcast")
|
|
189
|
+
up_parser.add_argument("--port-range", default="8200:8210", help="Instance port range")
|
|
190
|
+
up_parser.add_argument("--dev", action="store_true", help="Use saved dev config (from 'dev setup')")
|
|
191
|
+
up_parser.add_argument("--dev-core", metavar="PATH", help="Use local comfygit-core (editable)")
|
|
192
|
+
up_parser.add_argument("--dev-manager", metavar="PATH", help="Use local comfygit-manager")
|
|
193
|
+
|
|
194
|
+
# worker down
|
|
195
|
+
worker_subparsers.add_parser("down", help="Stop worker server")
|
|
196
|
+
|
|
197
|
+
# worker status
|
|
198
|
+
worker_subparsers.add_parser("status", help="Show worker status")
|
|
199
|
+
|
|
200
|
+
# worker regenerate-key
|
|
201
|
+
worker_subparsers.add_parser("regenerate-key", help="Regenerate API key")
|
|
202
|
+
|
|
203
|
+
# =========================================================================
|
|
204
|
+
# Dev commands (development mode setup)
|
|
205
|
+
# =========================================================================
|
|
206
|
+
dev_parser = subparsers.add_parser("dev", help="Development mode setup")
|
|
207
|
+
dev_subparsers = dev_parser.add_subparsers(dest="dev_command", help="Dev subcommands")
|
|
208
|
+
|
|
209
|
+
# dev setup
|
|
210
|
+
dev_setup_parser = dev_subparsers.add_parser(
|
|
211
|
+
"setup", help="Configure local dev paths for core/manager"
|
|
212
|
+
)
|
|
213
|
+
dev_setup_parser.add_argument("--core", metavar="PATH", help="Path to comfygit-core package")
|
|
214
|
+
dev_setup_parser.add_argument("--manager", metavar="PATH", help="Path to comfygit-manager")
|
|
215
|
+
dev_setup_parser.add_argument("--show", action="store_true", help="Show current dev config")
|
|
216
|
+
dev_setup_parser.add_argument("--clear", action="store_true", help="Clear dev config")
|
|
217
|
+
|
|
218
|
+
# dev patch
|
|
219
|
+
dev_patch_parser = dev_subparsers.add_parser(
|
|
220
|
+
"patch", help="Patch existing environments with dev config"
|
|
221
|
+
)
|
|
222
|
+
dev_patch_parser.add_argument("--env", help="Specific environment to patch (default: all)")
|
|
223
|
+
|
|
224
|
+
# dev add-node
|
|
225
|
+
dev_add_node_parser = dev_subparsers.add_parser(
|
|
226
|
+
"add-node", help="Add a dev node to be symlinked into environments"
|
|
227
|
+
)
|
|
228
|
+
dev_add_node_parser.add_argument("name", help="Node directory name (e.g., ComfyUI-Async-API)")
|
|
229
|
+
dev_add_node_parser.add_argument("path", help="Path to the node source directory")
|
|
230
|
+
|
|
231
|
+
# dev remove-node
|
|
232
|
+
dev_remove_node_parser = dev_subparsers.add_parser(
|
|
233
|
+
"remove-node", help="Remove a dev node from config"
|
|
234
|
+
)
|
|
235
|
+
dev_remove_node_parser.add_argument("name", help="Node name to remove")
|
|
236
|
+
|
|
237
|
+
# dev list-nodes
|
|
238
|
+
dev_subparsers.add_parser("list-nodes", help="List configured dev nodes")
|
|
239
|
+
|
|
240
|
+
return parser
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def main(args: list[str] | None = None) -> int:
|
|
244
|
+
"""Main entry point for cg-deploy CLI.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
args: Command-line arguments (uses sys.argv if None)
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Exit code (0 for success, non-zero for errors)
|
|
251
|
+
"""
|
|
252
|
+
parser = create_parser()
|
|
253
|
+
parsed = parser.parse_args(args)
|
|
254
|
+
|
|
255
|
+
if not parsed.command:
|
|
256
|
+
parser.print_help()
|
|
257
|
+
return 0
|
|
258
|
+
|
|
259
|
+
# Import command handlers
|
|
260
|
+
from .commands import instances as instance_commands
|
|
261
|
+
from .commands import runpod as runpod_commands
|
|
262
|
+
|
|
263
|
+
# Dispatch to command handlers
|
|
264
|
+
try:
|
|
265
|
+
if parsed.command == "runpod":
|
|
266
|
+
if not parsed.runpod_command:
|
|
267
|
+
parser.parse_args(["runpod", "--help"])
|
|
268
|
+
return 0
|
|
269
|
+
handler_map = {
|
|
270
|
+
"config": runpod_commands.handle_config,
|
|
271
|
+
"gpus": runpod_commands.handle_gpus,
|
|
272
|
+
"volumes": runpod_commands.handle_volumes,
|
|
273
|
+
"regions": runpod_commands.handle_regions,
|
|
274
|
+
"deploy": runpod_commands.handle_deploy,
|
|
275
|
+
}
|
|
276
|
+
handler = handler_map.get(parsed.runpod_command)
|
|
277
|
+
if handler:
|
|
278
|
+
return handler(parsed)
|
|
279
|
+
print(f"Unknown runpod command: {parsed.runpod_command}")
|
|
280
|
+
return 1
|
|
281
|
+
|
|
282
|
+
elif parsed.command == "instances":
|
|
283
|
+
return instance_commands.handle_instances(parsed)
|
|
284
|
+
|
|
285
|
+
elif parsed.command == "start":
|
|
286
|
+
return instance_commands.handle_start(parsed)
|
|
287
|
+
|
|
288
|
+
elif parsed.command == "stop":
|
|
289
|
+
return instance_commands.handle_stop(parsed)
|
|
290
|
+
|
|
291
|
+
elif parsed.command == "terminate":
|
|
292
|
+
return instance_commands.handle_terminate(parsed)
|
|
293
|
+
|
|
294
|
+
elif parsed.command == "open":
|
|
295
|
+
return instance_commands.handle_open(parsed)
|
|
296
|
+
|
|
297
|
+
elif parsed.command == "wait":
|
|
298
|
+
return instance_commands.handle_wait(parsed)
|
|
299
|
+
|
|
300
|
+
elif parsed.command == "logs":
|
|
301
|
+
return instance_commands.handle_logs(parsed)
|
|
302
|
+
|
|
303
|
+
elif parsed.command == "worker":
|
|
304
|
+
from .commands import worker as worker_commands
|
|
305
|
+
|
|
306
|
+
if not parsed.worker_command:
|
|
307
|
+
parser.parse_args(["worker", "--help"])
|
|
308
|
+
return 0
|
|
309
|
+
handler_map = {
|
|
310
|
+
"setup": worker_commands.handle_setup,
|
|
311
|
+
"up": worker_commands.handle_up,
|
|
312
|
+
"down": worker_commands.handle_down,
|
|
313
|
+
"status": worker_commands.handle_status,
|
|
314
|
+
"regenerate-key": worker_commands.handle_regenerate_key,
|
|
315
|
+
}
|
|
316
|
+
handler = handler_map.get(parsed.worker_command)
|
|
317
|
+
if handler:
|
|
318
|
+
return handler(parsed)
|
|
319
|
+
print(f"Unknown worker command: {parsed.worker_command}")
|
|
320
|
+
return 1
|
|
321
|
+
|
|
322
|
+
elif parsed.command == "custom":
|
|
323
|
+
from .commands import custom as custom_commands
|
|
324
|
+
|
|
325
|
+
if not parsed.custom_command:
|
|
326
|
+
parser.parse_args(["custom", "--help"])
|
|
327
|
+
return 0
|
|
328
|
+
handler_map = {
|
|
329
|
+
"scan": custom_commands.handle_scan,
|
|
330
|
+
"add": custom_commands.handle_add,
|
|
331
|
+
"remove": custom_commands.handle_remove,
|
|
332
|
+
"list": custom_commands.handle_list,
|
|
333
|
+
"test": custom_commands.handle_test,
|
|
334
|
+
"deploy": custom_commands.handle_deploy,
|
|
335
|
+
}
|
|
336
|
+
handler = handler_map.get(parsed.custom_command)
|
|
337
|
+
if handler:
|
|
338
|
+
return handler(parsed)
|
|
339
|
+
print(f"Unknown custom command: {parsed.custom_command}")
|
|
340
|
+
return 1
|
|
341
|
+
|
|
342
|
+
elif parsed.command == "dev":
|
|
343
|
+
from .commands import dev as dev_commands
|
|
344
|
+
|
|
345
|
+
if not parsed.dev_command:
|
|
346
|
+
parser.parse_args(["dev", "--help"])
|
|
347
|
+
return 0
|
|
348
|
+
handler_map = {
|
|
349
|
+
"setup": dev_commands.handle_setup,
|
|
350
|
+
"patch": dev_commands.handle_patch,
|
|
351
|
+
"add-node": dev_commands.handle_add_node,
|
|
352
|
+
"remove-node": dev_commands.handle_remove_node,
|
|
353
|
+
"list-nodes": dev_commands.handle_list_nodes,
|
|
354
|
+
}
|
|
355
|
+
handler = handler_map.get(parsed.dev_command)
|
|
356
|
+
if handler:
|
|
357
|
+
return handler(parsed)
|
|
358
|
+
print(f"Unknown dev command: {parsed.dev_command}")
|
|
359
|
+
return 1
|
|
360
|
+
|
|
361
|
+
else:
|
|
362
|
+
print(f"Unknown command: {parsed.command}")
|
|
363
|
+
return 1
|
|
364
|
+
|
|
365
|
+
except KeyboardInterrupt:
|
|
366
|
+
print("\nCancelled.")
|
|
367
|
+
return 130
|
|
368
|
+
except Exception as e:
|
|
369
|
+
print(f"Error: {e}")
|
|
370
|
+
return 1
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
if __name__ == "__main__":
|
|
374
|
+
sys.exit(main())
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Custom worker CLI command handlers.
|
|
2
|
+
|
|
3
|
+
Commands for managing connections to self-hosted workers.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import asyncio
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import asdict
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from ..config import DeployConfig
|
|
13
|
+
from ..providers.custom import CustomWorkerClient
|
|
14
|
+
from ..worker.mdns import MDNSScanner
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_worker_connection(host: str, port: int, api_key: str) -> dict[str, Any]:
|
|
18
|
+
"""Test connection to a worker (sync wrapper)."""
|
|
19
|
+
async def _test():
|
|
20
|
+
client = CustomWorkerClient(host=host, port=port, api_key=api_key)
|
|
21
|
+
return await client.test_connection()
|
|
22
|
+
|
|
23
|
+
return asyncio.run(_test())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def deploy_to_worker(
|
|
27
|
+
host: str,
|
|
28
|
+
port: int,
|
|
29
|
+
api_key: str,
|
|
30
|
+
import_source: str,
|
|
31
|
+
name: str | None = None,
|
|
32
|
+
branch: str | None = None,
|
|
33
|
+
mode: str | None = None,
|
|
34
|
+
) -> dict[str, Any]:
|
|
35
|
+
"""Deploy to a worker (sync wrapper)."""
|
|
36
|
+
async def _deploy():
|
|
37
|
+
client = CustomWorkerClient(host=host, port=port, api_key=api_key)
|
|
38
|
+
return await client.create_instance(
|
|
39
|
+
import_source=import_source,
|
|
40
|
+
name=name,
|
|
41
|
+
branch=branch,
|
|
42
|
+
mode=mode,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return asyncio.run(_deploy())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def handle_add(args: argparse.Namespace) -> int:
|
|
49
|
+
"""Handle 'custom add' command."""
|
|
50
|
+
config = DeployConfig()
|
|
51
|
+
host = args.host
|
|
52
|
+
port = args.port
|
|
53
|
+
mode = "docker" # Default mode
|
|
54
|
+
|
|
55
|
+
if getattr(args, "discovered", False):
|
|
56
|
+
# Load from last scan results
|
|
57
|
+
discovered_file = config.path.parent / "discovered_workers.json"
|
|
58
|
+
if not discovered_file.exists():
|
|
59
|
+
print("Error: No scan results found. Run 'cg-deploy custom scan' first.")
|
|
60
|
+
return 1
|
|
61
|
+
|
|
62
|
+
discovered = json.loads(discovered_file.read_text())
|
|
63
|
+
worker = next((w for w in discovered if w["name"] == args.name), None)
|
|
64
|
+
if not worker:
|
|
65
|
+
print(f"Error: Worker '{args.name}' not found in scan results.")
|
|
66
|
+
return 1
|
|
67
|
+
|
|
68
|
+
host = worker["host"]
|
|
69
|
+
port = worker["port"]
|
|
70
|
+
mode = worker.get("mode", "docker")
|
|
71
|
+
|
|
72
|
+
if not host:
|
|
73
|
+
print("Error: --host is required (or use --discovered)")
|
|
74
|
+
return 1
|
|
75
|
+
|
|
76
|
+
if not args.api_key:
|
|
77
|
+
print("Error: --api-key is required")
|
|
78
|
+
return 1
|
|
79
|
+
|
|
80
|
+
config.add_worker(args.name, host, port, args.api_key, mode=mode)
|
|
81
|
+
config.save()
|
|
82
|
+
|
|
83
|
+
print(f"Added worker '{args.name}'")
|
|
84
|
+
print(f" Host: {host}:{port}")
|
|
85
|
+
print(f" Mode: {mode}")
|
|
86
|
+
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def handle_remove(args: argparse.Namespace) -> int:
|
|
91
|
+
"""Handle 'custom remove' command."""
|
|
92
|
+
config = DeployConfig()
|
|
93
|
+
|
|
94
|
+
if not config.remove_worker(args.name):
|
|
95
|
+
print(f"Error: Worker '{args.name}' not found")
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
config.save()
|
|
99
|
+
print(f"Removed worker '{args.name}'")
|
|
100
|
+
return 0
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def handle_list(args: argparse.Namespace) -> int:
|
|
104
|
+
"""Handle 'custom list' command."""
|
|
105
|
+
config = DeployConfig()
|
|
106
|
+
workers = config.workers
|
|
107
|
+
|
|
108
|
+
if not workers:
|
|
109
|
+
print("No workers registered.")
|
|
110
|
+
print("Use 'cg-deploy custom add' to register a worker.")
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
print("Registered workers:")
|
|
114
|
+
for name, info in workers.items():
|
|
115
|
+
host = info.get("host", "?")
|
|
116
|
+
port = info.get("port", "?")
|
|
117
|
+
mode = info.get("mode", "docker")
|
|
118
|
+
print(f" {name}: {host}:{port} ({mode})")
|
|
119
|
+
|
|
120
|
+
return 0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def handle_test(args: argparse.Namespace) -> int:
|
|
124
|
+
"""Handle 'custom test' command."""
|
|
125
|
+
config = DeployConfig()
|
|
126
|
+
worker = config.get_worker(args.name)
|
|
127
|
+
|
|
128
|
+
if not worker:
|
|
129
|
+
print(f"Error: Worker '{args.name}' not found")
|
|
130
|
+
return 1
|
|
131
|
+
|
|
132
|
+
print(f"Testing connection to '{args.name}'...")
|
|
133
|
+
|
|
134
|
+
result = test_worker_connection(
|
|
135
|
+
host=worker["host"],
|
|
136
|
+
port=worker["port"],
|
|
137
|
+
api_key=worker["api_key"],
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if result.get("success"):
|
|
141
|
+
print(" Status: OK")
|
|
142
|
+
print(f" Version: {result.get('worker_version', 'unknown')}")
|
|
143
|
+
return 0
|
|
144
|
+
else:
|
|
145
|
+
print(" Status: FAILED")
|
|
146
|
+
print(f" Error: {result.get('error', 'Unknown error')}")
|
|
147
|
+
return 1
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def handle_deploy(args: argparse.Namespace) -> int:
|
|
151
|
+
"""Handle 'custom deploy' command."""
|
|
152
|
+
config = DeployConfig()
|
|
153
|
+
worker = config.get_worker(args.worker_name)
|
|
154
|
+
|
|
155
|
+
if not worker:
|
|
156
|
+
print(f"Error: Worker '{args.worker_name}' not found")
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
print(f"Deploying to '{args.worker_name}'...")
|
|
160
|
+
print(f" Source: {args.import_source}")
|
|
161
|
+
if args.branch:
|
|
162
|
+
print(f" Branch: {args.branch}")
|
|
163
|
+
print(f" Mode: {args.mode}")
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
result = deploy_to_worker(
|
|
167
|
+
host=worker["host"],
|
|
168
|
+
port=worker["port"],
|
|
169
|
+
api_key=worker["api_key"],
|
|
170
|
+
import_source=args.import_source,
|
|
171
|
+
name=args.name,
|
|
172
|
+
branch=args.branch,
|
|
173
|
+
mode=args.mode,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
print()
|
|
177
|
+
print("Deployment started!")
|
|
178
|
+
print(f" Instance ID: {result.get('id')}")
|
|
179
|
+
print(f" Name: {result.get('name')}")
|
|
180
|
+
print(f" Port: {result.get('assigned_port')}")
|
|
181
|
+
print(f" Status: {result.get('status')}")
|
|
182
|
+
|
|
183
|
+
return 0
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
print(f"Error: {e}")
|
|
187
|
+
return 1
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def handle_scan(args: argparse.Namespace) -> int:
|
|
191
|
+
"""Handle 'custom scan' command (mDNS discovery)."""
|
|
192
|
+
timeout = getattr(args, "timeout", 5)
|
|
193
|
+
print(f"Scanning for workers (timeout: {timeout}s)...")
|
|
194
|
+
|
|
195
|
+
scanner = MDNSScanner(timeout=float(timeout))
|
|
196
|
+
workers = scanner.scan()
|
|
197
|
+
|
|
198
|
+
if not workers:
|
|
199
|
+
print("\nNo workers found on the network.")
|
|
200
|
+
print("Make sure workers are running with mDNS enabled.")
|
|
201
|
+
return 0
|
|
202
|
+
|
|
203
|
+
print(f"\nFound {len(workers)} worker(s):\n")
|
|
204
|
+
for w in workers:
|
|
205
|
+
print(f" {w.name}")
|
|
206
|
+
print(f" Host: {w.host}:{w.port}")
|
|
207
|
+
print(f" Mode: {w.mode}")
|
|
208
|
+
print(f" Version: {w.version}")
|
|
209
|
+
print()
|
|
210
|
+
|
|
211
|
+
# Save results for --discovered flag
|
|
212
|
+
config = DeployConfig()
|
|
213
|
+
discovered_file = config.path.parent / "discovered_workers.json"
|
|
214
|
+
discovered_file.parent.mkdir(parents=True, exist_ok=True)
|
|
215
|
+
discovered_file.write_text(json.dumps([asdict(w) for w in workers], indent=2))
|
|
216
|
+
|
|
217
|
+
print("Use 'cg-deploy custom add <name> --discovered --api-key <key>' to register.")
|
|
218
|
+
return 0
|