@akash-chowdhury-24/deployhub 2.0.31 → 2.0.33
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 +3 -2
- package/package.json +1 -1
- package/src/commands/deploy.js +21 -1
- package/src/commands/doctor.js +167 -17
- package/src/commands/env.js +11 -1
- package/src/commands/init.js +1 -0
- package/src/commands/verify.js +32 -9
- package/src/core/config.js +10 -0
- package/src/core/environments.js +21 -6
- package/src/core/stages.js +20 -2
- package/src/deployment/deployment-env.js +90 -24
- package/src/deployment/init-prompts.js +146 -15
- package/src/deployment/providers/docker.js +175 -6
- package/src/deployment/providers/kubernetes.js +3 -0
- package/src/deployment/providers/ssh.js +12 -87
- package/src/deployment/ssh-connection.js +149 -0
- package/src/utils/credential-inventory.js +10 -0
- package/src/utils/docker-image-deploy.js +4 -0
- package/src/utils/docker-port-publish.js +328 -0
- package/src/utils/docker-remote-mode.js +33 -0
- package/src/utils/docker-remote.js +218 -0
- package/src/utils/github-actions.js +6 -1
package/README.md
CHANGED
|
@@ -855,7 +855,7 @@ DeployHub detects whether the server uses Debian-style `sites-available` or RHEL
|
|
|
855
855
|
1. Set `DOCKER_IMAGE_NAME` in `.env` (e.g. `myuser/myapp` for Docker Hub)
|
|
856
856
|
2. For private registries (or any push): set `DOCKER_REGISTRY_USERNAME` and `DOCKER_REGISTRY_TOKEN`
|
|
857
857
|
3. Leave `DOCKER_IMAGE_TAG` unset for a unique tag each build — set it only if you intentionally want a fixed tag
|
|
858
|
-
4. For remote
|
|
858
|
+
4. For a remote Linux host, choose **Remote Linux server via SSH** at init (`remote.mode: "ssh"`) and set `SSH_HOST`, `SSH_USER`, `SSH_KEY_PATH` — DeployHub runs `docker pull`/`run` over node-ssh. Use `DOCKER_HOST` only for the advanced raw CLI transport (`tcp://` or a custom `ssh://` setup you already manage)
|
|
859
859
|
5. Run `deployhub doctor`, then `git push origin main`
|
|
860
860
|
|
|
861
861
|
| Variable | Description | Example | Where to get it |
|
|
@@ -865,7 +865,8 @@ DeployHub detects whether the server uses Debian-style `sites-available` or RHEL
|
|
|
865
865
|
| `DOCKER_REGISTRY_URL` | Registry URL (optional) | `https://ghcr.io` | Registry docs |
|
|
866
866
|
| `DOCKER_REGISTRY_USERNAME` | Registry user | `myuser` | Registry account |
|
|
867
867
|
| `DOCKER_REGISTRY_TOKEN` | Registry password/token | *(secret)* | Docker Hub / GHCR PAT |
|
|
868
|
-
| `DOCKER_HOST` |
|
|
868
|
+
| `DOCKER_HOST` | Advanced raw daemon URI (optional) | `tcp://host:2376` | Only if you manage TLS/ssh:// yourself |
|
|
869
|
+
| `SSH_HOST` / `SSH_USER` / `SSH_KEY_PATH` | Remote Linux via SSH (`remote.mode: ssh`) | `203.0.113.10` / `ubuntu` / `~/.ssh/key.pem` | Same names as EC2 |
|
|
869
870
|
|
|
870
871
|
### AWS EC2
|
|
871
872
|
|
package/package.json
CHANGED
package/src/commands/deploy.js
CHANGED
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
runHealthChecksForEnvs,
|
|
12
12
|
formatHealthCheckAllSummary,
|
|
13
13
|
} from '../utils/health-check.js';
|
|
14
|
+
import {
|
|
15
|
+
runDockerPortPublishChecksForEnvs,
|
|
16
|
+
verifyStageShouldRun,
|
|
17
|
+
} from '../utils/docker-port-publish.js';
|
|
14
18
|
import { createLogger } from '../logger/index.js';
|
|
15
19
|
|
|
16
20
|
/**
|
|
@@ -76,12 +80,28 @@ export function registerDeployCommand(program) {
|
|
|
76
80
|
const deployed = /** @type {string[]} */ (
|
|
77
81
|
ctx.state.deployedTargets || targets
|
|
78
82
|
);
|
|
79
|
-
return
|
|
83
|
+
return verifyStageShouldRun(
|
|
84
|
+
ctx.config,
|
|
85
|
+
deployed,
|
|
86
|
+
anyEnvHasResolvableHealthCheckUrl
|
|
87
|
+
);
|
|
80
88
|
},
|
|
81
89
|
async run(ctx) {
|
|
82
90
|
const deployed = /** @type {string[]} */ (
|
|
83
91
|
ctx.state.deployedTargets || targets
|
|
84
92
|
);
|
|
93
|
+
const portOutcome = await runDockerPortPublishChecksForEnvs(
|
|
94
|
+
ctx.config,
|
|
95
|
+
deployed,
|
|
96
|
+
{ requireRunning: true }
|
|
97
|
+
);
|
|
98
|
+
if (portOutcome.failures.length > 0) {
|
|
99
|
+
throw new Error(portOutcome.failures[0].error);
|
|
100
|
+
}
|
|
101
|
+
for (const r of portOutcome.results) {
|
|
102
|
+
console.log(chalk.green(`Docker port published (${r.envName}): ${r.message}`));
|
|
103
|
+
}
|
|
104
|
+
|
|
85
105
|
const { results, failures } = await runHealthChecksForEnvs(
|
|
86
106
|
ctx.config,
|
|
87
107
|
deployed
|
package/src/commands/doctor.js
CHANGED
|
@@ -31,6 +31,19 @@ import { formatPasswordlessSudoGuidance } from '../utils/nginx.js';
|
|
|
31
31
|
import { checkImagePullability } from '../utils/docker-image-deploy.js';
|
|
32
32
|
import { namespaceExists } from '../utils/kubernetes-namespace.js';
|
|
33
33
|
import { resolvePhpVersion } from '../utils/php-version.js';
|
|
34
|
+
import {
|
|
35
|
+
resolveDockerSshTarget,
|
|
36
|
+
probeRemoteDockerPs,
|
|
37
|
+
formatRemoteDockerSshFailure,
|
|
38
|
+
formatRemoteDockerNotInstalled,
|
|
39
|
+
formatRemoteDockerPermissionDenied,
|
|
40
|
+
formatRemoteDockerDaemonOk,
|
|
41
|
+
} from '../utils/docker-remote.js';
|
|
42
|
+
import { resolveDockerRemoteMode } from '../utils/docker-remote-mode.js';
|
|
43
|
+
import {
|
|
44
|
+
checkEnvDockerPortPublish,
|
|
45
|
+
resolveDockerPublishPort,
|
|
46
|
+
} from '../utils/docker-port-publish.js';
|
|
34
47
|
import {
|
|
35
48
|
buildPhpFpmUnitListCommand,
|
|
36
49
|
formatPhpFpmMissingError,
|
|
@@ -183,14 +196,16 @@ export async function runDeploymentChecks(config, envName, envConfig) {
|
|
|
183
196
|
|
|
184
197
|
/** @type {CheckResult[]} */
|
|
185
198
|
const checks = [];
|
|
186
|
-
const requiredKeys = getDeploymentEnvKeys(deployType, config);
|
|
199
|
+
const requiredKeys = getDeploymentEnvKeys(deployType, config, settings);
|
|
187
200
|
|
|
188
201
|
checks.push(
|
|
189
202
|
await runCheck(`${deployType} env vars`, async () => {
|
|
190
203
|
const missing = requiredKeys.filter((k) => {
|
|
191
204
|
if (k === 'SSH_KEY_PATH') {
|
|
192
|
-
return !process.env.SSH_KEY_PATH && !process.env.SSH_KEY;
|
|
205
|
+
return !process.env.SSH_KEY_PATH && !process.env.SSH_KEY && !settings.keyPath;
|
|
193
206
|
}
|
|
207
|
+
if (k === 'SSH_HOST') return !(settings.host || process.env.SSH_HOST);
|
|
208
|
+
if (k === 'SSH_USER') return !(settings.user || process.env.SSH_USER);
|
|
194
209
|
return !process.env[k];
|
|
195
210
|
});
|
|
196
211
|
if (missing.length > 0) {
|
|
@@ -447,22 +462,155 @@ export async function runDeploymentChecks(config, envName, envConfig) {
|
|
|
447
462
|
}
|
|
448
463
|
|
|
449
464
|
if (deployType === 'docker') {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
465
|
+
const dockerRemoteMode = resolveDockerRemoteMode(settings, process.env);
|
|
466
|
+
|
|
467
|
+
if (dockerRemoteMode === 'ssh') {
|
|
468
|
+
const target = resolveDockerSshTarget(settings, process.env);
|
|
469
|
+
const host = target.host;
|
|
470
|
+
const user = target.user;
|
|
471
|
+
const keyPath = target.keyPath;
|
|
472
|
+
const sshPort = target.sshPort;
|
|
473
|
+
|
|
474
|
+
checks.push(
|
|
475
|
+
await runCheck('SSH key', async () => {
|
|
476
|
+
const result = await validateSshKeyForDoctor(
|
|
477
|
+
keyPath ? String(keyPath) : undefined,
|
|
478
|
+
process.env.SSH_KEY
|
|
479
|
+
);
|
|
458
480
|
return {
|
|
459
|
-
name: '
|
|
481
|
+
name: 'SSH key',
|
|
482
|
+
pass: result.ok,
|
|
483
|
+
message: result.message,
|
|
484
|
+
};
|
|
485
|
+
})
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
checks.push(
|
|
489
|
+
await runCheck('SSH host reachability', async () => {
|
|
490
|
+
if (!host) {
|
|
491
|
+
return {
|
|
492
|
+
name: 'SSH host reachability',
|
|
493
|
+
pass: false,
|
|
494
|
+
message: 'SSH_HOST is required — set it in .env to your server IP or hostname.',
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
const result = await testSshHostReachability(String(host), sshPort);
|
|
498
|
+
return {
|
|
499
|
+
name: 'SSH host reachability',
|
|
500
|
+
pass: result.ok,
|
|
501
|
+
message: result.message,
|
|
502
|
+
};
|
|
503
|
+
})
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
checks.push(
|
|
507
|
+
await runCheck('Remote Docker daemon reachable', async () => {
|
|
508
|
+
const probe = await probeRemoteDockerPs(target);
|
|
509
|
+
if (!probe.sshOk) {
|
|
510
|
+
return {
|
|
511
|
+
name: 'Remote Docker daemon reachable',
|
|
512
|
+
pass: false,
|
|
513
|
+
message: formatRemoteDockerSshFailure(host, user),
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
if (probe.kind === 'not-installed') {
|
|
517
|
+
return {
|
|
518
|
+
name: 'Remote Docker daemon reachable',
|
|
519
|
+
pass: false,
|
|
520
|
+
message: formatRemoteDockerNotInstalled(host, user),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
if (probe.kind === 'ok' || probe.kind === 'permission') {
|
|
524
|
+
return {
|
|
525
|
+
name: 'Remote Docker daemon reachable',
|
|
526
|
+
pass: true,
|
|
527
|
+
message: formatRemoteDockerDaemonOk(host, user),
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
return {
|
|
531
|
+
name: 'Remote Docker daemon reachable',
|
|
460
532
|
pass: false,
|
|
461
|
-
message:
|
|
533
|
+
message:
|
|
534
|
+
probe.detail ||
|
|
535
|
+
`Remote Docker daemon is not reachable (${user}@${host}).`,
|
|
462
536
|
};
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
|
|
537
|
+
})
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
checks.push(
|
|
541
|
+
await runCheck('Remote Docker permission', async () => {
|
|
542
|
+
const probe = await probeRemoteDockerPs(target);
|
|
543
|
+
if (!probe.sshOk) {
|
|
544
|
+
return {
|
|
545
|
+
name: 'Remote Docker permission',
|
|
546
|
+
pass: false,
|
|
547
|
+
message: formatRemoteDockerSshFailure(host, user),
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
if (probe.kind === 'permission') {
|
|
551
|
+
return {
|
|
552
|
+
name: 'Remote Docker permission',
|
|
553
|
+
pass: false,
|
|
554
|
+
message: formatRemoteDockerPermissionDenied(host, user),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
if (probe.kind === 'ok') {
|
|
558
|
+
return {
|
|
559
|
+
name: 'Remote Docker permission',
|
|
560
|
+
pass: true,
|
|
561
|
+
message: `SSH user '${user}' can access the Docker daemon on ${host}`,
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
if (probe.kind === 'not-installed') {
|
|
565
|
+
return {
|
|
566
|
+
name: 'Remote Docker permission',
|
|
567
|
+
pass: false,
|
|
568
|
+
message: formatRemoteDockerNotInstalled(host, user),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
return {
|
|
572
|
+
name: 'Remote Docker permission',
|
|
573
|
+
pass: false,
|
|
574
|
+
message:
|
|
575
|
+
probe.detail ||
|
|
576
|
+
`Cannot verify Docker permissions for '${user}' on ${host}.`,
|
|
577
|
+
};
|
|
578
|
+
})
|
|
579
|
+
);
|
|
580
|
+
} else {
|
|
581
|
+
checks.push(
|
|
582
|
+
await runCheck('Docker daemon', async () => {
|
|
583
|
+
try {
|
|
584
|
+
const provider = getDeploymentProvider('docker', config, envName);
|
|
585
|
+
await provider.testConnection();
|
|
586
|
+
return { name: 'Docker daemon', pass: true, message: 'Docker daemon reachable' };
|
|
587
|
+
} catch (err) {
|
|
588
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
589
|
+
return {
|
|
590
|
+
name: 'Docker daemon',
|
|
591
|
+
pass: false,
|
|
592
|
+
message: `Docker not reachable — ${msg}. Install Docker or set DOCKER_HOST for a remote daemon.`,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
})
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const publishPort = resolveDockerPublishPort(config, settings, envName);
|
|
600
|
+
if (dockerRemoteMode === 'ssh' || publishPort != null) {
|
|
601
|
+
checks.push(
|
|
602
|
+
await runCheck('Docker port published', async () => {
|
|
603
|
+
const outcome = await checkEnvDockerPortPublish(config, envName, {
|
|
604
|
+
requireRunning: false,
|
|
605
|
+
});
|
|
606
|
+
return {
|
|
607
|
+
name: 'Docker port published',
|
|
608
|
+
pass: outcome.pass,
|
|
609
|
+
message: outcome.message,
|
|
610
|
+
};
|
|
611
|
+
})
|
|
612
|
+
);
|
|
613
|
+
}
|
|
466
614
|
}
|
|
467
615
|
|
|
468
616
|
if (deployType === 'kubernetes') {
|
|
@@ -1142,7 +1290,9 @@ export function registerDoctorCommand(program) {
|
|
|
1142
1290
|
const method = getEnvMethod(env);
|
|
1143
1291
|
if (!method) continue;
|
|
1144
1292
|
// Local/.env uses unprefixed names; prefixed CI names are checked separately below.
|
|
1145
|
-
required.push(
|
|
1293
|
+
required.push(
|
|
1294
|
+
...getDeploymentSecretKeys(method, config, getEnvSettings(env))
|
|
1295
|
+
);
|
|
1146
1296
|
}
|
|
1147
1297
|
|
|
1148
1298
|
const unique = [...new Set(required)];
|
|
@@ -1179,7 +1329,7 @@ export function registerDoctorCommand(program) {
|
|
|
1179
1329
|
config,
|
|
1180
1330
|
config.environments
|
|
1181
1331
|
);
|
|
1182
|
-
const baseKeys = getDeploymentSecretKeys(method, config);
|
|
1332
|
+
const baseKeys = getDeploymentSecretKeys(method, config, getEnvSettings(env));
|
|
1183
1333
|
|
|
1184
1334
|
for (let i = 0; i < baseKeys.length; i++) {
|
|
1185
1335
|
const base = baseKeys[i];
|
package/src/commands/env.js
CHANGED
|
@@ -164,6 +164,7 @@ export function registerEnvCommand(program) {
|
|
|
164
164
|
existingEnvNames: Object.keys(config.environments || {}),
|
|
165
165
|
deployType: opts.method,
|
|
166
166
|
nonInteractive: Boolean(opts.yes),
|
|
167
|
+
portDefault: config.port,
|
|
167
168
|
}
|
|
168
169
|
);
|
|
169
170
|
} catch (err) {
|
|
@@ -171,12 +172,21 @@ export function registerEnvCommand(program) {
|
|
|
171
172
|
process.exit(1);
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
// Docker env add must not inherit top-level port via singleConfig — that
|
|
176
|
+
// silently stamps another environment's port onto the new env. Interactive
|
|
177
|
+
// answers carry deployAnswers.port; --yes omits it so SSH deploy/doctor
|
|
178
|
+
// fail with the published-port error instead of a wrong fallback.
|
|
179
|
+
const entrySingleConfig =
|
|
180
|
+
deployAnswers.deployType === 'docker'
|
|
181
|
+
? { framework: singleConfig?.framework, port: deployAnswers.port }
|
|
182
|
+
: singleConfig;
|
|
183
|
+
|
|
174
184
|
config.environments[name] = buildServerEnvEntry(
|
|
175
185
|
deployAnswers,
|
|
176
186
|
projectType,
|
|
177
187
|
config.project,
|
|
178
188
|
backendConfig,
|
|
179
|
-
|
|
189
|
+
entrySingleConfig
|
|
180
190
|
);
|
|
181
191
|
|
|
182
192
|
if (!config.defaultEnvironment) {
|
package/src/commands/init.js
CHANGED
|
@@ -382,6 +382,7 @@ export function registerInitCommand(program) {
|
|
|
382
382
|
{
|
|
383
383
|
...(opts.envName ? { envName: opts.envName } : {}),
|
|
384
384
|
existingEnvNames: Object.keys(environments),
|
|
385
|
+
portDefault: singleConfig?.port ?? backendConfig?.port,
|
|
385
386
|
}
|
|
386
387
|
);
|
|
387
388
|
primaryDeployType = deployAnswers.deployType;
|
package/src/commands/verify.js
CHANGED
|
@@ -7,6 +7,10 @@ import {
|
|
|
7
7
|
runHealthChecksForEnvs,
|
|
8
8
|
formatHealthCheckAllSummary,
|
|
9
9
|
} from '../utils/health-check.js';
|
|
10
|
+
import {
|
|
11
|
+
runDockerPortPublishChecksForEnvs,
|
|
12
|
+
verifyStageShouldRun,
|
|
13
|
+
} from '../utils/docker-port-publish.js';
|
|
10
14
|
|
|
11
15
|
/**
|
|
12
16
|
* Standalone verify — same per-env URL resolution and summary as the deploy pipeline stage.
|
|
@@ -31,7 +35,7 @@ export async function runVerify(config, envFlag, options = {}) {
|
|
|
31
35
|
};
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
if (!
|
|
38
|
+
if (!verifyStageShouldRun(config, targets, anyEnvHasResolvableHealthCheckUrl)) {
|
|
35
39
|
const label =
|
|
36
40
|
targets.length === 1
|
|
37
41
|
? `environment "${targets[0]}"`
|
|
@@ -47,28 +51,47 @@ export async function runVerify(config, envFlag, options = {}) {
|
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
const portOutcome = await runDockerPortPublishChecksForEnvs(config, targets, {
|
|
55
|
+
requireRunning: true,
|
|
56
|
+
...options,
|
|
57
|
+
});
|
|
58
|
+
|
|
50
59
|
const { results, failures } = await runHealthChecksForEnvs(config, targets, options);
|
|
51
|
-
const
|
|
60
|
+
const allFailures = [
|
|
61
|
+
...portOutcome.failures.map((f) => ({ envName: f.envName, url: '', error: f.error })),
|
|
62
|
+
...failures,
|
|
63
|
+
];
|
|
64
|
+
const portResults = portOutcome.results.map((r) => ({
|
|
65
|
+
envName: r.envName,
|
|
66
|
+
url: '',
|
|
67
|
+
status: 0,
|
|
68
|
+
elapsed: 0,
|
|
69
|
+
message: r.message,
|
|
70
|
+
}));
|
|
71
|
+
const mergedResults = [...portResults, ...results];
|
|
72
|
+
const multi = targets.length > 1 || allFailures.length > 0;
|
|
52
73
|
const summary = multi ? formatHealthCheckAllSummary(results, failures) : '';
|
|
53
74
|
|
|
54
75
|
let message = '';
|
|
55
|
-
if (
|
|
76
|
+
if (allFailures.length === 0 && results.length === 1 && portOutcome.results.length === 0 && !multi) {
|
|
56
77
|
const r = results[0];
|
|
57
78
|
message = `Health check passed (${r.envName}): HTTP ${r.status} (${r.elapsed}ms)`;
|
|
58
|
-
} else if (
|
|
79
|
+
} else if (allFailures.length === 0 && results.length === 0 && portOutcome.results.length === 1) {
|
|
80
|
+
message = portOutcome.results[0].message;
|
|
81
|
+
} else if (allFailures.length === 0 && results.length === 0 && portOutcome.results.length === 0) {
|
|
59
82
|
message = `No health check URL configured for the selected environment(s).`;
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
return {
|
|
63
|
-
ok:
|
|
86
|
+
ok: allFailures.length === 0 && (results.length > 0 || portOutcome.results.length > 0),
|
|
64
87
|
targets,
|
|
65
|
-
results,
|
|
66
|
-
failures,
|
|
88
|
+
results: mergedResults,
|
|
89
|
+
failures: allFailures,
|
|
67
90
|
summary,
|
|
68
91
|
message:
|
|
69
92
|
message ||
|
|
70
|
-
(
|
|
71
|
-
?
|
|
93
|
+
(allFailures.length > 0
|
|
94
|
+
? allFailures[0].error
|
|
72
95
|
: `All ${results.length} environment(s) passed health checks.`),
|
|
73
96
|
skippedDisabled,
|
|
74
97
|
};
|
package/src/core/config.js
CHANGED
|
@@ -54,6 +54,16 @@ const MethodConfigSchema = z
|
|
|
54
54
|
dockerImageName: z.string().optional(),
|
|
55
55
|
dockerRegistryUrl: z.string().optional(),
|
|
56
56
|
dockerHost: z.string().optional(),
|
|
57
|
+
/**
|
|
58
|
+
* Docker-method only. Kubernetes must ignore this — it deploys via kubectl,
|
|
59
|
+
* never a remote Docker daemon. `ssh` = node-ssh + remote docker CLI;
|
|
60
|
+
* `local` = this machine; `raw` = unmanaged DOCKER_HOST (ssh:// or tcp://).
|
|
61
|
+
*/
|
|
62
|
+
remote: z
|
|
63
|
+
.object({
|
|
64
|
+
mode: z.enum(['ssh', 'local', 'raw']),
|
|
65
|
+
})
|
|
66
|
+
.optional(),
|
|
57
67
|
healthCheckUrl: z.string().optional(),
|
|
58
68
|
appName: z.string().optional(),
|
|
59
69
|
framework: z.string().optional(),
|
package/src/core/environments.js
CHANGED
|
@@ -266,6 +266,22 @@ export function resolveEnvTargets(config, envFlag) {
|
|
|
266
266
|
return { targets: [envFlag], skippedDisabled: [] };
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Whitelist of method-config fields copied onto process env for Docker and
|
|
271
|
+
* Kubernetes providers. Structural (not a per-method if): only these keys are
|
|
272
|
+
* ever copied. `remote` / `host` / `user` / `keyPath` are intentionally absent
|
|
273
|
+
* — docker SSH identity is read by docker.js from settings + SSH_* env vars.
|
|
274
|
+
* Kubernetes uses this same helper and therefore cannot observe remote.mode.
|
|
275
|
+
*/
|
|
276
|
+
export const METHOD_SETTINGS_ENV_OVERLAY = Object.freeze({
|
|
277
|
+
dockerImageName: 'DOCKER_IMAGE_NAME',
|
|
278
|
+
dockerRegistryUrl: 'DOCKER_REGISTRY_URL',
|
|
279
|
+
dockerHost: 'DOCKER_HOST',
|
|
280
|
+
kubeNamespace: 'KUBE_NAMESPACE',
|
|
281
|
+
kubeconfig: 'KUBECONFIG',
|
|
282
|
+
kubeContext: 'KUBE_CONTEXT',
|
|
283
|
+
});
|
|
284
|
+
|
|
269
285
|
/**
|
|
270
286
|
* Overlay method-specific config onto process env for Docker/K8s providers.
|
|
271
287
|
* Secrets still come from real env vars; non-secret names/paths come from config.
|
|
@@ -277,12 +293,10 @@ export function resolveEnvTargets(config, envFlag) {
|
|
|
277
293
|
export function mergeMethodSettingsIntoEnv(env, settings) {
|
|
278
294
|
/** @type {Record<string, string|undefined>} */
|
|
279
295
|
const out = { ...env };
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (settings.kubeconfig) out.KUBECONFIG = String(settings.kubeconfig);
|
|
285
|
-
if (settings.kubeContext) out.KUBE_CONTEXT = String(settings.kubeContext);
|
|
296
|
+
for (const [settingKey, envKey] of Object.entries(METHOD_SETTINGS_ENV_OVERLAY)) {
|
|
297
|
+
const value = settings[settingKey];
|
|
298
|
+
if (value) out[envKey] = String(value);
|
|
299
|
+
}
|
|
286
300
|
return out;
|
|
287
301
|
}
|
|
288
302
|
|
|
@@ -302,4 +316,5 @@ export default {
|
|
|
302
316
|
buildEnvironmentEntry,
|
|
303
317
|
resolveEnvTargets,
|
|
304
318
|
mergeMethodSettingsIntoEnv,
|
|
319
|
+
METHOD_SETTINGS_ENV_OVERLAY,
|
|
305
320
|
};
|
package/src/core/stages.js
CHANGED
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
anyEnvHasResolvableHealthCheckUrl,
|
|
9
9
|
runHealthChecksForEnvs,
|
|
10
10
|
} from '../utils/health-check.js';
|
|
11
|
+
import {
|
|
12
|
+
anyDockerEnvHasPublishPort,
|
|
13
|
+
runDockerPortPublishChecksForEnvs,
|
|
14
|
+
verifyStageShouldRun,
|
|
15
|
+
} from '../utils/docker-port-publish.js';
|
|
11
16
|
import { getProjectVersion } from '../utils/version.js';
|
|
12
17
|
import { resolveBuildId } from '../utils/build-id.js';
|
|
13
18
|
import { ensureDeployScaffold } from '../utils/scaffold.js';
|
|
@@ -224,12 +229,25 @@ export function buildPipelineStages(config, cwd, state) {
|
|
|
224
229
|
{
|
|
225
230
|
name: 'verify',
|
|
226
231
|
enabled: (ctx) => {
|
|
227
|
-
if (ctx.config.pipeline.verify !== true)
|
|
232
|
+
if (ctx.config.pipeline.verify !== true) {
|
|
233
|
+
const deployed = /** @type {string[]} */ (ctx.state.deployedTargets || []);
|
|
234
|
+
return anyDockerEnvHasPublishPort(ctx.config, deployed);
|
|
235
|
+
}
|
|
228
236
|
const deployed = /** @type {string[]} */ (ctx.state.deployedTargets || []);
|
|
229
|
-
return
|
|
237
|
+
return verifyStageShouldRun(
|
|
238
|
+
ctx.config,
|
|
239
|
+
deployed,
|
|
240
|
+
anyEnvHasResolvableHealthCheckUrl
|
|
241
|
+
);
|
|
230
242
|
},
|
|
231
243
|
async run(ctx) {
|
|
232
244
|
const deployed = /** @type {string[]} */ (ctx.state.deployedTargets || []);
|
|
245
|
+
const portOutcome = await runDockerPortPublishChecksForEnvs(ctx.config, deployed, {
|
|
246
|
+
requireRunning: true,
|
|
247
|
+
});
|
|
248
|
+
if (portOutcome.failures.length > 0) {
|
|
249
|
+
throw new Error(portOutcome.failures[0].error);
|
|
250
|
+
}
|
|
233
251
|
const { results, failures } = await runHealthChecksForEnvs(ctx.config, deployed);
|
|
234
252
|
if (failures.length > 0) {
|
|
235
253
|
throw new Error(failures[0].error);
|