elestio 1.2.3 → 1.3.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/README.md +18 -11
- package/package.json +1 -1
- package/src/commands/cicd-template.js +44 -13
- package/src/payloads/pipeline.js +50 -1
- package/src/registry.js +1 -0
- package/src/templates/elestio-config.js +14 -2
package/README.md
CHANGED
|
@@ -345,17 +345,24 @@ written as `random_password` are generated, `[EMAIL]` becomes your account
|
|
|
345
345
|
email, and `[CI_CD_DOMAIN]` is resolved by the platform once the pipeline has a
|
|
346
346
|
domain.
|
|
347
347
|
|
|
348
|
-
**
|
|
349
|
-
|
|
350
|
-
| |
|
|
351
|
-
|
|
352
|
-
| What it does | Inlines the template's `docker-compose.yml` | Generates the template repo into your Git account, then builds from it |
|
|
353
|
-
| Needs a Git account | No | Yes, connected in the dashboard |
|
|
354
|
-
|
|
|
355
|
-
|
|
|
356
|
-
| Works for | Templates that need no
|
|
357
|
-
|
|
358
|
-
The
|
|
348
|
+
**Three routes. The default needs nothing from you:**
|
|
349
|
+
|
|
350
|
+
| | repo (default) | inline (`--inline-compose`) | git (`--owner <git-user>`) |
|
|
351
|
+
|---|---|---|---|
|
|
352
|
+
| What it does | Points the pipeline at the `elestio-examples` repo, which Elestio clones on the VM | Inlines the template's `docker-compose.yml` only | Generates the template repo into your Git account, then builds from it |
|
|
353
|
+
| Needs a Git account | No | No | Yes, connected in the dashboard |
|
|
354
|
+
| Install scripts | **Run** | Skipped | Run |
|
|
355
|
+
| Files the compose mounts or reads | **Present** | Missing | Present |
|
|
356
|
+
| Works for | The whole catalogue | Templates that need no file from the repo | Every template |
|
|
357
|
+
|
|
358
|
+
The default route sends `imageData.dockerExample` (the template repo URL),
|
|
359
|
+
`branch` and `repoName`. The backend stores them as the pipeline's
|
|
360
|
+
`gitConfig.repoURL`, and the deployment agent clones that repo with Elestio's
|
|
361
|
+
own Git token - the same thing the dashboard does. Nothing is copied into your
|
|
362
|
+
account, and no Git account is needed.
|
|
363
|
+
|
|
364
|
+
`--inline-compose` sends only the compose file, which is what you want for a
|
|
365
|
+
compose of your own. That route has no checkout and runs no install script, so a template
|
|
359
366
|
whose `docker-compose.yml` bind-mounts a file cannot work: Docker creates the
|
|
360
367
|
missing source as an empty directory and the container fails to start. The file
|
|
361
368
|
may ship in the template repo (n8n's `./n8n-task-runners.json`) or be generated
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import { loadConfig } from '../config.js';
|
|
|
3
3
|
import { log, colors, outputJson, formatTable } from '../utils.js';
|
|
4
4
|
import { getTemplates } from './templates.js';
|
|
5
5
|
import { createSubstitutions, normalizeElestioConfig, repoFileMounts } from '../templates/elestio-config.js';
|
|
6
|
-
import { buildGitPipelinePayload, buildComposePipelinePayload } from '../payloads/pipeline.js';
|
|
6
|
+
import { buildGitPipelinePayload, buildComposePipelinePayload, buildTemplateRepoPipelinePayload } from '../payloads/pipeline.js';
|
|
7
7
|
import { parseGitAuthId } from './cicd.js';
|
|
8
8
|
import { TEMPLATE_REPO_OWNER, TEMPLATE_REPO_HOST } from '../constants.js';
|
|
9
9
|
|
|
@@ -16,11 +16,18 @@ import { TEMPLATE_REPO_OWNER, TEMPLATE_REPO_HOST } from '../constants.js';
|
|
|
16
16
|
* produces a pipeline that starts nothing, which is why this is a dedicated
|
|
17
17
|
* command rather than a flag on `cicd create`.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Three routes:
|
|
20
|
+
* repo (default) - point the pipeline at the elestio-examples repo itself.
|
|
21
|
+
* Elestio clones it on the VM with its own Git token, so the repo's
|
|
22
|
+
* files exist and its install scripts run. No Git account needed,
|
|
23
|
+
* nothing copied into the user's account. This is what the
|
|
24
|
+
* dashboard does.
|
|
25
|
+
* inline - inline the docker-compose.yml only (--inline-compose). No
|
|
26
|
+
* checkout: install scripts are skipped and any file the compose
|
|
27
|
+
* mounts or reads (env_file) is missing. Half the catalogue cannot
|
|
28
|
+
* work this way.
|
|
29
|
+
* git - generate the template repo into the user's own Git account
|
|
30
|
+
* (--owner). Needs an endpoint the API does not expose (404).
|
|
24
31
|
*/
|
|
25
32
|
|
|
26
33
|
function templateRepoUrl(repoName) {
|
|
@@ -208,13 +215,16 @@ export async function deployTemplate(nameOrId, options = {}) {
|
|
|
208
215
|
}
|
|
209
216
|
log('success', `Config loaded: runtime "${elestioConfig.config.runTime}", ${elestioConfig.variables ? elestioConfig.variables.split('\n').length : 0} env vars, ${elestioConfig.ports.length} port(s)`);
|
|
210
217
|
|
|
211
|
-
const
|
|
218
|
+
const route = useGit ? 'git' : (options.inlineCompose ? 'inline' : 'repo');
|
|
219
|
+
const payload = route === 'git'
|
|
212
220
|
? await buildGitRoute({ options, projectId, target, template, repoName, pipelineName, branch, elestioConfig, dryRun: !!options.dryRun })
|
|
213
|
-
:
|
|
221
|
+
: route === 'inline'
|
|
222
|
+
? await buildComposeRoute({ projectId, target, repoName, pipelineName, branch, sourceRepoUrl, elestioConfig, force: options.force })
|
|
223
|
+
: await buildTemplateRepoRoute({ projectId, target, repoName, pipelineName, branch, sourceRepoUrl, elestioConfig });
|
|
214
224
|
|
|
215
225
|
if (options.dryRun) {
|
|
216
|
-
if (options.json) { outputJson({ dryRun: true, route
|
|
217
|
-
printPlan({ template, repoName, pipelineName, target, elestioConfig,
|
|
226
|
+
if (options.json) { outputJson({ dryRun: true, route, payload }); return payload; }
|
|
227
|
+
printPlan({ template, repoName, pipelineName, target, elestioConfig, route, branch });
|
|
218
228
|
log('info', 'To deploy, run the same command without --dry-run');
|
|
219
229
|
return payload;
|
|
220
230
|
}
|
|
@@ -229,7 +239,7 @@ export async function deployTemplate(nameOrId, options = {}) {
|
|
|
229
239
|
log('success', `Pipeline "${pipelineName}" created on ${target.displayName}`);
|
|
230
240
|
|
|
231
241
|
if (options.json) {
|
|
232
|
-
outputJson({ status: 'OK', pipelineName, target: target.vmID, route
|
|
242
|
+
outputJson({ status: 'OK', pipelineName, target: target.vmID, route, webUI: elestioConfig.webUI, response });
|
|
233
243
|
return response;
|
|
234
244
|
}
|
|
235
245
|
|
|
@@ -286,6 +296,22 @@ async function buildGitRoute({ options, projectId, target, template, repoName, p
|
|
|
286
296
|
});
|
|
287
297
|
}
|
|
288
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Default route: the pipeline points at the template repo, which Elestio clones
|
|
301
|
+
* on the VM. Nothing to refuse -- the repo's files are there and its install
|
|
302
|
+
* scripts run.
|
|
303
|
+
*/
|
|
304
|
+
async function buildTemplateRepoRoute({ projectId, target, repoName, pipelineName, branch, sourceRepoUrl, elestioConfig }) {
|
|
305
|
+
log('info', `Fetching docker-compose from ${TEMPLATE_REPO_OWNER}/${repoName}...`);
|
|
306
|
+
const compose = await fetchCompose(sourceRepoUrl, branch, projectId);
|
|
307
|
+
log('success', 'Compose file loaded');
|
|
308
|
+
|
|
309
|
+
const hooks = installHooks(elestioConfig);
|
|
310
|
+
if (hooks.length > 0) log('info', `Install scripts run from the cloned repo: ${hooks.join(', ')}`);
|
|
311
|
+
|
|
312
|
+
return buildTemplateRepoPipelinePayload({ target, projectId, pipelineName, compose, repoName, branch, elestioConfig });
|
|
313
|
+
}
|
|
314
|
+
|
|
289
315
|
async function buildComposeRoute({ projectId, target, repoName, pipelineName, branch, sourceRepoUrl, elestioConfig, force }) {
|
|
290
316
|
log('info', `Fetching docker-compose from ${TEMPLATE_REPO_OWNER}/${repoName}...`);
|
|
291
317
|
const compose = await fetchCompose(sourceRepoUrl, branch, projectId);
|
|
@@ -346,13 +372,18 @@ function collectOverrides(options) {
|
|
|
346
372
|
return overrides;
|
|
347
373
|
}
|
|
348
374
|
|
|
349
|
-
function printPlan({ template, repoName, pipelineName, target, elestioConfig,
|
|
375
|
+
function printPlan({ template, repoName, pipelineName, target, elestioConfig, route, branch }) {
|
|
350
376
|
console.log(`\n${colors.bold}Pipeline Preview (--dry-run)${colors.reset}\n`);
|
|
351
377
|
console.log(` Software: ${colors.cyan}${template.title}${colors.reset} (ID: ${template.id})`);
|
|
352
378
|
console.log(` Template: ${TEMPLATE_REPO_OWNER}/${repoName} @ ${branch}`);
|
|
353
379
|
console.log(` Pipeline: ${pipelineName}`);
|
|
354
380
|
console.log(` Target: ${target.displayName} (${target.vmID})`);
|
|
355
|
-
|
|
381
|
+
const routes = {
|
|
382
|
+
repo: 'repo (Elestio clones the template repo on the VM, install scripts run)',
|
|
383
|
+
inline: 'inline compose (no checkout: install scripts skipped, mounted files missing)',
|
|
384
|
+
git: 'git (repo generated in your account, install scripts run)'
|
|
385
|
+
};
|
|
386
|
+
console.log(` Route: ${routes[route]}`);
|
|
356
387
|
console.log(` Runtime: ${elestioConfig.config.runTime || 'N/A'}`);
|
|
357
388
|
console.log(` Ports: ${elestioConfig.ports.map(p => `${p.listeningPort}->${p.targetPort}`).join(', ') || 'default'}`);
|
|
358
389
|
console.log(` Env vars: ${elestioConfig.variables ? elestioConfig.variables.split('\n').map(v => v.split('=')[0]).join(', ') : 'none'}`);
|
package/src/payloads/pipeline.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RUNTIME_PRESETS } from '../constants.js';
|
|
1
|
+
import { RUNTIME_PRESETS, TEMPLATE_REPO_OWNER, TEMPLATE_REPO_HOST } from '../constants.js';
|
|
2
2
|
import { EMPTY_LIFECYCLE } from '../templates/elestio-config.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -147,6 +147,55 @@ export function buildGitPipelinePayload({
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Pipeline running catalog software from its own template repo.
|
|
152
|
+
*
|
|
153
|
+
* This is what the dashboard sends: `imageData.dockerExample` carries the
|
|
154
|
+
* elestio-examples URL, and the backend then stores it as the pipeline's
|
|
155
|
+
* gitConfig.repoURL (createCiCdExistServer). The deployment agent clones that
|
|
156
|
+
* repo on the VM with Elestio's own Git token, so the repo's files exist and
|
|
157
|
+
* its lifecycle scripts run -- without copying anything into the user's Git
|
|
158
|
+
* account, and without needing a connected Git account at all.
|
|
159
|
+
*
|
|
160
|
+
* The inline route below cannot do either, which is why half the catalogue
|
|
161
|
+
* (Kafka, Chromadb, Weaviate, ClickHouse, WordPress...) is undeployable
|
|
162
|
+
* through it.
|
|
163
|
+
*/
|
|
164
|
+
export function buildTemplateRepoPipelinePayload({
|
|
165
|
+
target, projectId, pipelineName, compose, repoName, branch = 'main', elestioConfig, overrides = {}
|
|
166
|
+
}) {
|
|
167
|
+
if (!compose) throw new Error('A docker-compose file is required');
|
|
168
|
+
if (!repoName) throw new Error('A template repo name is required');
|
|
169
|
+
const fromFile = elestioConfig && elestioConfig.hasConfig ? elestioConfig : null;
|
|
170
|
+
|
|
171
|
+
return buildPipelinePayload({
|
|
172
|
+
target, projectId, pipelineName,
|
|
173
|
+
cicdMode: 'DockerCompose',
|
|
174
|
+
gitData: {},
|
|
175
|
+
imageData: {
|
|
176
|
+
isPrivate: false,
|
|
177
|
+
compose,
|
|
178
|
+
// The three fields that turn this into a cloned-repo pipeline.
|
|
179
|
+
dockerExample: `${TEMPLATE_REPO_HOST}/${TEMPLATE_REPO_OWNER}/${repoName}`,
|
|
180
|
+
branch,
|
|
181
|
+
repoName
|
|
182
|
+
},
|
|
183
|
+
configData: {
|
|
184
|
+
runTime: 'NodeJs', framework: 'NoFramework', version: '20',
|
|
185
|
+
buildDir: '/', rootDir: '/', buildCmd: '', runCmd: '', installCmd: ''
|
|
186
|
+
},
|
|
187
|
+
ports: overrides.ports || fromFile?.ports,
|
|
188
|
+
exposedPorts: overrides.exposedPorts || fromFile?.exposedPorts,
|
|
189
|
+
variables: overrides.variables ?? fromFile?.variables ?? '',
|
|
190
|
+
// Kept, unlike the inline route: the repo is checked out, so they can run.
|
|
191
|
+
lifeCycleCommand: fromFile?.lifeCycleCommand,
|
|
192
|
+
copyCommandConfig: fromFile?.copyCommandConfig || [],
|
|
193
|
+
authID: null,
|
|
194
|
+
isPublicGitRepo: false,
|
|
195
|
+
isNeedToCreateRepo: false
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
150
199
|
/**
|
|
151
200
|
* Pipeline running an inline docker-compose file, with no Git repo attached.
|
|
152
201
|
*
|
package/src/registry.js
CHANGED
|
@@ -632,6 +632,7 @@ export const registry = {
|
|
|
632
632
|
owner: args.owner, branch: args.branch, repoName: args['repo-name'],
|
|
633
633
|
gitType: args['git-type'], authId: args['auth-id'],
|
|
634
634
|
git: !args['no-git'] && (args.git === true || args.git === 'true' || !!args.owner),
|
|
635
|
+
inlineCompose: !!args['inline-compose'],
|
|
635
636
|
private: !!args.private, nonOrg: !!args['non-org'],
|
|
636
637
|
buildCmd: args['build-cmd'], runCmd: args['run-cmd'],
|
|
637
638
|
installCmd: args['install-cmd'], buildDir: args['build-dir'],
|
|
@@ -185,8 +185,6 @@ export function repoFileMounts(compose) {
|
|
|
185
185
|
};
|
|
186
186
|
|
|
187
187
|
// Short syntax, quoted or not: - ./file:/in/container[:ro]
|
|
188
|
-
// Many templates quote their mounts (Chromadb among them), and the
|
|
189
|
-
// unquoted-only pattern let those through silently.
|
|
190
188
|
for (const match of compose.matchAll(/^\s*-\s+["']?(\.{1,2}\/[^\s:"']+)["']?\s*:/gm)) {
|
|
191
189
|
add(match[1]);
|
|
192
190
|
}
|
|
@@ -196,5 +194,19 @@ export function repoFileMounts(compose) {
|
|
|
196
194
|
add(match[1]);
|
|
197
195
|
}
|
|
198
196
|
|
|
197
|
+
// env_file, inline or as a list. Docker refuses to start when the file is
|
|
198
|
+
// missing, and 8 of the 22 catalogue templates sampled read one.
|
|
199
|
+
for (const match of compose.matchAll(/^\s*env_file:\s*(?:\[([^\]]*)\]|["']?([^\s"'\[]+)["']?)\s*$/gm)) {
|
|
200
|
+
for (const entry of (match[1] ?? match[2] ?? '').split(',')) {
|
|
201
|
+
const file = entry.trim().replace(/^["']|["']$/g, '');
|
|
202
|
+
if (file.startsWith('.')) add(file);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const block of compose.matchAll(/^\s*env_file:\s*$((?:\n\s*-\s+.*)+)/gm)) {
|
|
206
|
+
for (const item of block[1].matchAll(/^\s*-\s+["']?([^\s"']+)["']?\s*$/gm)) {
|
|
207
|
+
if (item[1].startsWith('.')) add(item[1]);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
199
211
|
return [...mounts];
|
|
200
212
|
}
|