@akash-chowdhury-24/deployhub 2.0.13 → 2.0.15
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 +53 -11
- package/package.json +1 -1
- package/src/cli/index.js +4 -0
- package/src/commands/artifact.js +17 -9
- package/src/commands/doctor.js +19 -2
- package/src/commands/init.js +1 -0
- package/src/commands/sync-k8s-ports.js +71 -0
- package/src/commands/sync-workflows.js +43 -0
- package/src/deployment/index.js +3 -2
- package/src/deployment/providers/azure-vm.js +39 -18
- package/src/deployment/providers/docker.js +29 -7
- package/src/deployment/providers/ec2.js +39 -18
- package/src/deployment/providers/gcp-vm.js +39 -18
- package/src/deployment/providers/kubernetes.js +67 -12
- package/src/deployment/providers/ssh.js +1 -1
- package/src/storage/index.js +34 -10
- package/src/storage/providers/aws.js +6 -2
- package/src/storage/providers/dropbox.js +8 -2
- package/src/storage/providers/ftp.js +8 -2
- package/src/storage/storage-errors.js +106 -0
- package/src/utils/docker-image-deploy.js +44 -21
- package/src/utils/docker-image.js +48 -9
- package/src/utils/dockerfile-expose.js +117 -0
- package/src/utils/github-actions.js +175 -8
- package/src/utils/kubernetes-manifests.js +102 -9
- package/src/utils/rollback/engine.js +14 -12
- package/src/utils/scaffold.js +6 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { resolveContainerPort } from './dockerfile-expose.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @param {string} name
|
|
@@ -65,6 +66,11 @@ export function generateKubernetesManifests({
|
|
|
65
66
|
? ` imagePullSecrets:\n - name: ${imagePullSecret}\n`
|
|
66
67
|
: '';
|
|
67
68
|
|
|
69
|
+
// Service port stays 80 (Ingress-friendly cluster-facing port).
|
|
70
|
+
// containerPort / targetPort must match the container's listening port (EXPOSE).
|
|
71
|
+
const servicePort = 80;
|
|
72
|
+
const targetPort = port;
|
|
73
|
+
|
|
68
74
|
const deploymentYaml = `apiVersion: apps/v1
|
|
69
75
|
kind: Deployment
|
|
70
76
|
metadata:
|
|
@@ -96,9 +102,6 @@ ${pullSecretBlock} containers:
|
|
|
96
102
|
cpu: "500m"
|
|
97
103
|
`;
|
|
98
104
|
|
|
99
|
-
const servicePort = port === 80 ? 80 : 80;
|
|
100
|
-
const targetPort = port;
|
|
101
|
-
|
|
102
105
|
const serviceYaml = `apiVersion: v1
|
|
103
106
|
kind: Service
|
|
104
107
|
metadata:
|
|
@@ -116,12 +119,29 @@ spec:
|
|
|
116
119
|
return { deploymentYaml, serviceYaml };
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Surgically update only containerPort / targetPort in manifest YAML.
|
|
124
|
+
* Does not change Service `port:`, replicas, resources, env, probes, etc.
|
|
125
|
+
*
|
|
126
|
+
* @param {string} yaml
|
|
127
|
+
* @param {number} port
|
|
128
|
+
* @returns {{ content: string, changed: boolean }}
|
|
129
|
+
*/
|
|
130
|
+
export function patchKubernetesManifestPorts(yaml, port) {
|
|
131
|
+
const next = String(yaml)
|
|
132
|
+
.replace(/^([ \t]*-?[ \t]*containerPort:[ \t]*)\d+[ \t]*$/gm, `$1${port}`)
|
|
133
|
+
.replace(/^([ \t]*targetPort:[ \t]*)\d+[ \t]*$/gm, `$1${port}`);
|
|
134
|
+
|
|
135
|
+
return { content: next, changed: next !== yaml };
|
|
136
|
+
}
|
|
137
|
+
|
|
119
138
|
/**
|
|
120
139
|
* @param {import('../core/config.js').DeployHubConfig} config
|
|
121
140
|
* @param {Record<string, Record<string, unknown>>} [environments]
|
|
141
|
+
* @param {{ port?: number }} [options]
|
|
122
142
|
* @returns {{ appName: string, imageName: string, imageTag: string, port: number, namespace: string, imagePullSecret: string }}
|
|
123
143
|
*/
|
|
124
|
-
export function resolveKubernetesManifestOptions(config, environments = {}) {
|
|
144
|
+
export function resolveKubernetesManifestOptions(config, environments = {}, options = {}) {
|
|
125
145
|
const envList = Object.values(environments);
|
|
126
146
|
const k8sEnv = envList.find((env) => env.type === 'kubernetes') || {};
|
|
127
147
|
|
|
@@ -142,13 +162,18 @@ export function resolveKubernetesManifestOptions(config, environments = {}) {
|
|
|
142
162
|
process.env.KUBE_IMAGE_PULL_SECRET ||
|
|
143
163
|
'';
|
|
144
164
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
/** @type {number} */
|
|
166
|
+
let port;
|
|
167
|
+
if (typeof options.port === 'number' && Number.isFinite(options.port)) {
|
|
168
|
+
port = options.port;
|
|
169
|
+
} else if (config.projectType === 'both' && config.backend?.port) {
|
|
170
|
+
port = Number(config.backend.port);
|
|
148
171
|
} else if (config.port) {
|
|
149
|
-
port = config.port;
|
|
172
|
+
port = Number(config.port);
|
|
150
173
|
} else if (config.backend?.port) {
|
|
151
|
-
port = config.backend.port;
|
|
174
|
+
port = Number(config.backend.port);
|
|
175
|
+
} else {
|
|
176
|
+
port = 3000;
|
|
152
177
|
}
|
|
153
178
|
|
|
154
179
|
return {
|
|
@@ -161,9 +186,77 @@ export function resolveKubernetesManifestOptions(config, environments = {}) {
|
|
|
161
186
|
};
|
|
162
187
|
}
|
|
163
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Resolve manifest options with Dockerfile EXPOSE → config → fallback port.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} cwd
|
|
193
|
+
* @param {import('../core/config.js').DeployHubConfig} config
|
|
194
|
+
* @param {Record<string, Record<string, unknown>>} [environments]
|
|
195
|
+
*/
|
|
196
|
+
export async function resolveKubernetesManifestOptionsFromCwd(
|
|
197
|
+
cwd,
|
|
198
|
+
config,
|
|
199
|
+
environments = {}
|
|
200
|
+
) {
|
|
201
|
+
const { port, source } = await resolveContainerPort(cwd, config);
|
|
202
|
+
return {
|
|
203
|
+
...resolveKubernetesManifestOptions(config, environments, { port }),
|
|
204
|
+
portSource: source,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Paths DeployHub normally writes for Kubernetes starter manifests.
|
|
210
|
+
* @param {string} cwd
|
|
211
|
+
* @returns {{ deploymentPath: string, servicePath: string }}
|
|
212
|
+
*/
|
|
213
|
+
export function getDefaultKubernetesManifestPaths(cwd) {
|
|
214
|
+
const k8sDir = path.join(cwd, 'k8s');
|
|
215
|
+
return {
|
|
216
|
+
deploymentPath: path.join(k8sDir, 'deployment.yaml'),
|
|
217
|
+
servicePath: path.join(k8sDir, 'service.yaml'),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Patch containerPort/targetPort in existing k8s/deployment.yaml + service.yaml only.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} cwd
|
|
225
|
+
* @param {number} port
|
|
226
|
+
* @returns {Promise<{ patched: string[], skipped: string[], port: number }>}
|
|
227
|
+
*/
|
|
228
|
+
export async function syncKubernetesManifestPorts(cwd, port) {
|
|
229
|
+
const { deploymentPath, servicePath } = getDefaultKubernetesManifestPaths(cwd);
|
|
230
|
+
/** @type {string[]} */
|
|
231
|
+
const patched = [];
|
|
232
|
+
/** @type {string[]} */
|
|
233
|
+
const skipped = [];
|
|
234
|
+
|
|
235
|
+
for (const filePath of [deploymentPath, servicePath]) {
|
|
236
|
+
if (!(await fs.pathExists(filePath))) {
|
|
237
|
+
skipped.push(path.relative(cwd, filePath));
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const original = await fs.readFile(filePath, 'utf8');
|
|
241
|
+
const { content, changed } = patchKubernetesManifestPorts(original, port);
|
|
242
|
+
if (changed) {
|
|
243
|
+
await fs.writeFile(filePath, content);
|
|
244
|
+
patched.push(path.relative(cwd, filePath));
|
|
245
|
+
} else {
|
|
246
|
+
skipped.push(path.relative(cwd, filePath));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { patched, skipped, port };
|
|
251
|
+
}
|
|
252
|
+
|
|
164
253
|
export default {
|
|
165
254
|
sanitizeK8sName,
|
|
166
255
|
hasKubernetesManifests,
|
|
167
256
|
generateKubernetesManifests,
|
|
257
|
+
patchKubernetesManifestPorts,
|
|
168
258
|
resolveKubernetesManifestOptions,
|
|
259
|
+
resolveKubernetesManifestOptionsFromCwd,
|
|
260
|
+
getDefaultKubernetesManifestPaths,
|
|
261
|
+
syncKubernetesManifestPorts,
|
|
169
262
|
};
|
|
@@ -13,8 +13,9 @@ import path from 'path';
|
|
|
13
13
|
* @param {import('../../core/config.js').DeployHubConfig} config
|
|
14
14
|
* @param {string} artifactDir
|
|
15
15
|
* @param {string} envName
|
|
16
|
+
* @param {{ buildId: string, semver: string, remoteKey: string }} meta
|
|
16
17
|
*/
|
|
17
|
-
async function rollbackTarget(config, artifactDir, envName) {
|
|
18
|
+
async function rollbackTarget(config, artifactDir, envName, meta) {
|
|
18
19
|
const envConfig = config.environments[envName];
|
|
19
20
|
if (!envConfig) {
|
|
20
21
|
throw new Error(`Environment "${envName}" not found in config`);
|
|
@@ -24,7 +25,7 @@ async function rollbackTarget(config, artifactDir, envName) {
|
|
|
24
25
|
log.info(`Rolling back ${envName} (${envConfig.type || 'server'})...`);
|
|
25
26
|
|
|
26
27
|
const provider = getDeploymentProvider(envConfig.type, config, envName);
|
|
27
|
-
await provider.rollback(artifactDir);
|
|
28
|
+
await provider.rollback(artifactDir, meta);
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/**
|
|
@@ -39,7 +40,7 @@ export async function rollbackToVersion(config, versionOrBuildId, cwd = process.
|
|
|
39
40
|
throw new Error('No storage providers configured — cannot download artifact for rollback');
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
const history = await loadArtifactHistory(providers, config.project);
|
|
43
|
+
const { entries: history } = await loadArtifactHistory(providers, config.project);
|
|
43
44
|
const resolved = resolveRollbackTarget(history, versionOrBuildId);
|
|
44
45
|
|
|
45
46
|
if (!resolved.ok) {
|
|
@@ -62,15 +63,10 @@ export async function rollbackToVersion(config, versionOrBuildId, cwd = process.
|
|
|
62
63
|
const zipPath = path.join(artifactDir, 'artifact.zip');
|
|
63
64
|
await downloadArtifactEntry(providers, config, entry, zipPath);
|
|
64
65
|
|
|
66
|
+
// Extract into artifactDir itself so layout matches a normal build artifact:
|
|
67
|
+
// top-level contents (k8s/, dist/, metadata.json, …) alongside artifact.zip.
|
|
65
68
|
log.info('Extracting artifact for rollback...');
|
|
66
|
-
|
|
67
|
-
await fs.emptyDir(extractedDir);
|
|
68
|
-
await extractArtifact(artifactDir, extractedDir);
|
|
69
|
-
|
|
70
|
-
const extractedDeployment = path.join(extractedDir, 'deployment.json');
|
|
71
|
-
if (await fs.pathExists(extractedDeployment)) {
|
|
72
|
-
await fs.copy(extractedDeployment, path.join(artifactDir, 'deployment.json'));
|
|
73
|
-
}
|
|
69
|
+
await extractArtifact(artifactDir, artifactDir);
|
|
74
70
|
|
|
75
71
|
const targets = config.deploy || [];
|
|
76
72
|
if (targets.length === 0) {
|
|
@@ -78,9 +74,15 @@ export async function rollbackToVersion(config, versionOrBuildId, cwd = process.
|
|
|
78
74
|
return { artifactDir, entry };
|
|
79
75
|
}
|
|
80
76
|
|
|
77
|
+
const meta = {
|
|
78
|
+
buildId: entry.buildId,
|
|
79
|
+
semver: entry.semver,
|
|
80
|
+
remoteKey: entry.remoteKey,
|
|
81
|
+
};
|
|
82
|
+
|
|
81
83
|
log.info('Redeploying previous artifact to server targets...');
|
|
82
84
|
for (const envName of targets) {
|
|
83
|
-
await rollbackTarget(config, artifactDir, envName);
|
|
85
|
+
await rollbackTarget(config, artifactDir, envName, meta);
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
log.success(`Rollback to buildId=${entry.buildId} complete`);
|
package/src/utils/scaffold.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
generateKubernetesManifests,
|
|
12
12
|
hasKubernetesManifests,
|
|
13
|
-
|
|
13
|
+
resolveKubernetesManifestOptionsFromCwd,
|
|
14
14
|
} from './kubernetes-manifests.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -134,7 +134,11 @@ export async function ensureKubernetesManifests(
|
|
|
134
134
|
return { generated: false };
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
const manifestOptions =
|
|
137
|
+
const manifestOptions = await resolveKubernetesManifestOptionsFromCwd(
|
|
138
|
+
cwd,
|
|
139
|
+
config,
|
|
140
|
+
environments
|
|
141
|
+
);
|
|
138
142
|
const { deploymentYaml, serviceYaml } = generateKubernetesManifests(manifestOptions);
|
|
139
143
|
|
|
140
144
|
const k8sDir = path.join(cwd, 'k8s');
|