@magpiecloud/mags 1.8.2 → 1.8.3
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.
- package/bin/mags.js +61 -1
- package/package.json +1 -1
- package/python/src/mags/client.py +24 -0
package/bin/mags.js
CHANGED
|
@@ -208,6 +208,7 @@ ${colors.bold}Commands:${colors.reset}
|
|
|
208
208
|
list List recent jobs
|
|
209
209
|
url <name|id> [port] Enable URL access for a job
|
|
210
210
|
stop <name|id> Stop a running job
|
|
211
|
+
resize <workspace> --disk <GB> Resize a workspace's disk (restarts VM)
|
|
211
212
|
sync <workspace|id> Sync workspace to S3 (without stopping)
|
|
212
213
|
workspace list List persistent workspaces
|
|
213
214
|
workspace delete <id> Delete a workspace and its S3 data
|
|
@@ -730,6 +731,61 @@ async function stopJob(nameOrId) {
|
|
|
730
731
|
}
|
|
731
732
|
}
|
|
732
733
|
|
|
734
|
+
async function resizeVM(args) {
|
|
735
|
+
let name = null;
|
|
736
|
+
let diskGB = 0;
|
|
737
|
+
|
|
738
|
+
for (let i = 0; i < args.length; i++) {
|
|
739
|
+
if (args[i] === '--disk' && args[i + 1]) {
|
|
740
|
+
diskGB = parseInt(args[++i]) || 0;
|
|
741
|
+
} else if (!name) {
|
|
742
|
+
name = args[i];
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (!name || !diskGB) {
|
|
747
|
+
log('red', 'Error: Workspace name and --disk <GB> required');
|
|
748
|
+
console.log('\nUsage: mags resize <workspace> --disk <GB>\n');
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Find existing job for this workspace
|
|
753
|
+
const existingJob = await findWorkspaceJob(name);
|
|
754
|
+
if (existingJob) {
|
|
755
|
+
// Sync workspace before stopping (preserve files)
|
|
756
|
+
if (existingJob.status === 'running') {
|
|
757
|
+
log('blue', 'Syncing workspace before resize...');
|
|
758
|
+
await request('POST', `/api/v1/mags-jobs/${existingJob.request_id}/sync`);
|
|
759
|
+
}
|
|
760
|
+
log('blue', `Stopping existing VM...`);
|
|
761
|
+
await request('POST', `/api/v1/mags-jobs/${existingJob.request_id}/stop`);
|
|
762
|
+
// Brief wait for the stop to complete
|
|
763
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Create new VM with the same workspace name and new disk size
|
|
767
|
+
log('blue', `Creating new VM with ${diskGB}GB disk...`);
|
|
768
|
+
const payload = {
|
|
769
|
+
script: 'sleep infinity',
|
|
770
|
+
type: 'inline',
|
|
771
|
+
persistent: true,
|
|
772
|
+
name: name,
|
|
773
|
+
workspace_id: name,
|
|
774
|
+
startup_command: 'sleep infinity',
|
|
775
|
+
disk_gb: diskGB,
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
const response = await request('POST', '/api/v1/mags-jobs', payload);
|
|
779
|
+
if (!response.request_id) {
|
|
780
|
+
log('red', 'Failed to create VM:');
|
|
781
|
+
console.log(JSON.stringify(response, null, 2));
|
|
782
|
+
process.exit(1);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
log('green', `Resized '${name}' to ${diskGB}GB disk`);
|
|
786
|
+
log('gray', `Job: ${response.request_id}`);
|
|
787
|
+
}
|
|
788
|
+
|
|
733
789
|
async function syncWorkspace(nameOrId) {
|
|
734
790
|
if (!nameOrId) {
|
|
735
791
|
log('red', 'Error: Workspace name or job ID required');
|
|
@@ -1366,7 +1422,7 @@ async function main() {
|
|
|
1366
1422
|
break;
|
|
1367
1423
|
case '--version':
|
|
1368
1424
|
case '-v':
|
|
1369
|
-
console.log('mags v1.8.
|
|
1425
|
+
console.log('mags v1.8.3');
|
|
1370
1426
|
process.exit(0);
|
|
1371
1427
|
break;
|
|
1372
1428
|
case 'new':
|
|
@@ -1405,6 +1461,10 @@ async function main() {
|
|
|
1405
1461
|
await requireAuth();
|
|
1406
1462
|
await stopJob(args[1]);
|
|
1407
1463
|
break;
|
|
1464
|
+
case 'resize':
|
|
1465
|
+
await requireAuth();
|
|
1466
|
+
await resizeVM(args.slice(1));
|
|
1467
|
+
break;
|
|
1408
1468
|
case 'sync':
|
|
1409
1469
|
await requireAuth();
|
|
1410
1470
|
await syncWorkspace(args[1]);
|
package/package.json
CHANGED
|
@@ -211,6 +211,30 @@ class Mags:
|
|
|
211
211
|
request_id = self._resolve_job_id(name_or_id)
|
|
212
212
|
return self._request("POST", f"/mags-jobs/{request_id}/stop")
|
|
213
213
|
|
|
214
|
+
def resize(
|
|
215
|
+
self,
|
|
216
|
+
workspace: str,
|
|
217
|
+
disk_gb: int,
|
|
218
|
+
*,
|
|
219
|
+
timeout: float = 30.0,
|
|
220
|
+
poll_interval: float = 1.0,
|
|
221
|
+
) -> dict:
|
|
222
|
+
"""Resize a workspace's disk. Stops the existing VM, then creates a new one.
|
|
223
|
+
|
|
224
|
+
Workspace files are preserved in S3.
|
|
225
|
+
Returns ``{"request_id": ..., "status": "running"}``.
|
|
226
|
+
"""
|
|
227
|
+
existing = self.find_job(workspace)
|
|
228
|
+
if existing and existing.get("status") == "running":
|
|
229
|
+
self._request("POST", f"/mags-jobs/{existing['request_id']}/sync")
|
|
230
|
+
self._request("POST", f"/mags-jobs/{existing['request_id']}/stop")
|
|
231
|
+
time.sleep(1)
|
|
232
|
+
elif existing and existing.get("status") == "sleeping":
|
|
233
|
+
self._request("POST", f"/mags-jobs/{existing['request_id']}/stop")
|
|
234
|
+
time.sleep(1)
|
|
235
|
+
|
|
236
|
+
return self.new(workspace, disk_gb=disk_gb, timeout=timeout, poll_interval=poll_interval)
|
|
237
|
+
|
|
214
238
|
def new(
|
|
215
239
|
self,
|
|
216
240
|
name: str,
|