elestio 1.0.3 → 1.2.0
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/LICENSE +21 -21
- package/README.md +523 -279
- package/bin/elestio.js +5 -5
- package/package.json +49 -42
- package/src/api.js +143 -105
- package/src/cli.js +32 -838
- package/src/commands/access.js +224 -132
- package/src/commands/actions.js +427 -427
- package/src/commands/auth.js +146 -146
- package/src/commands/backups.js +215 -215
- package/src/commands/billing.js +106 -106
- package/src/commands/cicd-template.js +363 -0
- package/src/commands/cicd.js +509 -486
- package/src/commands/clusters.js +589 -0
- package/src/commands/projects.js +145 -145
- package/src/commands/services.js +306 -294
- package/src/commands/templates.js +188 -188
- package/src/commands/volumes.js +117 -117
- package/src/config.js +104 -84
- package/src/constants.js +76 -0
- package/src/deployment.js +33 -0
- package/src/payloads/pipeline.js +191 -0
- package/src/payloads/server.js +130 -0
- package/src/registry.js +715 -0
- package/src/router.js +188 -0
- package/src/templates/elestio-config.js +188 -0
- package/src/utils.js +174 -162
package/src/registry.js
ADDED
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { log, colors, outputJson } from './utils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The command table. Handlers are imported lazily so that `elestio --help` and
|
|
5
|
+
* a mistyped command stay instant, and so one broken module cannot take the
|
|
6
|
+
* whole CLI down.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const load = {
|
|
10
|
+
access: () => import('./commands/access.js'),
|
|
11
|
+
actions: () => import('./commands/actions.js'),
|
|
12
|
+
auth: () => import('./commands/auth.js'),
|
|
13
|
+
backups: () => import('./commands/backups.js'),
|
|
14
|
+
billing: () => import('./commands/billing.js'),
|
|
15
|
+
cicd: () => import('./commands/cicd.js'),
|
|
16
|
+
cicdTemplate: () => import('./commands/cicd-template.js'),
|
|
17
|
+
clusters: () => import('./commands/clusters.js'),
|
|
18
|
+
projects: () => import('./commands/projects.js'),
|
|
19
|
+
services: () => import('./commands/services.js'),
|
|
20
|
+
templates: () => import('./commands/templates.js'),
|
|
21
|
+
volumes: () => import('./commands/volumes.js')
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Reads a positional argument, falling back to named flags. */
|
|
25
|
+
function positional(args, index, ...flagNames) {
|
|
26
|
+
for (const flag of flagNames) {
|
|
27
|
+
if (args[flag] !== undefined) return args[flag];
|
|
28
|
+
}
|
|
29
|
+
return args._[index];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function requireArg(value, usage) {
|
|
33
|
+
if (value === undefined || value === null || value === '') throw new Error(`Usage: elestio ${usage}`);
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A vmID-taking action: `elestio <command> <vmID>`. */
|
|
38
|
+
function vmAction({ group, summary, handler, module = 'actions', extra }) {
|
|
39
|
+
return {
|
|
40
|
+
group,
|
|
41
|
+
summary,
|
|
42
|
+
usage: `${summary.split(' ')[0].toLowerCase()} <vmID>`,
|
|
43
|
+
async run({ args }) {
|
|
44
|
+
const vmID = requireArg(args._[1], `${args._[0]} <vmID>`);
|
|
45
|
+
const mod = await load[module]();
|
|
46
|
+
await mod[handler](vmID, extra ? extra(args) : undefined);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const registry = {
|
|
52
|
+
|
|
53
|
+
// ── Auth & Config ──
|
|
54
|
+
|
|
55
|
+
login: {
|
|
56
|
+
group: 'Auth & Config',
|
|
57
|
+
summary: 'Save your Elestio credentials',
|
|
58
|
+
usage: 'login --email <email> --token <token>',
|
|
59
|
+
flags: [
|
|
60
|
+
{ name: '--email <email>', summary: 'Account email' },
|
|
61
|
+
{ name: '--token <token>', summary: 'API token from Dashboard > Security' }
|
|
62
|
+
],
|
|
63
|
+
examples: [{ description: 'Authenticate', command: 'elestio login --email you@example.com --token abc123' }],
|
|
64
|
+
async run({ args, json }) {
|
|
65
|
+
const { configure } = await load.auth();
|
|
66
|
+
await configure(args.email || args._[1], args.token || args._[2], json);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
whoami: {
|
|
71
|
+
group: 'Auth & Config',
|
|
72
|
+
summary: 'Show the current user and default project',
|
|
73
|
+
async run({ args, json }) {
|
|
74
|
+
const { whoami } = await load.auth();
|
|
75
|
+
whoami(json);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
config: {
|
|
80
|
+
group: 'Auth & Config',
|
|
81
|
+
summary: 'Show or update CLI configuration',
|
|
82
|
+
usage: 'config [--set-default-project <id>] [--provider <p>] ...',
|
|
83
|
+
flags: [
|
|
84
|
+
{ name: '--set-default-project <id>', summary: 'Project used when --project is omitted' },
|
|
85
|
+
{ name: '--provider <name>', summary: 'Default cloud provider' },
|
|
86
|
+
{ name: '--datacenter <code>', summary: 'Default region' },
|
|
87
|
+
{ name: '--server-type <size>', summary: 'Default server size' },
|
|
88
|
+
{ name: '--support <level>', summary: 'Default support tier' }
|
|
89
|
+
],
|
|
90
|
+
async run({ args, json }) {
|
|
91
|
+
const auth = await load.auth();
|
|
92
|
+
if (args.email && args.token) {
|
|
93
|
+
await auth.configure(args.email, args.token, json);
|
|
94
|
+
} else if (args['set-default-project'] || args['default-project']) {
|
|
95
|
+
auth.setDefaultProject(args['set-default-project'] || args['default-project']);
|
|
96
|
+
} else if (args.provider || args.datacenter || args['server-type'] || args.support) {
|
|
97
|
+
auth.setDefaults(args.provider, args.datacenter, args['server-type'], args.support);
|
|
98
|
+
} else if (args._[1] === 'test') {
|
|
99
|
+
await auth.testAuth(json);
|
|
100
|
+
} else {
|
|
101
|
+
auth.showConfig(json);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
auth: {
|
|
107
|
+
group: 'Auth & Config',
|
|
108
|
+
summary: 'Test authentication',
|
|
109
|
+
defaultAction: 'test',
|
|
110
|
+
actions: {
|
|
111
|
+
test: { summary: 'Verify the stored credentials still work', usage: 'test', async run({ json }) { (await load.auth()).testAuth(json); } }
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// ── Catalog ──
|
|
116
|
+
|
|
117
|
+
templates: {
|
|
118
|
+
group: 'Catalog',
|
|
119
|
+
summary: 'Browse the software catalog',
|
|
120
|
+
defaultAction: 'list',
|
|
121
|
+
flags: [{ name: '--category <name>', summary: 'Filter by category' }],
|
|
122
|
+
examples: [
|
|
123
|
+
{ description: 'Find PostgreSQL', command: 'elestio templates search postgres' },
|
|
124
|
+
{ description: 'Inspect a template', command: 'elestio templates info 11' }
|
|
125
|
+
],
|
|
126
|
+
actions: {
|
|
127
|
+
list: {
|
|
128
|
+
summary: 'List all templates',
|
|
129
|
+
usage: 'list',
|
|
130
|
+
async run({ args, json }) { await (await load.templates()).listTemplates(args.category, json); }
|
|
131
|
+
},
|
|
132
|
+
search: {
|
|
133
|
+
summary: 'Search templates by name',
|
|
134
|
+
usage: 'search <query>',
|
|
135
|
+
async run({ args, json }) {
|
|
136
|
+
const { searchTemplates } = await load.templates();
|
|
137
|
+
const results = await searchTemplates(args._[2], args.category);
|
|
138
|
+
if (json) { outputJson(results.map(t => ({ id: t.id, title: t.title, category: t.category }))); return; }
|
|
139
|
+
console.log(`\n${colors.bold}Search Results (${results.length})${colors.reset}\n`);
|
|
140
|
+
results.forEach(t => console.log(` ${t.id}: ${t.title} [${t.category}]`));
|
|
141
|
+
console.log('');
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
info: {
|
|
145
|
+
summary: 'Show one template in detail',
|
|
146
|
+
usage: 'info <id|name>',
|
|
147
|
+
async run({ args, json }) {
|
|
148
|
+
const { findTemplate } = await load.templates();
|
|
149
|
+
const template = await findTemplate(requireArg(args._[2], 'templates info <id|name>'));
|
|
150
|
+
if (!template) throw new Error(`Template "${args._[2]}" not found`);
|
|
151
|
+
if (json) { outputJson(template); return; }
|
|
152
|
+
console.log(`\n${colors.bold}${template.title}${colors.reset}`);
|
|
153
|
+
console.log(` ID: ${template.id}`);
|
|
154
|
+
console.log(` Category: ${template.category}`);
|
|
155
|
+
console.log(` Version: ${template.version || template.dockerhub_default_tag || 'latest'}`);
|
|
156
|
+
if (template.description) console.log(` Desc: ${template.description}`);
|
|
157
|
+
console.log('');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
sizes: {
|
|
164
|
+
group: 'Catalog',
|
|
165
|
+
summary: 'List server sizes and pricing',
|
|
166
|
+
usage: 'sizes [--provider <name>] [--country <code>]',
|
|
167
|
+
async run({ args, json }) { await (await load.templates()).listSizes(args.provider, args.country, json); }
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
categories: {
|
|
171
|
+
group: 'Catalog',
|
|
172
|
+
summary: 'List template categories',
|
|
173
|
+
async run({ json }) { await (await load.templates()).listCategories(json); }
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// ── Projects ──
|
|
177
|
+
|
|
178
|
+
projects: {
|
|
179
|
+
group: 'Projects',
|
|
180
|
+
summary: 'Manage projects and their members',
|
|
181
|
+
defaultAction: 'list',
|
|
182
|
+
actions: {
|
|
183
|
+
list: { summary: 'List all projects', usage: 'list', async run({ json }) { await (await load.projects()).listProjects(json); } },
|
|
184
|
+
create: {
|
|
185
|
+
summary: 'Create a project', usage: 'create <name>',
|
|
186
|
+
async run({ args }) { await (await load.projects()).createProject(positional(args, 2, 'name'), args.description, args.emails); }
|
|
187
|
+
},
|
|
188
|
+
edit: {
|
|
189
|
+
summary: 'Rename or re-describe a project', usage: 'edit <id>',
|
|
190
|
+
async run({ args }) { await (await load.projects()).editProject(positional(args, 2, 'id'), args.name, args.description, args.emails); }
|
|
191
|
+
},
|
|
192
|
+
delete: {
|
|
193
|
+
summary: 'Delete a project (--force)', usage: 'delete <id> --force',
|
|
194
|
+
async run({ args }) { await (await load.projects()).deleteProject(positional(args, 2, 'id'), !!args.force); }
|
|
195
|
+
},
|
|
196
|
+
members: {
|
|
197
|
+
summary: 'List project members', usage: 'members <projectId>',
|
|
198
|
+
async run({ args, json }) { await (await load.projects()).listMembers(positional(args, 2, 'project', 'id'), json); }
|
|
199
|
+
},
|
|
200
|
+
'add-member': {
|
|
201
|
+
summary: 'Add a member (--role)', usage: 'add-member <projectId> <email>',
|
|
202
|
+
async run({ args }) { await (await load.projects()).addMember(positional(args, 2, 'project'), positional(args, 3, 'email'), args.role); }
|
|
203
|
+
},
|
|
204
|
+
'remove-member': {
|
|
205
|
+
summary: 'Remove a member', usage: 'remove-member <projectId> <memberId>',
|
|
206
|
+
async run({ args }) { await (await load.projects()).removeMember(positional(args, 2, 'project'), positional(args, 3, 'member')); }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
// ── Services ──
|
|
212
|
+
|
|
213
|
+
services: {
|
|
214
|
+
group: 'Services',
|
|
215
|
+
summary: 'List services in a project',
|
|
216
|
+
usage: 'services [--project <id>]',
|
|
217
|
+
async run({ args, json }) { await (await load.services()).listServices(args.project, json); }
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
service: {
|
|
221
|
+
group: 'Services',
|
|
222
|
+
summary: 'Show one service in detail',
|
|
223
|
+
usage: 'service <vmID>',
|
|
224
|
+
async run({ args, json }) {
|
|
225
|
+
await (await load.services()).showService(requireArg(args._[1], 'service <vmID>'), args.project, json);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
deploy: {
|
|
230
|
+
group: 'Services',
|
|
231
|
+
summary: 'Deploy a service, optionally as a cluster',
|
|
232
|
+
usage: 'deploy <template> [options]',
|
|
233
|
+
flags: [
|
|
234
|
+
{ name: '--name <name>', summary: 'Service name (generated when omitted)' },
|
|
235
|
+
{ name: '--size <type>', summary: 'Server size, e.g. MEDIUM-2C-4G' },
|
|
236
|
+
{ name: '--region <code>', summary: 'Datacenter, e.g. nbg' },
|
|
237
|
+
{ name: '--provider <name>', summary: 'Cloud provider, e.g. hetzner' },
|
|
238
|
+
{ name: '--version <tag>', summary: 'Software version' },
|
|
239
|
+
{ name: '--cluster', summary: 'Deploy as a cluster instead of a single node' },
|
|
240
|
+
{ name: '--nodes <n>', summary: 'Total nodes, primary included (default: the template minimum)' },
|
|
241
|
+
{ name: '--cluster-mode <mode>', summary: 'primary-replica (default) or multi-master' },
|
|
242
|
+
{ name: '--pipeline-name <n>', summary: 'CI/CD targets: first pipeline name (max 24 chars, [a-z0-9-])' },
|
|
243
|
+
{ name: '--cicd-mode <mode>', summary: 'CI/CD targets: DockerCompose (default), GITHUB, GITLAB, GITLAB_SELF_HOSTED' },
|
|
244
|
+
{ name: '--dry-run', summary: 'Print what would be created, create nothing' },
|
|
245
|
+
{ name: '--wait false', summary: 'Return without waiting for the deployment' }
|
|
246
|
+
],
|
|
247
|
+
examples: [
|
|
248
|
+
{ description: 'Single PostgreSQL node', command: 'elestio deploy postgresql --size MEDIUM-2C-4G' },
|
|
249
|
+
{ description: 'PostgreSQL with 2 replicas (3 VMs)', command: 'elestio deploy postgresql --cluster --nodes 3' },
|
|
250
|
+
{ description: 'MySQL multi-master', command: 'elestio deploy mysql --cluster --cluster-mode multi-master' },
|
|
251
|
+
{ description: 'Check the plan and cost first', command: 'elestio deploy clickhouse --cluster --dry-run' }
|
|
252
|
+
],
|
|
253
|
+
async run({ args, json }) {
|
|
254
|
+
const { deployService } = await load.services();
|
|
255
|
+
await deployService(requireArg(args._[1], 'deploy <template> [options]'), {
|
|
256
|
+
name: args.name, size: args.size, region: args.region,
|
|
257
|
+
provider: args.provider, project: args.project,
|
|
258
|
+
support: args.support, email: args.email,
|
|
259
|
+
version: args.version, dryRun: !!args['dry-run'],
|
|
260
|
+
wait: args.wait !== 'false', timeout: args.timeout ? parseInt(args.timeout) : undefined,
|
|
261
|
+
json, pipelineName: args['pipeline-name'], cicdMode: args['cicd-mode'],
|
|
262
|
+
cluster: !!args.cluster,
|
|
263
|
+
nodes: args.nodes !== undefined ? Number(args.nodes) : undefined,
|
|
264
|
+
clusterMode: args['cluster-mode']
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
'delete-service': {
|
|
270
|
+
group: 'Services',
|
|
271
|
+
summary: 'Delete a service (--force)',
|
|
272
|
+
usage: 'delete-service <vmID> --force',
|
|
273
|
+
async run({ args }) {
|
|
274
|
+
await (await load.services()).deleteService(requireArg(args._[1], 'delete-service <vmID> --force'), {
|
|
275
|
+
force: !!args.force, project: args.project, withBackups: !!args['with-backups']
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
|
|
280
|
+
'move-service': {
|
|
281
|
+
group: 'Services',
|
|
282
|
+
summary: 'Move a service to another project',
|
|
283
|
+
usage: 'move-service <vmID> <targetProjectId>',
|
|
284
|
+
async run({ args }) {
|
|
285
|
+
const vmID = requireArg(args._[1], 'move-service <vmID> <targetProjectId>');
|
|
286
|
+
const targetProject = requireArg(args._[2] || args.target, 'move-service <vmID> <targetProjectId>');
|
|
287
|
+
await (await load.services()).moveService(vmID, targetProject, args.project);
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
wait: {
|
|
292
|
+
group: 'Services',
|
|
293
|
+
summary: 'Block until a deployment finishes',
|
|
294
|
+
usage: 'wait <vmID>',
|
|
295
|
+
async run({ args }) {
|
|
296
|
+
await (await load.services()).waitForDeployment(
|
|
297
|
+
requireArg(args._[1], 'wait <vmID>'), args.project, args.timeout ? parseInt(args.timeout) : undefined
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
// ── Clusters ──
|
|
303
|
+
|
|
304
|
+
clusters: {
|
|
305
|
+
group: 'Clusters',
|
|
306
|
+
summary: 'Manage database and application clusters',
|
|
307
|
+
defaultAction: 'list',
|
|
308
|
+
examples: [
|
|
309
|
+
{ description: 'See what can be clustered', command: 'elestio clusters templates' },
|
|
310
|
+
{ description: 'Create one', command: 'elestio deploy postgresql --cluster --nodes 3' },
|
|
311
|
+
{ description: 'Promote a replica', command: 'elestio clusters promote 7 848530 --force' }
|
|
312
|
+
],
|
|
313
|
+
actions: {
|
|
314
|
+
list: { summary: 'List clusters in the project', usage: 'list', async run({ args, json }) { await (await load.clusters()).listClusters(args.project, json); } },
|
|
315
|
+
info: {
|
|
316
|
+
summary: 'Show a cluster and its nodes', usage: 'info <clusterID>',
|
|
317
|
+
async run({ args, json }) { await (await load.clusters()).showCluster(requireArg(args._[2], 'clusters info <clusterID>'), args.project, json); }
|
|
318
|
+
},
|
|
319
|
+
nodes: {
|
|
320
|
+
summary: 'List the active nodes of a cluster', usage: 'nodes <clusterID>',
|
|
321
|
+
async run({ args, json }) { await (await load.clusters()).listNodes(requireArg(args._[2], 'clusters nodes <clusterID>'), args.project, json); }
|
|
322
|
+
},
|
|
323
|
+
templates: {
|
|
324
|
+
summary: 'List the software that supports clustering', usage: 'templates',
|
|
325
|
+
async run({ json }) { await (await load.clusters()).listClusterTemplates(json); }
|
|
326
|
+
},
|
|
327
|
+
promote: {
|
|
328
|
+
summary: 'Promote a replica to primary (--force)', usage: 'promote <clusterID> <vmID> --force',
|
|
329
|
+
async run({ args }) {
|
|
330
|
+
await (await load.clusters()).promoteNode(
|
|
331
|
+
requireArg(args._[2], 'clusters promote <clusterID> <vmID> --force'),
|
|
332
|
+
requireArg(args._[3] || args.node, 'clusters promote <clusterID> <vmID> --force'),
|
|
333
|
+
args.project, !!args.force
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
failover: {
|
|
338
|
+
summary: 'Turn automatic failover on or off', usage: 'failover <clusterID> on|off',
|
|
339
|
+
async run({ args }) {
|
|
340
|
+
await (await load.clusters()).setAutoFailover(
|
|
341
|
+
requireArg(args._[2], 'clusters failover <clusterID> on|off'),
|
|
342
|
+
requireArg(args._[3], 'clusters failover <clusterID> on|off')
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
resync: {
|
|
347
|
+
summary: 'Re-sync replicas from the primary, erasing replica data (--force)', usage: 'resync <clusterID> --force',
|
|
348
|
+
async run({ args }) { await (await load.clusters()).resyncCluster(requireArg(args._[2], 'clusters resync <clusterID> --force'), args.project, !!args.force); }
|
|
349
|
+
},
|
|
350
|
+
'add-node': {
|
|
351
|
+
summary: 'Add a node, copying the primary (--size/--region to change; --dry-run)', usage: 'add-node <clusterID>',
|
|
352
|
+
async run({ args, json }) {
|
|
353
|
+
await (await load.clusters()).addClusterNode(requireArg(args._[2], 'clusters add-node <clusterID>'), {
|
|
354
|
+
project: args.project, size: args.size, region: args.region, provider: args.provider,
|
|
355
|
+
version: args.version, email: args.email, dryRun: !!args['dry-run'], json
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
'remove-node': {
|
|
360
|
+
summary: 'Remove a replica node and its VM (--force)', usage: 'remove-node <clusterID> <vmID> --force',
|
|
361
|
+
async run({ args }) {
|
|
362
|
+
await (await load.clusters()).removeClusterNode(
|
|
363
|
+
requireArg(args._[2], 'clusters remove-node <clusterID> <vmID> --force'),
|
|
364
|
+
requireArg(args._[3], 'clusters remove-node <clusterID> <vmID> --force'),
|
|
365
|
+
args.project, !!args.force
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
firewall: {
|
|
370
|
+
summary: 'Show which IPs each port of a cluster accepts', usage: 'firewall <clusterID>',
|
|
371
|
+
async run({ args, json }) { await (await load.clusters()).showClusterFirewall(requireArg(args._[2], 'clusters firewall <clusterID>'), args.project, json); }
|
|
372
|
+
},
|
|
373
|
+
'firewall-restrict': {
|
|
374
|
+
summary: 'Only accept a port from given IPs, on every node', usage: 'firewall-restrict <clusterID> --port P --ips ip1,ip2',
|
|
375
|
+
async run({ args }) {
|
|
376
|
+
const { setClusterPortAccess, parseIpList } = await load.clusters();
|
|
377
|
+
const ips = parseIpList(args.ips);
|
|
378
|
+
if (ips.length === 0) throw new Error('--ips is required (comma-separated IPs or CIDR ranges). To reopen a port use firewall-open.');
|
|
379
|
+
await setClusterPortAccess(requireArg(args._[2], 'clusters firewall-restrict <clusterID> --port P --ips ...'), requireArg(args.port, '--port <port>'), ips, args.project);
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
'firewall-open': {
|
|
383
|
+
summary: 'Open a port of a cluster to everyone again', usage: 'firewall-open <clusterID> --port P',
|
|
384
|
+
async run({ args }) {
|
|
385
|
+
await (await load.clusters()).setClusterPortAccess(requireArg(args._[2], 'clusters firewall-open <clusterID> --port P'), requireArg(args.port, '--port <port>'), [], args.project);
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
delete: {
|
|
389
|
+
summary: 'Delete a cluster and all its nodes (--force)', usage: 'delete <clusterID> --force',
|
|
390
|
+
async run({ args }) { await (await load.clusters()).deleteCluster(requireArg(args._[2], 'clusters delete <clusterID> --force'), args.project, !!args.force); }
|
|
391
|
+
},
|
|
392
|
+
lock: {
|
|
393
|
+
summary: 'Enable termination protection', usage: 'lock <clusterID>',
|
|
394
|
+
async run({ args }) { await (await load.clusters()).lockCluster(requireArg(args._[2], 'clusters lock <clusterID>')); }
|
|
395
|
+
},
|
|
396
|
+
unlock: {
|
|
397
|
+
summary: 'Disable termination protection', usage: 'unlock <clusterID>',
|
|
398
|
+
async run({ args }) { await (await load.clusters()).unlockCluster(requireArg(args._[2], 'clusters unlock <clusterID>')); }
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
// ── Server Actions ──
|
|
404
|
+
|
|
405
|
+
reboot: vmAction({ group: 'Server Actions', summary: 'Reboot a VM', handler: 'reboot' }),
|
|
406
|
+
reset: vmAction({ group: 'Server Actions', summary: 'Reset a VM (hard)', handler: 'reset' }),
|
|
407
|
+
shutdown: vmAction({ group: 'Server Actions', summary: 'Shutdown a VM gracefully', handler: 'shutdown' }),
|
|
408
|
+
poweroff: vmAction({ group: 'Server Actions', summary: 'Poweroff a VM (forced)', handler: 'poweroff' }),
|
|
409
|
+
poweron: vmAction({ group: 'Server Actions', summary: 'Poweron a VM', handler: 'poweron' }),
|
|
410
|
+
'restart-stack': vmAction({ group: 'Server Actions', summary: 'Restart the Docker stack', handler: 'restartStack' }),
|
|
411
|
+
lock: vmAction({ group: 'Server Actions', summary: 'Lock a VM (termination protection)', handler: 'lock' }),
|
|
412
|
+
unlock: vmAction({ group: 'Server Actions', summary: 'Unlock a VM', handler: 'unlock' }),
|
|
413
|
+
|
|
414
|
+
resize: {
|
|
415
|
+
group: 'Server Actions',
|
|
416
|
+
summary: 'Resize a VM',
|
|
417
|
+
usage: 'resize <vmID> --size <serverType>',
|
|
418
|
+
async run({ args }) {
|
|
419
|
+
const { resizeServer } = await load.actions();
|
|
420
|
+
await resizeServer(requireArg(args._[1], 'resize <vmID> --size <type>'), args.size || args._[2], {
|
|
421
|
+
project: args.project, provider: args.provider, region: args.region,
|
|
422
|
+
cpuRamOnly: args['cpu-ram-only'] !== 'false'
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
'change-version': {
|
|
428
|
+
group: 'Server Actions',
|
|
429
|
+
summary: 'Change the software version of a service',
|
|
430
|
+
usage: 'change-version <vmID> <version>',
|
|
431
|
+
async run({ args }) {
|
|
432
|
+
const { changeVersion } = await load.actions();
|
|
433
|
+
await changeVersion(requireArg(args._[1], 'change-version <vmID> <version>'), args._[2] || args.version);
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
|
|
437
|
+
// ── Networking & Security ──
|
|
438
|
+
|
|
439
|
+
firewall: {
|
|
440
|
+
group: 'Networking & Security',
|
|
441
|
+
summary: 'Manage firewall rules',
|
|
442
|
+
actions: {
|
|
443
|
+
get: { summary: 'Show firewall rules', usage: 'get <vmID>', async run({ args, json }) { await (await load.actions()).getFirewallRules(requireArg(args._[2], 'firewall get <vmID>'), json); } },
|
|
444
|
+
enable: { summary: "Enable the firewall with rules", usage: "enable <vmID> --rules '[...]'", async run({ args }) { await (await load.actions()).enableFirewall(requireArg(args._[2], "firewall enable <vmID> --rules '[...]'"), JSON.parse(args.rules || '[]')); } },
|
|
445
|
+
update: { summary: 'Replace the firewall rules', usage: "update <vmID> --rules '[...]'", async run({ args }) { await (await load.actions()).updateFirewall(requireArg(args._[2], "firewall update <vmID> --rules '[...]'"), JSON.parse(args.rules || '[]')); } },
|
|
446
|
+
disable: { summary: 'Disable the firewall', usage: 'disable <vmID>', async run({ args }) { await (await load.actions()).disableFirewall(requireArg(args._[2], 'firewall disable <vmID>')); } }
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
ssl: {
|
|
451
|
+
group: 'Networking & Security',
|
|
452
|
+
summary: 'Manage SSL and custom domains',
|
|
453
|
+
actions: {
|
|
454
|
+
list: { summary: 'List custom domains', usage: 'list <vmID>', async run({ args, json }) { await (await load.actions()).listSslDomains(requireArg(args._[2], 'ssl list <vmID>'), json); } },
|
|
455
|
+
add: { summary: 'Add a custom domain', usage: 'add <vmID> <domain>', async run({ args }) { await (await load.actions()).addSslDomain(requireArg(args._[2], 'ssl add <vmID> <domain>'), positional(args, 3, 'domain')); } },
|
|
456
|
+
remove: { summary: 'Remove a custom domain', usage: 'remove <vmID> <domain>', async run({ args }) { await (await load.actions()).removeSslDomain(requireArg(args._[2], 'ssl remove <vmID> <domain>'), positional(args, 3, 'domain')); } }
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
|
|
460
|
+
'ssh-keys': {
|
|
461
|
+
group: 'Networking & Security',
|
|
462
|
+
summary: 'Manage SSH keys on a service',
|
|
463
|
+
actions: {
|
|
464
|
+
list: { summary: 'List SSH keys', usage: 'list <vmID>', async run({ args, json }) { await (await load.actions()).listSshKeys(requireArg(args._[2], 'ssh-keys list <vmID>'), json); } },
|
|
465
|
+
add: { summary: 'Add an SSH key', usage: 'add <vmID> --name <n> --key <pubkey>', async run({ args }) { await (await load.actions()).addSshKey(requireArg(args._[2], 'ssh-keys add <vmID> --name <n> --key <pubkey>'), args.name || args._[3], args.key || args._[4]); } },
|
|
466
|
+
remove: { summary: 'Remove an SSH key', usage: 'remove <vmID> --name <n>', async run({ args }) { await (await load.actions()).removeSshKey(requireArg(args._[2], 'ssh-keys remove <vmID> --name <n>'), args.name || args._[3]); } }
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
updates: {
|
|
471
|
+
group: 'Updates',
|
|
472
|
+
summary: 'Manage system and application auto-updates',
|
|
473
|
+
actions: {
|
|
474
|
+
'system-enable': { summary: 'Enable OS auto-updates', usage: 'system-enable <vmID>', async run({ args }) { await (await load.actions()).enableSystemAutoUpdate(requireArg(args._[2], 'updates system-enable <vmID>'), { securityOnly: args['security-only'] !== 'false', dayOfWeek: args.day, hour: args.hour, minute: args.minute }); } },
|
|
475
|
+
'system-disable': { summary: 'Disable OS auto-updates', usage: 'system-disable <vmID>', async run({ args }) { await (await load.actions()).disableSystemAutoUpdate(requireArg(args._[2], 'updates system-disable <vmID>')); } },
|
|
476
|
+
'system-now': { summary: 'Run the OS update now', usage: 'system-now <vmID>', async run({ args }) { await (await load.actions()).runSystemUpdate(requireArg(args._[2], 'updates system-now <vmID>')); } },
|
|
477
|
+
'app-enable': { summary: 'Enable application auto-updates', usage: 'app-enable <vmID>', async run({ args }) { await (await load.actions()).enableAppAutoUpdate(requireArg(args._[2], 'updates app-enable <vmID>'), { dayOfWeek: args.day, hour: args.hour, minute: args.minute }); } },
|
|
478
|
+
'app-disable': { summary: 'Disable application auto-updates', usage: 'app-disable <vmID>', async run({ args }) { await (await load.actions()).disableAppAutoUpdate(requireArg(args._[2], 'updates app-disable <vmID>')); } },
|
|
479
|
+
'app-now': { summary: 'Run the application update now', usage: 'app-now <vmID>', async run({ args }) { await (await load.actions()).runAppUpdate(requireArg(args._[2], 'updates app-now <vmID>')); } }
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
alerts: {
|
|
484
|
+
group: 'Monitoring',
|
|
485
|
+
summary: 'Manage alert rules',
|
|
486
|
+
actions: {
|
|
487
|
+
get: { summary: 'Show alert rules', usage: 'get <vmID>', async run({ args, json }) { await (await load.actions()).getAlerts(requireArg(args._[2], 'alerts get <vmID>'), json); } },
|
|
488
|
+
enable: { summary: 'Enable alerts', usage: "enable <vmID> --rules '[...]'", async run({ args }) { await (await load.actions()).enableAlerts(requireArg(args._[2], "alerts enable <vmID> --rules '[...]'"), JSON.parse(args.rules || '[]'), args.cycle ? parseInt(args.cycle) : 60); } },
|
|
489
|
+
update: { summary: 'Replace the alert rules', usage: "update <vmID> --rules '[...]'", async run({ args }) { await (await load.actions()).enableAlerts(requireArg(args._[2], "alerts update <vmID> --rules '[...]'"), JSON.parse(args.rules || '[]'), args.cycle ? parseInt(args.cycle) : 60); } },
|
|
490
|
+
disable: { summary: 'Disable alerts', usage: 'disable <vmID>', async run({ args }) { await (await load.actions()).disableAlerts(requireArg(args._[2], 'alerts disable <vmID>')); } }
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
// ── Backups & Snapshots ──
|
|
495
|
+
|
|
496
|
+
backups: {
|
|
497
|
+
group: 'Backups & Snapshots',
|
|
498
|
+
summary: 'Manage local and remote backups',
|
|
499
|
+
actions: {
|
|
500
|
+
'local-list': { summary: 'List local backups', usage: 'local-list <vmID>', async run({ args, json }) { await (await load.backups()).listLocalBackups(requireArg(args._[2], 'backups local-list <vmID>'), json); } },
|
|
501
|
+
'local-take': { summary: 'Take a local backup', usage: 'local-take <vmID>', async run({ args }) { await (await load.backups()).takeLocalBackup(requireArg(args._[2], 'backups local-take <vmID>')); } },
|
|
502
|
+
'local-restore': { summary: 'Restore a local backup', usage: 'local-restore <vmID> <path>', async run({ args }) { await (await load.backups()).restoreLocalBackup(requireArg(args._[2], 'backups local-restore <vmID> <path>'), positional(args, 3, 'path')); } },
|
|
503
|
+
'local-delete': { summary: 'Delete a local backup', usage: 'local-delete <vmID> <path>', async run({ args }) { await (await load.backups()).deleteLocalBackup(requireArg(args._[2], 'backups local-delete <vmID> <path>'), positional(args, 3, 'path')); } },
|
|
504
|
+
'remote-list': { summary: 'List remote backups', usage: 'remote-list <vmID>', async run({ args, json }) { await (await load.backups()).listRemoteBackups(requireArg(args._[2], 'backups remote-list <vmID>'), json); } },
|
|
505
|
+
'remote-take': { summary: 'Take a remote backup', usage: 'remote-take <vmID>', async run({ args }) { await (await load.backups()).takeRemoteBackup(requireArg(args._[2], 'backups remote-take <vmID>')); } },
|
|
506
|
+
'remote-restore': { summary: 'Restore a remote backup', usage: 'remote-restore <vmID> <snapshot>', async run({ args }) { await (await load.backups()).restoreRemoteBackup(requireArg(args._[2], 'backups remote-restore <vmID> <snapshot>'), positional(args, 3, 'snapshot')); } },
|
|
507
|
+
'auto-enable': { summary: 'Enable scheduled backups (--path, --hour)', usage: 'auto-enable <vmID>', async run({ args }) { await (await load.backups()).setupAutoBackups(requireArg(args._[2], 'backups auto-enable <vmID>'), args.path, args.hour); } },
|
|
508
|
+
'auto-disable': { summary: 'Disable scheduled backups', usage: 'auto-disable <vmID>', async run({ args }) { await (await load.backups()).disableAutoBackups(requireArg(args._[2], 'backups auto-disable <vmID>')); } }
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
snapshots: {
|
|
513
|
+
group: 'Backups & Snapshots',
|
|
514
|
+
summary: 'Manage provider-level VM snapshots',
|
|
515
|
+
actions: {
|
|
516
|
+
list: { summary: 'List snapshots', usage: 'list <vmID>', async run({ args, json }) { await (await load.backups()).listSnapshots(requireArg(args._[2], 'snapshots list <vmID>'), json); } },
|
|
517
|
+
take: { summary: 'Take a snapshot', usage: 'take <vmID>', async run({ args }) { await (await load.backups()).takeSnapshot(requireArg(args._[2], 'snapshots take <vmID>')); } },
|
|
518
|
+
restore: { summary: 'Restore a snapshot (0 = most recent)', usage: 'restore <vmID> <orderID>', async run({ args }) { await (await load.backups()).restoreSnapshot(requireArg(args._[2], 'snapshots restore <vmID> <orderID>'), positional(args, 3, 'order')); } },
|
|
519
|
+
delete: { summary: 'Delete a snapshot', usage: 'delete <vmID> <snapshotID>', async run({ args }) { await (await load.backups()).deleteSnapshot(requireArg(args._[2], 'snapshots delete <vmID> <snapshotID>'), positional(args, 3, 'id')); } },
|
|
520
|
+
'auto-enable': { summary: 'Enable automatic snapshots', usage: 'auto-enable <vmID>', async run({ args }) { await (await load.backups()).enableAutoSnapshots(requireArg(args._[2], 'snapshots auto-enable <vmID>')); } },
|
|
521
|
+
'auto-disable': { summary: 'Disable automatic snapshots', usage: 'auto-disable <vmID>', async run({ args }) { await (await load.backups()).disableAutoSnapshots(requireArg(args._[2], 'snapshots auto-disable <vmID>')); } }
|
|
522
|
+
}
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
's3-backup': {
|
|
526
|
+
group: 'Backups & Snapshots',
|
|
527
|
+
summary: 'Manage backups to an external S3 bucket',
|
|
528
|
+
flags: [
|
|
529
|
+
{ name: '--key <id>', summary: 'S3 access key' },
|
|
530
|
+
{ name: '--secret <secret>', summary: 'S3 secret key' },
|
|
531
|
+
{ name: '--bucket <name>', summary: 'Bucket name' },
|
|
532
|
+
{ name: '--endpoint <url>', summary: 'S3 endpoint' },
|
|
533
|
+
{ name: '--prefix <path>', summary: 'Key prefix' }
|
|
534
|
+
],
|
|
535
|
+
actions: {
|
|
536
|
+
verify: { summary: 'Check the S3 configuration', usage: 'verify <vmID>', async run({ args }) { await (await load.backups()).verifyS3Config(requireArg(args._[2], 's3-backup verify <vmID>'), s3Config(args)); } },
|
|
537
|
+
enable: { summary: 'Enable S3 backups', usage: 'enable <vmID>', async run({ args }) { await (await load.backups()).enableS3Backup(requireArg(args._[2], 's3-backup enable <vmID>'), s3Config(args)); } },
|
|
538
|
+
disable: { summary: 'Disable S3 backups', usage: 'disable <vmID>', async run({ args }) { await (await load.backups()).disableS3Backup(requireArg(args._[2], 's3-backup disable <vmID>')); } },
|
|
539
|
+
take: { summary: 'Take an S3 backup now', usage: 'take <vmID>', async run({ args }) { await (await load.backups()).takeS3Backup(requireArg(args._[2], 's3-backup take <vmID>')); } },
|
|
540
|
+
list: { summary: 'List S3 backups', usage: 'list <vmID>', async run({ args, json }) { await (await load.backups()).listS3Backups(requireArg(args._[2], 's3-backup list <vmID>'), json); } },
|
|
541
|
+
restore: { summary: 'Restore an S3 backup', usage: 'restore <vmID> <key>', async run({ args }) { await (await load.backups()).restoreS3Backup(requireArg(args._[2], 's3-backup restore <vmID> <key>'), positional(args, 3, 'key')); } },
|
|
542
|
+
delete: { summary: 'Delete an S3 backup', usage: 'delete <vmID> <key>', async run({ args }) { await (await load.backups()).deleteS3Backup(requireArg(args._[2], 's3-backup delete <vmID> <key>'), positional(args, 3, 'key')); } }
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
// ── Access ──
|
|
547
|
+
|
|
548
|
+
logs: {
|
|
549
|
+
group: 'Access',
|
|
550
|
+
summary: 'Open a live log view of a service (--mode install for the install log)',
|
|
551
|
+
usage: 'logs <vmID> [--mode app|install]',
|
|
552
|
+
async run({ args, json }) { await (await load.access()).getLogsView(requireArg(args._[1], 'logs <vmID>'), args.project, args.mode || 'app', json); }
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
audits: {
|
|
556
|
+
group: 'Access',
|
|
557
|
+
summary: 'Show the audit trail of a service (--days, default 30)',
|
|
558
|
+
usage: 'audits <vmID> [--days N]',
|
|
559
|
+
async run({ args, json }) { await (await load.access()).getAudits(requireArg(args._[1], 'audits <vmID>'), args.project, args.days ? Number(args.days) : 30, json); }
|
|
560
|
+
},
|
|
561
|
+
|
|
562
|
+
credentials: {
|
|
563
|
+
group: 'Access',
|
|
564
|
+
summary: 'Show the application credentials of a service',
|
|
565
|
+
usage: 'credentials <vmID>',
|
|
566
|
+
async run({ args, json }) { await (await load.access()).getCredentials(requireArg(args._[1], 'credentials <vmID>'), args.project, json); }
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
ssh: {
|
|
570
|
+
group: 'Access',
|
|
571
|
+
summary: 'Open SSH access (--direct for host details)',
|
|
572
|
+
usage: 'ssh <vmID> [--direct]',
|
|
573
|
+
async run({ args, json }) {
|
|
574
|
+
const access = await load.access();
|
|
575
|
+
const vmID = requireArg(args._[1], 'ssh <vmID>');
|
|
576
|
+
if (args.direct) await access.getSSHDirect(vmID, args.project, json);
|
|
577
|
+
else await access.getSSH(vmID, args.project, json);
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
vscode: {
|
|
582
|
+
group: 'Access',
|
|
583
|
+
summary: 'Open VSCode in the browser',
|
|
584
|
+
usage: 'vscode <vmID>',
|
|
585
|
+
async run({ args, json }) { await (await load.access()).getVSCode(requireArg(args._[1], 'vscode <vmID>'), args.project, json); }
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
files: {
|
|
589
|
+
group: 'Access',
|
|
590
|
+
summary: 'Open the file explorer',
|
|
591
|
+
usage: 'files <vmID>',
|
|
592
|
+
async run({ args, json }) { await (await load.access()).getFileExplorer(requireArg(args._[1], 'files <vmID>'), args.project, json); }
|
|
593
|
+
},
|
|
594
|
+
|
|
595
|
+
// ── Volumes ──
|
|
596
|
+
|
|
597
|
+
volumes: {
|
|
598
|
+
group: 'Volumes',
|
|
599
|
+
summary: 'Manage block storage volumes',
|
|
600
|
+
defaultAction: 'list',
|
|
601
|
+
actions: {
|
|
602
|
+
list: { summary: 'List volumes in the project', usage: 'list', async run({ args, json }) { await (await load.volumes()).listVolumes(args.project, json); } },
|
|
603
|
+
create: { summary: 'Create a standalone volume', usage: 'create --name <n> --size <gb>', async run({ args }) { await (await load.volumes()).createVolume({ name: args.name || args._[2], size: args.size ? parseInt(args.size) : undefined, provider: args.provider, datacenter: args.datacenter, projectId: args.project, serverId: args.server, storageType: args.type }); } },
|
|
604
|
+
'service-list': { summary: 'List volumes attached to a service', usage: 'service-list <vmID>', async run({ args, json }) { await (await load.volumes()).getServiceVolumes(requireArg(args._[2], 'volumes service-list <vmID>'), json); } },
|
|
605
|
+
'service-create': { summary: 'Create and attach a volume', usage: 'service-create <vmID> --name <n> --size <gb>', async run({ args }) { await (await load.volumes()).createServiceVolume(requireArg(args._[2], 'volumes service-create <vmID> --name <n>'), { name: args.name || args._[3], size: args.size ? parseInt(args.size) : undefined, storageType: args.type }); } },
|
|
606
|
+
resize: { summary: 'Resize a volume', usage: 'resize <vmID> <volumeID> --size <gb>', async run({ args }) { await (await load.volumes()).resizeVolume(requireArg(args._[2], 'volumes resize <vmID> <volumeID> --size <gb>'), positional(args, 3, 'volume'), parseInt(args.size || args._[4])); } },
|
|
607
|
+
detach: { summary: 'Detach a volume (--keep false deletes it)', usage: 'detach <vmID> <volumeID>', async run({ args }) { await (await load.volumes()).detachVolume(requireArg(args._[2], 'volumes detach <vmID> <volumeID>'), positional(args, 3, 'volume'), { keepVolume: args.keep !== 'false' }); } },
|
|
608
|
+
delete: { summary: 'Delete a volume', usage: 'delete <vmID> <volumeID>', async run({ args }) { await (await load.volumes()).deleteServiceVolume(requireArg(args._[2], 'volumes delete <vmID> <volumeID>'), positional(args, 3, 'volume')); } },
|
|
609
|
+
protect: { summary: 'Toggle volume protection (--disable)', usage: 'protect <vmID> <volumeID>', async run({ args }) { await (await load.volumes()).setVolumeProtection(requireArg(args._[2], 'volumes protect <vmID> <volumeID>'), positional(args, 3, 'volume'), args.disable !== true); } }
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
|
|
613
|
+
// ── CI/CD ──
|
|
614
|
+
|
|
615
|
+
cicd: {
|
|
616
|
+
group: 'CI/CD',
|
|
617
|
+
summary: 'Manage CI/CD targets and pipelines',
|
|
618
|
+
examples: [
|
|
619
|
+
{ description: 'Deploy catalog software as a pipeline (the usual case)', command: 'elestio cicd deploy-template n8n --target 848528' },
|
|
620
|
+
{ description: 'See what it would create first', command: 'elestio cicd deploy-template n8n --target 848528 --dry-run' },
|
|
621
|
+
{ description: 'Git route: generates the repo in your account, keeps lifecycle scripts', command: 'elestio cicd deploy-template n8n --target 848528 --owner my-github-user' },
|
|
622
|
+
{ description: 'Pipeline from your own repo', command: 'elestio cicd create --auto --target 848528 --name my-app --repo acme/my-app' }
|
|
623
|
+
],
|
|
624
|
+
actions: {
|
|
625
|
+
'deploy-template': {
|
|
626
|
+
summary: 'Deploy catalog software as a pipeline, configured from its elestio.yml',
|
|
627
|
+
usage: 'deploy-template <software> --target <vmID>',
|
|
628
|
+
async run({ args, json }) {
|
|
629
|
+
const { deployTemplate } = await load.cicdTemplate();
|
|
630
|
+
await deployTemplate(requireArg(args._[2], 'cicd deploy-template <software> --target <vmID>'), {
|
|
631
|
+
target: args.target, project: args.project, name: args.name,
|
|
632
|
+
owner: args.owner, branch: args.branch, repoName: args['repo-name'],
|
|
633
|
+
gitType: args['git-type'], authId: args['auth-id'],
|
|
634
|
+
git: !args['no-git'] && (args.git === true || args.git === 'true' || !!args.owner),
|
|
635
|
+
private: !!args.private, nonOrg: !!args['non-org'],
|
|
636
|
+
buildCmd: args['build-cmd'], runCmd: args['run-cmd'],
|
|
637
|
+
installCmd: args['install-cmd'], buildDir: args['build-dir'],
|
|
638
|
+
variables: args.variables,
|
|
639
|
+
dryRun: !!args['dry-run'], force: !!args.force, json
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
templates: {
|
|
644
|
+
summary: 'List catalog software deployable as a pipeline',
|
|
645
|
+
usage: 'templates [query]',
|
|
646
|
+
async run({ args, json }) { await (await load.cicdTemplate()).listDeployableTemplates(args._[2], json); }
|
|
647
|
+
},
|
|
648
|
+
targets: { summary: 'List CI/CD targets', usage: 'targets', async run({ args, json }) { await (await load.cicd()).getCicdServices(args.project, json); } },
|
|
649
|
+
pipelines: { summary: 'List pipelines on a target', usage: 'pipelines <vmID>', async run({ args, json }) { await (await load.cicd()).getServicePipelines(requireArg(args._[2], 'cicd pipelines <vmID>'), args.project, json); } },
|
|
650
|
+
'pipeline-info': { summary: 'Show pipeline details', usage: 'pipeline-info <vmID> <pipelineID>', async run({ args, json }) { await (await load.cicd()).getPipelineDetails(requireArg(args._[2], 'cicd pipeline-info <vmID> <pipelineID>'), positional(args, 3, 'pipeline'), args.project, json); } },
|
|
651
|
+
'pipeline-restart': { summary: 'Restart a pipeline', usage: 'pipeline-restart <vmID> <pipelineID>', async run({ args }) { await (await load.cicd()).restartPipeline(requireArg(args._[2], 'cicd pipeline-restart <vmID> <pipelineID>'), positional(args, 3, 'pipeline'), args.project); } },
|
|
652
|
+
'pipeline-stop': { summary: 'Stop a pipeline', usage: 'pipeline-stop <vmID> <pipelineID>', async run({ args }) { await (await load.cicd()).stopPipeline(requireArg(args._[2], 'cicd pipeline-stop <vmID> <pipelineID>'), positional(args, 3, 'pipeline'), args.project); } },
|
|
653
|
+
'pipeline-delete': { summary: 'Delete a pipeline (--force)', usage: 'pipeline-delete <vmID> <pipelineID> --force', async run({ args }) { await (await load.cicd()).deletePipeline(requireArg(args._[2], 'cicd pipeline-delete <vmID> <pipelineID> --force'), positional(args, 3, 'pipeline'), args.project, !!args.force); } },
|
|
654
|
+
'pipeline-resync': { summary: 'Re-sync a pipeline', usage: 'pipeline-resync <vmID> <pipelineID>', async run({ args }) { await (await load.cicd()).resyncPipeline(requireArg(args._[2], 'cicd pipeline-resync <vmID> <pipelineID>'), positional(args, 3, 'pipeline'), args.project); } },
|
|
655
|
+
'pipeline-logs': { summary: 'Tail the running pipeline logs', usage: 'pipeline-logs <vmID> <pipelineID>', async run({ args }) { await (await load.cicd()).getPipelineLogs(requireArg(args._[2], 'cicd pipeline-logs <vmID> <pipelineID>'), positional(args, 3, 'pipeline'), args.project); } },
|
|
656
|
+
'pipeline-history': { summary: 'Show the build history', usage: 'pipeline-history <vmID> <pipelineID>', async run({ args, json }) { await (await load.cicd()).getPipelineHistory(requireArg(args._[2], 'cicd pipeline-history <vmID> <pipelineID>'), positional(args, 3, 'pipeline'), args.project, json); } },
|
|
657
|
+
'pipeline-log': { summary: 'Print one build log file', usage: 'pipeline-log <vmID> --pipeline <id> --file <path>', async run({ args }) { await (await load.cicd()).viewPipelineLog(requireArg(args._[2], 'cicd pipeline-log <vmID> --pipeline <id> --file <path>'), args.pipeline, args.file, args.project); } },
|
|
658
|
+
domains: { summary: 'List pipeline domains', usage: 'domains <vmID> <pipelineID>', async run({ args, json }) { await (await load.cicd()).listPipelineDomains(requireArg(args._[2], 'cicd domains <vmID> <pipelineID>'), positional(args, 3, 'pipeline'), args.project, json); } },
|
|
659
|
+
'domain-add': { summary: 'Add a pipeline domain', usage: 'domain-add <vmID> --pipeline <id> --domain <d>', async run({ args }) { await (await load.cicd()).addPipelineDomain(requireArg(args._[2], 'cicd domain-add <vmID> --pipeline <id> --domain <d>'), args.pipeline, args.domain || args._[3], args.project); } },
|
|
660
|
+
'domain-remove': { summary: 'Remove a pipeline domain', usage: 'domain-remove <vmID> --pipeline <id> --domain <d>', async run({ args }) { await (await load.cicd()).removePipelineDomain(requireArg(args._[2], 'cicd domain-remove <vmID> --pipeline <id> --domain <d>'), args.pipeline, args.domain || args._[3], args.project); } },
|
|
661
|
+
create: {
|
|
662
|
+
summary: 'Create a pipeline from your own repo (--auto) or a JSON file',
|
|
663
|
+
usage: 'create --auto --target <vmID> --name <n> --repo <owner/repo>',
|
|
664
|
+
async run({ args }) {
|
|
665
|
+
const cicd = await load.cicd();
|
|
666
|
+
if (!args.auto) { await cicd.createPipeline(args._[2] || args.file); return; }
|
|
667
|
+
await cicd.autoCreatePipeline({
|
|
668
|
+
project: args.project, target: args.target, name: args.name,
|
|
669
|
+
mode: args.mode, repo: args.repo, branch: args.branch,
|
|
670
|
+
authId: args['auth-id'], image: args.image, imageTag: args['image-tag'],
|
|
671
|
+
buildDir: args['build-dir'], rootDir: args['root-dir'],
|
|
672
|
+
framework: args.framework, buildCmd: args['build-cmd'],
|
|
673
|
+
runCmd: args['run-cmd'], installCmd: args['install-cmd'],
|
|
674
|
+
nodeVersion: args['node-version'], variables: args.variables,
|
|
675
|
+
compose: args.compose
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
template: { summary: 'Print a pipeline config template', usage: 'template [mode]', async run({ args }) { console.log((await load.cicd()).generatePipelineTemplate(args._[2] || args.mode || 'docker')); } },
|
|
680
|
+
registries: { summary: 'List Docker registries', usage: 'registries', async run({ args, json }) { await (await load.cicd()).getDockerRegistries(args.project, json); } },
|
|
681
|
+
'registry-add': {
|
|
682
|
+
summary: 'Add a Docker registry',
|
|
683
|
+
usage: 'registry-add --name <n> --username <u> --password <p> --url <repo>',
|
|
684
|
+
async run({ args }) {
|
|
685
|
+
await (await load.cicd()).addDockerRegistry(
|
|
686
|
+
args.project, args.name || args._[2], args.username || args._[3],
|
|
687
|
+
args.password, args.url,
|
|
688
|
+
args['registry-type'] || args.registryType || 'docker.io',
|
|
689
|
+
args['repo-id'] || args.repoId || '',
|
|
690
|
+
args['gitlab-url'] || args.gitlabUrl || ''
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
|
|
697
|
+
// ── Billing ──
|
|
698
|
+
|
|
699
|
+
billing: {
|
|
700
|
+
group: 'Billing',
|
|
701
|
+
summary: 'View billing totals',
|
|
702
|
+
defaultAction: 'summary',
|
|
703
|
+
actions: {
|
|
704
|
+
summary: { summary: 'Account-wide billing summary', usage: 'summary', async run({ json }) { await (await load.billing()).getBillingSummary(json); } },
|
|
705
|
+
project: { summary: 'Billing for one project', usage: 'project <projectId>', async run({ args, json }) { await (await load.billing()).getProjectBilling(positional(args, 2, 'project'), json); } }
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
function s3Config(args) {
|
|
711
|
+
return {
|
|
712
|
+
apiKey: args.key, secretKey: args.secret, bucketName: args.bucket,
|
|
713
|
+
endPoint: args.endpoint, prefix: args.prefix, providerType: args.type
|
|
714
|
+
};
|
|
715
|
+
}
|