@akash-chowdhury-24/deployhub 2.0.33 → 2.0.34
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/package.json
CHANGED
|
@@ -15,6 +15,57 @@ import {
|
|
|
15
15
|
EXPLICIT_IMAGE_TAG_WARNING,
|
|
16
16
|
} from './docker-image.js';
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Classify `docker pull` failure for rollback logs / interpreted-backend errors.
|
|
20
|
+
* @param {unknown} err
|
|
21
|
+
* @returns {'not found'|'auth failed'|'network error'|string}
|
|
22
|
+
*/
|
|
23
|
+
export function classifyDockerPullFailure(err) {
|
|
24
|
+
const execErr = /** @type {{ message?: string, stderr?: string, stdout?: string }} */ (err);
|
|
25
|
+
const combined = `${execErr?.message || ''} ${execErr?.stderr || ''} ${execErr?.stdout || ''}`.toLowerCase();
|
|
26
|
+
// Docker Hub reports missing repos as "denied" — check existence first.
|
|
27
|
+
if (
|
|
28
|
+
combined.includes('manifest unknown') ||
|
|
29
|
+
combined.includes('not found') ||
|
|
30
|
+
combined.includes('repository does not exist') ||
|
|
31
|
+
combined.includes('no such image')
|
|
32
|
+
) {
|
|
33
|
+
return 'not found';
|
|
34
|
+
}
|
|
35
|
+
if (
|
|
36
|
+
combined.includes('unauthorized') ||
|
|
37
|
+
combined.includes('authentication required') ||
|
|
38
|
+
combined.includes('access denied') ||
|
|
39
|
+
combined.includes('denied: requested access')
|
|
40
|
+
) {
|
|
41
|
+
return 'auth failed';
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
combined.includes('network') ||
|
|
45
|
+
combined.includes('timeout') ||
|
|
46
|
+
combined.includes('timed out') ||
|
|
47
|
+
combined.includes('econnrefused') ||
|
|
48
|
+
combined.includes('connection refused') ||
|
|
49
|
+
combined.includes('no such host') ||
|
|
50
|
+
combined.includes('dial tcp')
|
|
51
|
+
) {
|
|
52
|
+
return 'network error';
|
|
53
|
+
}
|
|
54
|
+
const stderr = String(execErr?.stderr || '').trim().split('\n').filter(Boolean).pop();
|
|
55
|
+
return stderr || (err instanceof Error ? err.message : 'pull failed');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} imageRef
|
|
60
|
+
* @param {string} reason
|
|
61
|
+
*/
|
|
62
|
+
export function formatImageNotLocalAndPullFailed(imageRef, reason) {
|
|
63
|
+
return (
|
|
64
|
+
`Target image ${imageRef} not found locally and could not be pulled ` +
|
|
65
|
+
`from the registry (${reason}).`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
18
69
|
/**
|
|
19
70
|
* Shared Docker image build, reuse, push, and pullability logic used by
|
|
20
71
|
* docker and kubernetes deploy providers.
|
|
@@ -105,6 +156,41 @@ export function createDockerImageDeployContext(config, env = process.env, log) {
|
|
|
105
156
|
}
|
|
106
157
|
}
|
|
107
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Rollback (and any skipImageReuse path): after a local-cache miss, pull the
|
|
161
|
+
* exact tag from the registry before falling through to artifact rebuild.
|
|
162
|
+
* @param {string} ref
|
|
163
|
+
* @returns {Promise<{ ok: true, output: string }|{ ok: false, reason: string, output: string }>}
|
|
164
|
+
*/
|
|
165
|
+
async function tryPullImage(ref) {
|
|
166
|
+
log.info(`Pulling ${ref} from registry...`);
|
|
167
|
+
try {
|
|
168
|
+
const pulled = await execa('docker', ['pull', ref], {
|
|
169
|
+
stdio: 'pipe',
|
|
170
|
+
env: getDockerEnv(),
|
|
171
|
+
});
|
|
172
|
+
const output = [pulled.stdout, pulled.stderr].filter(Boolean).join('\n').trim();
|
|
173
|
+
if (output) log.info(output);
|
|
174
|
+
if (await imageExistsLocally(ref)) {
|
|
175
|
+
log.success(`Pulled ${ref}`);
|
|
176
|
+
return { ok: true, output };
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
reason: 'pull reported success but image is still missing locally',
|
|
181
|
+
output,
|
|
182
|
+
};
|
|
183
|
+
} catch (err) {
|
|
184
|
+
const execErr = /** @type {{ stdout?: string, stderr?: string }} */ (err);
|
|
185
|
+
const output = [execErr.stdout, execErr.stderr]
|
|
186
|
+
.filter(Boolean)
|
|
187
|
+
.join('\n')
|
|
188
|
+
.trim();
|
|
189
|
+
if (output) log.info(output);
|
|
190
|
+
return { ok: false, reason: classifyDockerPullFailure(err), output };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
108
194
|
/**
|
|
109
195
|
* Prefer the image already built during the pipeline `docker` stage.
|
|
110
196
|
* Retag when the pipeline used `:latest` and deploy needs a version tag.
|
|
@@ -138,8 +224,15 @@ export function createDockerImageDeployContext(config, env = process.env, log) {
|
|
|
138
224
|
* @param {Record<string, unknown>} metadata
|
|
139
225
|
* @param {string} framework
|
|
140
226
|
* @param {number} port
|
|
227
|
+
* @param {string} [imageRef]
|
|
141
228
|
*/
|
|
142
|
-
async function prepareBackendBuildContext(
|
|
229
|
+
async function prepareBackendBuildContext(
|
|
230
|
+
buildContext,
|
|
231
|
+
metadata,
|
|
232
|
+
framework,
|
|
233
|
+
port,
|
|
234
|
+
imageRef = fullImage
|
|
235
|
+
) {
|
|
143
236
|
if (framework === 'spring') {
|
|
144
237
|
const targetDir = path.join(buildContext, 'target');
|
|
145
238
|
if (await fs.pathExists(targetDir)) {
|
|
@@ -199,7 +292,7 @@ export function createDockerImageDeployContext(config, env = process.env, log) {
|
|
|
199
292
|
if (isInterpretedBackendFramework(framework)) {
|
|
200
293
|
const gap = describeInterpretedBackendGap(framework);
|
|
201
294
|
throw new Error(
|
|
202
|
-
`Cannot rebuild ${gap.ecosystem} backend image "${
|
|
295
|
+
`Cannot rebuild ${gap.ecosystem} backend image "${imageRef}" from the packaged artifact.\n` +
|
|
203
296
|
`Backend artifacts include source/manifests but not ${gap.missing}, ` +
|
|
204
297
|
`so Dockerfiles that run \`${gap.installCmd}\` cannot reliably succeed from the artifact alone.\n\n` +
|
|
205
298
|
'What to do instead:\n' +
|
|
@@ -286,7 +379,13 @@ export function createDockerImageDeployContext(config, env = process.env, log) {
|
|
|
286
379
|
generateFrontendRuntimeDockerfile(buildOutput)
|
|
287
380
|
);
|
|
288
381
|
} else {
|
|
289
|
-
await prepareBackendBuildContext(
|
|
382
|
+
await prepareBackendBuildContext(
|
|
383
|
+
buildContext,
|
|
384
|
+
metadata,
|
|
385
|
+
framework,
|
|
386
|
+
port,
|
|
387
|
+
imageRef
|
|
388
|
+
);
|
|
290
389
|
}
|
|
291
390
|
|
|
292
391
|
await execa('docker', ['build', '-t', imageRef, '.'], {
|
|
@@ -322,6 +421,8 @@ export function createDockerImageDeployContext(config, env = process.env, log) {
|
|
|
322
421
|
await dockerLogin();
|
|
323
422
|
|
|
324
423
|
let reused = false;
|
|
424
|
+
/** @type {string|null} */
|
|
425
|
+
let registryPullFailure = null;
|
|
325
426
|
if (!options.skipImageReuse) {
|
|
326
427
|
// Normal deploy: prefer pipeline image (exact tag, then :latest retag).
|
|
327
428
|
reused = await ensureImageFromPipeline(imageRef);
|
|
@@ -331,16 +432,32 @@ export function createDockerImageDeployContext(config, env = process.env, log) {
|
|
|
331
432
|
log.info(`Using restored image ${imageRef} (skipImageReuse — no :latest retag)`);
|
|
332
433
|
reused = true;
|
|
333
434
|
} else {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
435
|
+
const pulled = await tryPullImage(imageRef);
|
|
436
|
+
if (pulled.ok) {
|
|
437
|
+
reused = true;
|
|
438
|
+
} else {
|
|
439
|
+
registryPullFailure = pulled.reason;
|
|
440
|
+
log.info(
|
|
441
|
+
`${formatImageNotLocalAndPullFailed(imageRef, pulled.reason)} — attempting rebuild from artifact`
|
|
442
|
+
);
|
|
443
|
+
}
|
|
337
444
|
}
|
|
338
445
|
|
|
339
446
|
let ranCompose = false;
|
|
340
447
|
|
|
341
448
|
if (!reused) {
|
|
342
|
-
|
|
343
|
-
|
|
449
|
+
try {
|
|
450
|
+
const result = await buildFromArtifactContents(artifactDir, imageRef);
|
|
451
|
+
ranCompose = Boolean(result?.ranCompose);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
if (registryPullFailure) {
|
|
454
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
455
|
+
throw new Error(
|
|
456
|
+
`${formatImageNotLocalAndPullFailed(imageRef, registryPullFailure)}\n${detail}`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
throw err;
|
|
460
|
+
}
|
|
344
461
|
}
|
|
345
462
|
|
|
346
463
|
if (ranCompose) {
|
|
@@ -372,6 +489,7 @@ export function createDockerImageDeployContext(config, env = process.env, log) {
|
|
|
372
489
|
dockerLogin,
|
|
373
490
|
maybePushImage,
|
|
374
491
|
ensureImageFromPipeline,
|
|
492
|
+
tryPullImage,
|
|
375
493
|
buildFromArtifactContents,
|
|
376
494
|
ensureImageReadyForDeploy,
|
|
377
495
|
};
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
getEnvMethod,
|
|
12
12
|
resolveDefaultEnvironmentName,
|
|
13
13
|
} from '../../core/environments.js';
|
|
14
|
+
import {
|
|
15
|
+
runDockerPortPublishChecksForEnvs,
|
|
16
|
+
} from '../docker-port-publish.js';
|
|
14
17
|
import fs from 'fs-extra';
|
|
15
18
|
import path from 'path';
|
|
16
19
|
|
|
@@ -32,6 +35,19 @@ async function rollbackTarget(config, artifactDir, envName, meta) {
|
|
|
32
35
|
|
|
33
36
|
const provider = getDeploymentProvider(method, config, envName);
|
|
34
37
|
await provider.rollback(artifactDir, meta);
|
|
38
|
+
|
|
39
|
+
// Same post-deploy port-publish check as the deploy pipeline verify stage
|
|
40
|
+
// (`runDockerPortPublishChecksForEnvs`). SSH/EC2/VM skip; docker envs fail
|
|
41
|
+
// if the restored container is up without 0.0.0.0:<port>->.
|
|
42
|
+
const portOutcome = await runDockerPortPublishChecksForEnvs(config, [envName], {
|
|
43
|
+
requireRunning: true,
|
|
44
|
+
});
|
|
45
|
+
if (portOutcome.failures.length > 0) {
|
|
46
|
+
throw new Error(portOutcome.failures[0].error);
|
|
47
|
+
}
|
|
48
|
+
for (const r of portOutcome.results) {
|
|
49
|
+
log.success(r.message);
|
|
50
|
+
}
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
/**
|