hackerrun 0.1.11 → 0.1.12
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/dist/index.js +97 -34
- package/package.json +1 -1
- package/src/commands/services.ts +84 -0
- package/src/index.ts +3 -1
- package/src/lib/cluster.ts +16 -42
- package/src/lib/platform-client.ts +27 -0
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
|
-
import { Command as
|
|
10
|
+
import { Command as Command12 } from "commander";
|
|
11
11
|
|
|
12
12
|
// src/commands/login.ts
|
|
13
13
|
import { Command } from "commander";
|
|
@@ -1153,16 +1153,14 @@ var ClusterManager = class {
|
|
|
1153
1153
|
*/
|
|
1154
1154
|
async initializeCluster(options) {
|
|
1155
1155
|
const { appName, location, vmSize, storageSize, bootImage } = options;
|
|
1156
|
-
|
|
1157
|
-
let spinner = ora2(`Creating VM '${vmName}' in ${location}...`).start();
|
|
1156
|
+
let spinner = ora2(`Creating VM for '${appName}' in ${location}...`).start();
|
|
1158
1157
|
try {
|
|
1159
1158
|
spinner.text = "Fetching platform SSH keys...";
|
|
1160
1159
|
const platformKeys = await this.getPlatformKeys();
|
|
1161
1160
|
const gateway = await this.platformClient.getGateway(location);
|
|
1162
1161
|
const privateSubnetId = gateway?.subnetId;
|
|
1163
|
-
spinner.text =
|
|
1164
|
-
const vm = await this.platformClient.
|
|
1165
|
-
name: vmName,
|
|
1162
|
+
spinner.text = "Creating VM...";
|
|
1163
|
+
const vm = await this.platformClient.createAppVM(appName, {
|
|
1166
1164
|
location,
|
|
1167
1165
|
size: vmSize,
|
|
1168
1166
|
storage_size: storageSize,
|
|
@@ -1171,9 +1169,11 @@ var ClusterManager = class {
|
|
|
1171
1169
|
public_key: platformKeys.platformPublicKey,
|
|
1172
1170
|
enable_ip4: !privateSubnetId,
|
|
1173
1171
|
// IPv6-only if we have a subnet for NAT64
|
|
1174
|
-
private_subnet_id: privateSubnetId
|
|
1172
|
+
private_subnet_id: privateSubnetId,
|
|
1173
|
+
isPrimary: true
|
|
1175
1174
|
});
|
|
1176
|
-
|
|
1175
|
+
const vmName = vm.name;
|
|
1176
|
+
spinner.text = `Waiting for VM '${vmName}' to be ready...`;
|
|
1177
1177
|
spinner.stop();
|
|
1178
1178
|
console.log(chalk4.cyan("\nWaiting for VM to get an IPv6 address..."));
|
|
1179
1179
|
const vmWithIp = await this.waitForVM(location, vmName, 600, false);
|
|
@@ -1226,23 +1226,23 @@ var ClusterManager = class {
|
|
|
1226
1226
|
}
|
|
1227
1227
|
const gateway = await this.platformClient.getGateway(cluster.location);
|
|
1228
1228
|
const privateSubnetId = gateway?.subnetId;
|
|
1229
|
-
|
|
1230
|
-
let spinner = ora2(`Adding node '${vmName}' to cluster...`).start();
|
|
1229
|
+
let spinner = ora2("Adding node to cluster...").start();
|
|
1231
1230
|
try {
|
|
1232
1231
|
spinner.text = "Fetching platform SSH keys...";
|
|
1233
1232
|
const platformKeys = await this.getPlatformKeys();
|
|
1234
|
-
spinner.text =
|
|
1235
|
-
const vm = await this.platformClient.
|
|
1236
|
-
name: vmName,
|
|
1233
|
+
spinner.text = "Creating VM...";
|
|
1234
|
+
const vm = await this.platformClient.createAppVM(appName, {
|
|
1237
1235
|
location: cluster.location,
|
|
1238
1236
|
size: vmSize,
|
|
1239
1237
|
boot_image: bootImage,
|
|
1240
1238
|
unix_user: "root",
|
|
1241
1239
|
public_key: platformKeys.platformPublicKey,
|
|
1242
1240
|
enable_ip4: !privateSubnetId,
|
|
1243
|
-
private_subnet_id: privateSubnetId
|
|
1241
|
+
private_subnet_id: privateSubnetId,
|
|
1242
|
+
isPrimary: false
|
|
1244
1243
|
});
|
|
1245
|
-
|
|
1244
|
+
const vmName = vm.name;
|
|
1245
|
+
spinner.text = `Waiting for VM '${vmName}' to be ready...`;
|
|
1246
1246
|
spinner.stop();
|
|
1247
1247
|
console.log(chalk4.cyan("\nWaiting for VM to get an IPv6 address..."));
|
|
1248
1248
|
const vmWithIp = await this.waitForVM(cluster.location, vmName, 600, false);
|
|
@@ -1521,24 +1521,6 @@ The VM may still be provisioning. You can:
|
|
|
1521
1521
|
}
|
|
1522
1522
|
console.log(chalk4.yellow(" Warning: Timeout waiting for unregistry, continuing anyway..."));
|
|
1523
1523
|
}
|
|
1524
|
-
/**
|
|
1525
|
-
* Generate a sequential node name: appName-1, appName-2, etc.
|
|
1526
|
-
* Finds the next available number by checking existing node names
|
|
1527
|
-
*/
|
|
1528
|
-
generateNodeName(appName, existingNodes) {
|
|
1529
|
-
const existingNumbers = existingNodes.map((n) => {
|
|
1530
|
-
const match = n.name.match(new RegExp(`^${appName}-(\\d+)$`));
|
|
1531
|
-
return match ? parseInt(match[1], 10) : 0;
|
|
1532
|
-
}).filter((n) => n > 0);
|
|
1533
|
-
const maxNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) : 0;
|
|
1534
|
-
return `${appName}-${maxNumber + 1}`;
|
|
1535
|
-
}
|
|
1536
|
-
/**
|
|
1537
|
-
* Generate a random ID (used for legacy naming or fallback)
|
|
1538
|
-
*/
|
|
1539
|
-
generateId() {
|
|
1540
|
-
return Math.random().toString(36).substring(2, 9);
|
|
1541
|
-
}
|
|
1542
1524
|
/**
|
|
1543
1525
|
* Sleep for specified milliseconds
|
|
1544
1526
|
*/
|
|
@@ -1956,6 +1938,19 @@ var PlatformClient = class {
|
|
|
1956
1938
|
const { vm } = await this.request("POST", "/api/vms", params, { operation: `Create VM '${params.name}'` });
|
|
1957
1939
|
return vm;
|
|
1958
1940
|
}
|
|
1941
|
+
/**
|
|
1942
|
+
* Create a new VM for an app with auto-generated name
|
|
1943
|
+
* Name format: u{userId}-{appName}-{nodeNum}
|
|
1944
|
+
*/
|
|
1945
|
+
async createAppVM(appName, params) {
|
|
1946
|
+
const { vm } = await this.request(
|
|
1947
|
+
"POST",
|
|
1948
|
+
`/api/apps/${appName}/vms`,
|
|
1949
|
+
params,
|
|
1950
|
+
{ operation: `Create VM for app '${appName}'` }
|
|
1951
|
+
);
|
|
1952
|
+
return vm;
|
|
1953
|
+
}
|
|
1959
1954
|
/**
|
|
1960
1955
|
* Get a specific VM
|
|
1961
1956
|
*/
|
|
@@ -3916,8 +3911,75 @@ Error: ${error.message}
|
|
|
3916
3911
|
return cmd;
|
|
3917
3912
|
}
|
|
3918
3913
|
|
|
3914
|
+
// src/commands/services.ts
|
|
3915
|
+
import { Command as Command11 } from "commander";
|
|
3916
|
+
import chalk17 from "chalk";
|
|
3917
|
+
import ora11 from "ora";
|
|
3918
|
+
function createServicesCommand() {
|
|
3919
|
+
const cmd = new Command11("services");
|
|
3920
|
+
cmd.description("List services and their status for an app").argument("[app]", "App name (uses hackerrun.yaml if not specified)").action(async (appArg) => {
|
|
3921
|
+
const platformToken = getPlatformToken();
|
|
3922
|
+
const platformClient = new PlatformClient(platformToken);
|
|
3923
|
+
const uncloudRunner = new UncloudRunner(platformClient);
|
|
3924
|
+
try {
|
|
3925
|
+
const appName = appArg || getAppName();
|
|
3926
|
+
const app = await platformClient.getApp(appName);
|
|
3927
|
+
if (!app) {
|
|
3928
|
+
console.log(chalk17.red(`
|
|
3929
|
+
App '${appName}' not found
|
|
3930
|
+
`));
|
|
3931
|
+
process.exit(1);
|
|
3932
|
+
}
|
|
3933
|
+
const spinner = ora11("Fetching services...").start();
|
|
3934
|
+
try {
|
|
3935
|
+
const output = await uncloudRunner.serviceList(appName);
|
|
3936
|
+
spinner.stop();
|
|
3937
|
+
console.log(chalk17.cyan(`
|
|
3938
|
+
Services in '${appName}':
|
|
3939
|
+
`));
|
|
3940
|
+
const lines = output.trim().split("\n");
|
|
3941
|
+
if (lines.length === 0 || lines.length === 1 && !lines[0].trim()) {
|
|
3942
|
+
console.log(chalk17.yellow(" No services found.\n"));
|
|
3943
|
+
console.log(`Run ${chalk17.bold("hackerrun deploy")} to deploy your app.
|
|
3944
|
+
`);
|
|
3945
|
+
return;
|
|
3946
|
+
}
|
|
3947
|
+
for (const line of lines) {
|
|
3948
|
+
const trimmed = line.trim();
|
|
3949
|
+
if (trimmed.startsWith("Connecting") || trimmed.startsWith("Connected")) {
|
|
3950
|
+
continue;
|
|
3951
|
+
}
|
|
3952
|
+
if (trimmed) {
|
|
3953
|
+
if (trimmed.startsWith("NAME")) {
|
|
3954
|
+
console.log(chalk17.bold(` ${trimmed}`));
|
|
3955
|
+
} else {
|
|
3956
|
+
console.log(` ${trimmed}`);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
console.log();
|
|
3961
|
+
console.log(chalk17.dim(`Machines: ${app.nodes.length}`));
|
|
3962
|
+
console.log(chalk17.dim(`Scale a service: hackerrun scale ${appName} <service> <replicas>`));
|
|
3963
|
+
console.log();
|
|
3964
|
+
} catch (error) {
|
|
3965
|
+
spinner.fail("Failed to fetch services");
|
|
3966
|
+
console.log(chalk17.yellow("\nMake sure the app is deployed and running.\n"));
|
|
3967
|
+
process.exit(1);
|
|
3968
|
+
}
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
console.error(chalk17.red(`
|
|
3971
|
+
Error: ${error.message}
|
|
3972
|
+
`));
|
|
3973
|
+
process.exit(1);
|
|
3974
|
+
} finally {
|
|
3975
|
+
uncloudRunner.cleanup();
|
|
3976
|
+
}
|
|
3977
|
+
});
|
|
3978
|
+
return cmd;
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3919
3981
|
// src/index.ts
|
|
3920
|
-
var program = new
|
|
3982
|
+
var program = new Command12();
|
|
3921
3983
|
program.name("hackerrun").description("Deploy apps with full control over your infrastructure").version("0.1.0");
|
|
3922
3984
|
program.addCommand(createLoginCommand());
|
|
3923
3985
|
program.addCommand(createConfigCommand());
|
|
@@ -3929,6 +3991,7 @@ program.addCommand(createEnvCommand());
|
|
|
3929
3991
|
program.addCommand(createBuildsCommand());
|
|
3930
3992
|
program.addCommand(createVPNCommands());
|
|
3931
3993
|
program.addCommand(createScaleCommand());
|
|
3994
|
+
program.addCommand(createServicesCommand());
|
|
3932
3995
|
var { appsCmd, nodesCmd, sshCmd, destroyCmd, linkCmd, renameCmd, domainCmd } = createAppCommands();
|
|
3933
3996
|
program.addCommand(appsCmd);
|
|
3934
3997
|
program.addCommand(nodesCmd);
|
package/package.json
CHANGED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getPlatformToken } from '../lib/platform-auth.js';
|
|
5
|
+
import { PlatformClient } from '../lib/platform-client.js';
|
|
6
|
+
import { UncloudRunner } from '../lib/uncloud-runner.js';
|
|
7
|
+
import { getAppName } from '../lib/app-config.js';
|
|
8
|
+
|
|
9
|
+
export function createServicesCommand() {
|
|
10
|
+
const cmd = new Command('services');
|
|
11
|
+
|
|
12
|
+
cmd
|
|
13
|
+
.description('List services and their status for an app')
|
|
14
|
+
.argument('[app]', 'App name (uses hackerrun.yaml if not specified)')
|
|
15
|
+
.action(async (appArg: string | undefined) => {
|
|
16
|
+
const platformToken = getPlatformToken();
|
|
17
|
+
const platformClient = new PlatformClient(platformToken);
|
|
18
|
+
const uncloudRunner = new UncloudRunner(platformClient);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const appName = appArg || getAppName();
|
|
22
|
+
|
|
23
|
+
const app = await platformClient.getApp(appName);
|
|
24
|
+
if (!app) {
|
|
25
|
+
console.log(chalk.red(`\nApp '${appName}' not found\n`));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const spinner = ora('Fetching services...').start();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Get service list from uncloud (includes status, replicas, etc.)
|
|
33
|
+
const output = await uncloudRunner.serviceList(appName);
|
|
34
|
+
spinner.stop();
|
|
35
|
+
|
|
36
|
+
console.log(chalk.cyan(`\nServices in '${appName}':\n`));
|
|
37
|
+
|
|
38
|
+
// Parse and display the output nicely
|
|
39
|
+
const lines = output.trim().split('\n');
|
|
40
|
+
|
|
41
|
+
if (lines.length === 0 || (lines.length === 1 && !lines[0].trim())) {
|
|
42
|
+
console.log(chalk.yellow(' No services found.\n'));
|
|
43
|
+
console.log(`Run ${chalk.bold('hackerrun deploy')} to deploy your app.\n`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Print the table output from uncloud
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
const trimmed = line.trim();
|
|
50
|
+
// Skip connection messages
|
|
51
|
+
if (trimmed.startsWith('Connecting') || trimmed.startsWith('Connected')) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (trimmed) {
|
|
55
|
+
// Highlight header row
|
|
56
|
+
if (trimmed.startsWith('NAME')) {
|
|
57
|
+
console.log(chalk.bold(` ${trimmed}`));
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` ${trimmed}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log();
|
|
65
|
+
console.log(chalk.dim(`Machines: ${app.nodes.length}`));
|
|
66
|
+
console.log(chalk.dim(`Scale a service: hackerrun scale ${appName} <service> <replicas>`));
|
|
67
|
+
console.log();
|
|
68
|
+
|
|
69
|
+
} catch (error) {
|
|
70
|
+
spinner.fail('Failed to fetch services');
|
|
71
|
+
console.log(chalk.yellow('\nMake sure the app is deployed and running.\n'));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(chalk.red(`\nError: ${(error as Error).message}\n`));
|
|
77
|
+
process.exit(1);
|
|
78
|
+
} finally {
|
|
79
|
+
uncloudRunner.cleanup();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return cmd;
|
|
84
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { createConnectCommand, createDisconnectCommand } from './commands/connec
|
|
|
9
9
|
import { createBuildsCommand } from './commands/builds.js';
|
|
10
10
|
import { createVPNCommands } from './commands/vpn.js';
|
|
11
11
|
import { createScaleCommand } from './commands/scale.js';
|
|
12
|
+
import { createServicesCommand } from './commands/services.js';
|
|
12
13
|
|
|
13
14
|
const program = new Command();
|
|
14
15
|
|
|
@@ -36,8 +37,9 @@ program.addCommand(createBuildsCommand());
|
|
|
36
37
|
// Register VPN command
|
|
37
38
|
program.addCommand(createVPNCommands());
|
|
38
39
|
|
|
39
|
-
// Register scale
|
|
40
|
+
// Register scale and services commands
|
|
40
41
|
program.addCommand(createScaleCommand());
|
|
42
|
+
program.addCommand(createServicesCommand());
|
|
41
43
|
|
|
42
44
|
const { appsCmd, nodesCmd, sshCmd, destroyCmd, linkCmd, renameCmd, domainCmd } = createAppCommands();
|
|
43
45
|
program.addCommand(appsCmd);
|
package/src/lib/cluster.ts
CHANGED
|
@@ -62,10 +62,7 @@ export class ClusterManager {
|
|
|
62
62
|
async initializeCluster(options: ClusterInitOptions): Promise<AppCluster> {
|
|
63
63
|
const { appName, location, vmSize, storageSize, bootImage } = options;
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
const vmName = `${appName}-1`;
|
|
67
|
-
|
|
68
|
-
let spinner = ora(`Creating VM '${vmName}' in ${location}...`).start();
|
|
65
|
+
let spinner = ora(`Creating VM for '${appName}' in ${location}...`).start();
|
|
69
66
|
|
|
70
67
|
try {
|
|
71
68
|
// Get platform SSH keys (for VM creation)
|
|
@@ -77,10 +74,9 @@ export class ClusterManager {
|
|
|
77
74
|
const privateSubnetId = gateway?.subnetId;
|
|
78
75
|
|
|
79
76
|
// Create IPv6-only VM via platform API with platform SSH key
|
|
80
|
-
// VM is
|
|
81
|
-
spinner.text =
|
|
82
|
-
const vm = await this.platformClient.
|
|
83
|
-
name: vmName,
|
|
77
|
+
// VM name is auto-generated by platform: u{userId}-{appName}-{nodeNum}
|
|
78
|
+
spinner.text = 'Creating VM...';
|
|
79
|
+
const vm = await this.platformClient.createAppVM(appName, {
|
|
84
80
|
location,
|
|
85
81
|
size: vmSize,
|
|
86
82
|
storage_size: storageSize,
|
|
@@ -89,9 +85,12 @@ export class ClusterManager {
|
|
|
89
85
|
public_key: platformKeys.platformPublicKey,
|
|
90
86
|
enable_ip4: !privateSubnetId, // IPv6-only if we have a subnet for NAT64
|
|
91
87
|
private_subnet_id: privateSubnetId,
|
|
88
|
+
isPrimary: true,
|
|
92
89
|
});
|
|
93
90
|
|
|
94
|
-
|
|
91
|
+
const vmName = vm.name; // Name generated by platform
|
|
92
|
+
|
|
93
|
+
spinner.text = `Waiting for VM '${vmName}' to be ready...`;
|
|
95
94
|
spinner.stop();
|
|
96
95
|
|
|
97
96
|
console.log(chalk.cyan('\nWaiting for VM to get an IPv6 address...'));
|
|
@@ -172,10 +171,7 @@ export class ClusterManager {
|
|
|
172
171
|
const gateway = await this.platformClient.getGateway(cluster.location);
|
|
173
172
|
const privateSubnetId = gateway?.subnetId;
|
|
174
173
|
|
|
175
|
-
|
|
176
|
-
const vmName = this.generateNodeName(appName, cluster.nodes);
|
|
177
|
-
|
|
178
|
-
let spinner = ora(`Adding node '${vmName}' to cluster...`).start();
|
|
174
|
+
let spinner = ora('Adding node to cluster...').start();
|
|
179
175
|
|
|
180
176
|
try {
|
|
181
177
|
// Get platform SSH keys (for VM creation)
|
|
@@ -183,9 +179,9 @@ export class ClusterManager {
|
|
|
183
179
|
const platformKeys = await this.getPlatformKeys();
|
|
184
180
|
|
|
185
181
|
// Create new IPv6-only VM via platform API with platform SSH key
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
// VM name is auto-generated by platform: u{userId}-{appName}-{nodeNum}
|
|
183
|
+
spinner.text = 'Creating VM...';
|
|
184
|
+
const vm = await this.platformClient.createAppVM(appName, {
|
|
189
185
|
location: cluster.location,
|
|
190
186
|
size: vmSize,
|
|
191
187
|
boot_image: bootImage,
|
|
@@ -193,9 +189,12 @@ export class ClusterManager {
|
|
|
193
189
|
public_key: platformKeys.platformPublicKey,
|
|
194
190
|
enable_ip4: !privateSubnetId,
|
|
195
191
|
private_subnet_id: privateSubnetId,
|
|
192
|
+
isPrimary: false,
|
|
196
193
|
});
|
|
197
194
|
|
|
198
|
-
|
|
195
|
+
const vmName = vm.name; // Name generated by platform
|
|
196
|
+
|
|
197
|
+
spinner.text = `Waiting for VM '${vmName}' to be ready...`;
|
|
199
198
|
spinner.stop();
|
|
200
199
|
console.log(chalk.cyan('\nWaiting for VM to get an IPv6 address...'));
|
|
201
200
|
const vmWithIp = await this.waitForVM(cluster.location, vmName, 600, false);
|
|
@@ -544,31 +543,6 @@ REMOTESCRIPT`, {
|
|
|
544
543
|
console.log(chalk.yellow(' Warning: Timeout waiting for unregistry, continuing anyway...'));
|
|
545
544
|
}
|
|
546
545
|
|
|
547
|
-
/**
|
|
548
|
-
* Generate a sequential node name: appName-1, appName-2, etc.
|
|
549
|
-
* Finds the next available number by checking existing node names
|
|
550
|
-
*/
|
|
551
|
-
private generateNodeName(appName: string, existingNodes: VMNode[]): string {
|
|
552
|
-
// Extract existing numbers from node names
|
|
553
|
-
const existingNumbers = existingNodes
|
|
554
|
-
.map(n => {
|
|
555
|
-
const match = n.name.match(new RegExp(`^${appName}-(\\d+)$`));
|
|
556
|
-
return match ? parseInt(match[1], 10) : 0;
|
|
557
|
-
})
|
|
558
|
-
.filter(n => n > 0);
|
|
559
|
-
|
|
560
|
-
// Find the next available number
|
|
561
|
-
const maxNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) : 0;
|
|
562
|
-
return `${appName}-${maxNumber + 1}`;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
/**
|
|
566
|
-
* Generate a random ID (used for legacy naming or fallback)
|
|
567
|
-
*/
|
|
568
|
-
private generateId(): string {
|
|
569
|
-
return Math.random().toString(36).substring(2, 9);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
546
|
/**
|
|
573
547
|
* Sleep for specified milliseconds
|
|
574
548
|
*/
|
|
@@ -64,6 +64,19 @@ export interface CreateVMParams {
|
|
|
64
64
|
private_subnet_id?: string; // Put VM in specific private subnet for NAT64 routing
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// Parameters for creating an app VM (name is auto-generated by platform)
|
|
68
|
+
export interface CreateAppVMParams {
|
|
69
|
+
location: string;
|
|
70
|
+
size: string;
|
|
71
|
+
storage_size?: number;
|
|
72
|
+
unix_user: string;
|
|
73
|
+
public_key: string;
|
|
74
|
+
boot_image: string;
|
|
75
|
+
enable_ip4?: boolean;
|
|
76
|
+
private_subnet_id?: string;
|
|
77
|
+
isPrimary?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
67
80
|
export interface UbicloudVM {
|
|
68
81
|
id: string;
|
|
69
82
|
name: string;
|
|
@@ -274,6 +287,20 @@ export class PlatformClient {
|
|
|
274
287
|
return vm;
|
|
275
288
|
}
|
|
276
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Create a new VM for an app with auto-generated name
|
|
292
|
+
* Name format: u{userId}-{appName}-{nodeNum}
|
|
293
|
+
*/
|
|
294
|
+
async createAppVM(appName: string, params: CreateAppVMParams): Promise<UbicloudVM> {
|
|
295
|
+
const { vm } = await this.request<{ vm: UbicloudVM }>(
|
|
296
|
+
'POST',
|
|
297
|
+
`/api/apps/${appName}/vms`,
|
|
298
|
+
params,
|
|
299
|
+
{ operation: `Create VM for app '${appName}'` }
|
|
300
|
+
);
|
|
301
|
+
return vm;
|
|
302
|
+
}
|
|
303
|
+
|
|
277
304
|
/**
|
|
278
305
|
* Get a specific VM
|
|
279
306
|
*/
|