yj-deploy 0.0.8 → 0.0.10
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 +24 -1
- package/dist/yj-deploy.mjs +172 -101
- package/dist/yj-deploy.umd.js +8 -8
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
- ✔ 支持多种方式配置 `镜像仓库` `镜像环境`等参数
|
|
13
13
|
- ✔ 支持懒上传,仅上传新增部分文件,提升上传效率
|
|
14
14
|
- ✔ 支持失败自动重新上传,支持配置次数,防止网络不稳定可能导致上传失败
|
|
15
|
+
- ✔ 镜像推送成功支持自动重启服务,减少部署流程
|
|
15
16
|
> 全程不需要连接跳板机做任何操作即可完成镜像推送
|
|
16
17
|
|
|
17
18
|
## 运行情况
|
|
@@ -81,7 +82,17 @@ const deploy = new yjDeploy({
|
|
|
81
82
|
'else',
|
|
82
83
|
'echo - 镜像推送失败,请重试或联系运维人员,如需查看全部docker日志,请在命令后添加 --allLog查看更多信息',
|
|
83
84
|
'fi',
|
|
84
|
-
]
|
|
85
|
+
],
|
|
86
|
+
|
|
87
|
+
// 自动重启服务的rancher配置
|
|
88
|
+
rancherConfig: {
|
|
89
|
+
token: '', // Rancher API 访问令牌 (必填)
|
|
90
|
+
namespace: '', // 服务所在的命名空间 (必填)
|
|
91
|
+
workload: '', // 服务名称 (必填)
|
|
92
|
+
|
|
93
|
+
// baseUrl: '', // Rancher API 基础地址
|
|
94
|
+
// clusterId: '', // Rancher 集群 ID
|
|
95
|
+
}
|
|
85
96
|
},
|
|
86
97
|
|
|
87
98
|
// 上传dist目录信息 (除非运维改变,否则不要配置)
|
|
@@ -256,6 +267,15 @@ node uploader.js --tmpName=test
|
|
|
256
267
|
如果小程序修改了相同路径和文件名的文件,会存在无法判断的情况(使用hash值能判断,但是成本过高),这种情况请关掉此选项,或使用 --reset 重置项目
|
|
257
268
|
:::
|
|
258
269
|
|
|
270
|
+
### --restart
|
|
271
|
+
`--restart` :是否需要自动重启服务,加上此命令,且配置了rancherConfig,将会自动重启服务
|
|
272
|
+
|
|
273
|
+
::: warning
|
|
274
|
+
仅对开发环境生效,并且需要先在rancher创建服务后方能成功
|
|
275
|
+
:::
|
|
276
|
+
|
|
277
|
+
<br />
|
|
278
|
+
|
|
259
279
|
|
|
260
280
|
<br />
|
|
261
281
|
|
|
@@ -268,6 +288,9 @@ node uploader.js --tmpName=test
|
|
|
268
288
|
|
|
269
289
|
## 升级日志
|
|
270
290
|
|
|
291
|
+
### v0.0.9
|
|
292
|
+
支持自动重启服务,上传完后,可以根据配置自动重启rancher服务
|
|
293
|
+
|
|
271
294
|
### v0.0.8
|
|
272
295
|
支持判断是否推送成功,如果推送失败,则自动重试三次,可以手动配置重试次数
|
|
273
296
|
|
package/dist/yj-deploy.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var $ = (
|
|
4
|
-
const { stdout: F } = require("single-line-log"),
|
|
1
|
+
var x = Object.defineProperty;
|
|
2
|
+
var L = (g, e, o) => e in g ? x(g, e, { enumerable: !0, configurable: !0, writable: !0, value: o }) : g[e] = o;
|
|
3
|
+
var $ = (g, e, o) => (L(g, typeof e != "symbol" ? e + "" : e, o), o);
|
|
4
|
+
const { stdout: F } = require("single-line-log"), N = require("path"), S = require("fs"), { Client: U } = require("ssh2"), A = require("axios"), R = require("https");
|
|
5
5
|
module.exports = class {
|
|
6
6
|
constructor(e) {
|
|
7
7
|
$(this, "config", {
|
|
@@ -60,8 +60,19 @@ module.exports = class {
|
|
|
60
60
|
// 是否显示全部日志
|
|
61
61
|
lazyUpload: !1,
|
|
62
62
|
// 是否开启懒上传
|
|
63
|
-
rePushNum: 3
|
|
63
|
+
rePushNum: 3,
|
|
64
64
|
// 重新推送次数,为0代表不重新推送
|
|
65
|
+
// 仅能通过命令启用
|
|
66
|
+
rancherConfig: {
|
|
67
|
+
// baseUrl: '', // Rancher API 基础地址
|
|
68
|
+
// clusterId: '', // Rancher 集群 ID
|
|
69
|
+
token: "",
|
|
70
|
+
// Rancher API 访问令牌
|
|
71
|
+
namespace: "",
|
|
72
|
+
// 服务所在的命名空间
|
|
73
|
+
workload: ""
|
|
74
|
+
// 工作负载名称
|
|
75
|
+
}
|
|
65
76
|
});
|
|
66
77
|
$(this, "ssh2Conn", null);
|
|
67
78
|
// 上传状态
|
|
@@ -114,7 +125,7 @@ module.exports = class {
|
|
|
114
125
|
console.log("- 请配置跳板机密码 password");
|
|
115
126
|
return;
|
|
116
127
|
}
|
|
117
|
-
this.uploading || (this.ssh2Conn = new
|
|
128
|
+
this.uploading || (this.ssh2Conn = new U(), this.ssh2Conn.on("ready", () => {
|
|
118
129
|
console.log("- 跳板机连接成功!"), this.ssh2Conn.sftp(async (e, o) => {
|
|
119
130
|
e && (console.log("- sftp连接失败"), this.breakConnect()), this.config.namespace || (console.log("- 请配置项目命名空间 namespace"), this.breakConnect()), this.uploading = !0, this.getArgv("--reset") !== void 0 && (console.log("- 重置项目"), await this.onShell(`rm -r ${this.config.rootDir}/${this.config.namespace}`)), this.upLoadProject(o, `${this.config.rootDir}/${this.config.namespace}`);
|
|
120
131
|
});
|
|
@@ -142,23 +153,23 @@ module.exports = class {
|
|
|
142
153
|
*/
|
|
143
154
|
onShell(e) {
|
|
144
155
|
const o = this.getArgv("--allLog") !== void 0;
|
|
145
|
-
return new Promise((t,
|
|
146
|
-
this.ssh2Conn.shell((l,
|
|
156
|
+
return new Promise((t, i) => {
|
|
157
|
+
this.ssh2Conn.shell((l, s) => {
|
|
147
158
|
if (l) {
|
|
148
|
-
console.log("- 远程命令错误:" + l, e), this.breakConnect(),
|
|
159
|
+
console.log("- 远程命令错误:" + l, e), this.breakConnect(), i(l);
|
|
149
160
|
return;
|
|
150
161
|
}
|
|
151
|
-
let
|
|
152
|
-
|
|
153
|
-
t(
|
|
154
|
-
}).on("data", (
|
|
155
|
-
let
|
|
156
|
-
(o || this.config.allLog ||
|
|
157
|
-
` +
|
|
158
|
-
}).stderr.on("data", (
|
|
162
|
+
let n = [];
|
|
163
|
+
s.on("close", () => {
|
|
164
|
+
t(n.toString());
|
|
165
|
+
}).on("data", (a) => {
|
|
166
|
+
let c = a.toString();
|
|
167
|
+
(o || this.config.allLog || c.startsWith("- ")) && n.push(`
|
|
168
|
+
` + c);
|
|
169
|
+
}).stderr.on("data", (a) => {
|
|
159
170
|
console.log(`- 远程命令错误:
|
|
160
|
-
` +
|
|
161
|
-
}),
|
|
171
|
+
` + a), this.breakConnect(), i(a);
|
|
172
|
+
}), s.end(e + `
|
|
162
173
|
exit
|
|
163
174
|
`);
|
|
164
175
|
});
|
|
@@ -170,18 +181,18 @@ exit
|
|
|
170
181
|
* @param {*} dir
|
|
171
182
|
*/
|
|
172
183
|
projectInit(e, o) {
|
|
173
|
-
return new Promise(async (t,
|
|
184
|
+
return new Promise(async (t, i) => {
|
|
174
185
|
e.mkdir(o, async (l) => {
|
|
175
186
|
if (l)
|
|
176
|
-
|
|
187
|
+
i(l);
|
|
177
188
|
else {
|
|
178
189
|
console.log("- 正在创建dockerfile");
|
|
179
|
-
const
|
|
180
|
-
await this.onShell(`touch ${
|
|
181
|
-
`)}' > ${
|
|
182
|
-
const
|
|
183
|
-
await this.onShell(`touch ${
|
|
184
|
-
`)}' > ${
|
|
190
|
+
const s = `${o}/${this.config.dockerfile.name}`;
|
|
191
|
+
await this.onShell(`touch ${s}`), await this.onShell(`echo '${this.config.dockerfile.content.join(`
|
|
192
|
+
`)}' > ${s}`), console.log("- 正在创建upload.sh");
|
|
193
|
+
const n = `${o}/${this.config.upload.name}`;
|
|
194
|
+
await this.onShell(`touch ${n}`), await this.onShell(`echo '${this.config.upload.content.join(`
|
|
195
|
+
`)}' > ${n}`), t();
|
|
185
196
|
}
|
|
186
197
|
});
|
|
187
198
|
});
|
|
@@ -194,8 +205,8 @@ exit
|
|
|
194
205
|
*/
|
|
195
206
|
checkRemoteFile(e, o) {
|
|
196
207
|
return new Promise((t) => {
|
|
197
|
-
e.stat(o, (
|
|
198
|
-
t(!
|
|
208
|
+
e.stat(o, (i) => {
|
|
209
|
+
t(!i);
|
|
199
210
|
});
|
|
200
211
|
});
|
|
201
212
|
}
|
|
@@ -206,74 +217,74 @@ exit
|
|
|
206
217
|
*/
|
|
207
218
|
upLoadProject(e, o) {
|
|
208
219
|
let t = 0;
|
|
209
|
-
typeof this.config.rePushNum == "number" && this.config.rePushNum > 0 && (t = this.config.rePushNum), e.readdir(o, async (
|
|
210
|
-
if (
|
|
220
|
+
typeof this.config.rePushNum == "number" && this.config.rePushNum > 0 && (t = this.config.rePushNum), e.readdir(o, async (i) => {
|
|
221
|
+
if (i) {
|
|
211
222
|
console.log("- 跳板机不存在项目, 开始创建项目"), this.projectInit(e, o).then(() => {
|
|
212
223
|
console.log(`- ${this.config.namespace}项目创建成功`), this.upLoadProject(e, o);
|
|
213
|
-
}).catch((
|
|
214
|
-
console.log(`- 创建项目失败: ${
|
|
224
|
+
}).catch((r) => {
|
|
225
|
+
console.log(`- 创建项目失败: ${r}`), this.breakConnect();
|
|
215
226
|
});
|
|
216
227
|
return;
|
|
217
228
|
}
|
|
218
|
-
const l = this.getArgv("--lazyUpload") !== void 0 || this.config.lazyUpload,
|
|
219
|
-
if (l || await this.onShell(`rm -rf ${
|
|
229
|
+
const l = this.getArgv("--lazyUpload") !== void 0 || this.config.lazyUpload, s = `${o}/${this.config.dist.name}`;
|
|
230
|
+
if (l || await this.onShell(`rm -rf ${s}`), await this.onShell(`mkdir ${s}`), !this.config.fileDir) {
|
|
220
231
|
console.log("- 请配置待上传文件目录 fileDir"), this.breakConnect();
|
|
221
232
|
return;
|
|
222
233
|
}
|
|
223
|
-
const { localFileList:
|
|
224
|
-
if (
|
|
234
|
+
const { localFileList: n, dirList: a } = await this.getFilesInDirectory(this.config.fileDir);
|
|
235
|
+
if (n.length === 0) {
|
|
225
236
|
console.log("- 待上传目录没有获取到文件,请检查"), this.breakConnect();
|
|
226
237
|
return;
|
|
227
238
|
}
|
|
228
|
-
let
|
|
229
|
-
const
|
|
239
|
+
let c = 0, m = n.length;
|
|
240
|
+
const w = (r) => new Promise(async (f, u) => {
|
|
230
241
|
try {
|
|
231
|
-
if (l && await this.checkRemoteFile(e, `${
|
|
232
|
-
return
|
|
233
|
-
const h =
|
|
242
|
+
if (l && await this.checkRemoteFile(e, `${s}${r.remotePath}`))
|
|
243
|
+
return f();
|
|
244
|
+
const h = S.createReadStream(r.localPath), b = e.createWriteStream(`${s}${r.remotePath}`);
|
|
234
245
|
b.on("close", () => {
|
|
235
|
-
this.progressBar(++
|
|
236
|
-
}), b.on("error", (
|
|
237
|
-
console.log(`- 文件 ${
|
|
246
|
+
this.progressBar(++c, m), f();
|
|
247
|
+
}), b.on("error", (C) => {
|
|
248
|
+
console.log(`- 文件 ${r.remotePath} 上传失败:${C}`), u(C), this.breakConnect();
|
|
238
249
|
}), h.pipe(b);
|
|
239
250
|
} catch (h) {
|
|
240
|
-
|
|
251
|
+
u(h);
|
|
241
252
|
}
|
|
242
|
-
}), d = (
|
|
243
|
-
let
|
|
244
|
-
for (let h = 0; h <
|
|
245
|
-
|
|
246
|
-
return
|
|
247
|
-
},
|
|
248
|
-
if (
|
|
253
|
+
}), d = (r, f) => {
|
|
254
|
+
let u = [];
|
|
255
|
+
for (let h = 0; h < r.length; h += f)
|
|
256
|
+
u.push(r.slice(h, h + f));
|
|
257
|
+
return u;
|
|
258
|
+
}, p = (r) => {
|
|
259
|
+
if (r.length === 0)
|
|
249
260
|
return Promise.resolve();
|
|
250
|
-
const
|
|
251
|
-
return Promise.all(
|
|
252
|
-
},
|
|
253
|
-
if (
|
|
261
|
+
const f = r.shift();
|
|
262
|
+
return Promise.all(f.map((u) => w(u))).then(() => p(r));
|
|
263
|
+
}, k = (r) => {
|
|
264
|
+
if (r.length === 0)
|
|
254
265
|
return Promise.resolve();
|
|
255
|
-
let
|
|
256
|
-
return this.onShell(`mkdir -p ${
|
|
266
|
+
let u = r.shift().map((h) => `${s}${h}`).join(" ");
|
|
267
|
+
return this.onShell(`mkdir -p ${u}`).then(() => k(r));
|
|
257
268
|
};
|
|
258
|
-
let
|
|
259
|
-
await
|
|
269
|
+
let y = [...d(a, this.config.parallelDir || 20)];
|
|
270
|
+
await k(y), console.log("- 创建目录完成"), await new Promise((r) => {
|
|
260
271
|
setTimeout(() => {
|
|
261
|
-
|
|
272
|
+
r();
|
|
262
273
|
}, 500);
|
|
263
274
|
});
|
|
264
|
-
let
|
|
265
|
-
await
|
|
266
|
-
let
|
|
275
|
+
let I = [...d(n, this.config.parallelFile || 50)];
|
|
276
|
+
await p(I), l && (this.progressBar(m, m), console.log("\x1B[32m%s\x1B[0m", `- 已开启懒上传,本次共上传 ${c} 个文件`)), console.log("- 文件上传成功"), console.log("- 开始推送镜像");
|
|
277
|
+
let P = `cd ${o} && sh ${this.config.upload.name}`;
|
|
267
278
|
if (await this.isNewShell(e, o)) {
|
|
268
|
-
const
|
|
269
|
-
|
|
279
|
+
const r = this.getArgv("--imageStore") || this.config.imageStore;
|
|
280
|
+
P += ` ${r}`;
|
|
270
281
|
} else
|
|
271
282
|
console.log(`
|
|
272
283
|
- warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`), console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
await this.pushMirrorImage(
|
|
284
|
+
const v = this.getArgv("--tmpName") || this.config.tmpName;
|
|
285
|
+
P += ` ${v}`;
|
|
286
|
+
const D = Array.from(new Array(t + 1)).map(() => this.onShell(P));
|
|
287
|
+
await this.pushMirrorImage(D, D.length), this.breakConnect();
|
|
277
288
|
});
|
|
278
289
|
}
|
|
279
290
|
// 推送镜像
|
|
@@ -281,7 +292,7 @@ exit
|
|
|
281
292
|
if (e.length === 0)
|
|
282
293
|
return console.log("- 镜像推送失败,请添加 --allLog 查看全部日志"), null;
|
|
283
294
|
const t = await e[0];
|
|
284
|
-
return t.includes("- 镜像推送失败") ? (console.log(t), e.length > 1 && console.log(`- 正在尝试第 ${o - e.length + 1} 次推送...`), this.pushMirrorImage(e.slice(1), o)) : (console.log(t), console.log("- 镜像推送结束"), t);
|
|
295
|
+
return t.includes("- 镜像推送失败") ? (console.log(t), e.length > 1 && console.log(`- 正在尝试第 ${o - e.length + 1} 次推送...`), this.pushMirrorImage(e.slice(1), o)) : (console.log(t), console.log("- 镜像推送结束"), this.getArgv("--restart") !== void 0 && await q(t, this.config.rancherConfig), t);
|
|
285
296
|
}
|
|
286
297
|
/**
|
|
287
298
|
* 获取脚本内容是不是自动创建的脚本
|
|
@@ -289,13 +300,13 @@ exit
|
|
|
289
300
|
* 有 $2代表是自动创建的版本,否则老版本
|
|
290
301
|
*/
|
|
291
302
|
async isNewShell(e, o) {
|
|
292
|
-
return new Promise((t,
|
|
293
|
-
e.readFile(`${o}/${this.config.upload.name}`, (l,
|
|
303
|
+
return new Promise((t, i) => {
|
|
304
|
+
e.readFile(`${o}/${this.config.upload.name}`, (l, s) => {
|
|
294
305
|
if (l)
|
|
295
|
-
return
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
- warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`), t(
|
|
306
|
+
return i(!1);
|
|
307
|
+
const n = s.toString();
|
|
308
|
+
n.includes("- 镜像推送失败") || console.log(`
|
|
309
|
+
- warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`), t(n.includes("$2"));
|
|
299
310
|
});
|
|
300
311
|
});
|
|
301
312
|
}
|
|
@@ -305,14 +316,14 @@ exit
|
|
|
305
316
|
* @param bar_length 进度条的长度(单位:字符),默认设为 25
|
|
306
317
|
*/
|
|
307
318
|
progressBar(e, o, t = 25) {
|
|
308
|
-
let
|
|
309
|
-
for (let
|
|
310
|
-
|
|
311
|
-
let
|
|
312
|
-
for (let
|
|
313
|
-
|
|
314
|
-
let
|
|
315
|
-
F(
|
|
319
|
+
let i = (e / o).toFixed(4), l = Math.floor(i * t), s = "";
|
|
320
|
+
for (let c = 0; c < l; c++)
|
|
321
|
+
s += "█";
|
|
322
|
+
let n = "";
|
|
323
|
+
for (let c = 0; c < t - l; c++)
|
|
324
|
+
n += "░";
|
|
325
|
+
let a = `- 文件上传进度: ${s}${n} (${e}/${o}) ${(100 * i).toFixed(2)}%`;
|
|
326
|
+
F(a), e == o && console.log("");
|
|
316
327
|
}
|
|
317
328
|
/**
|
|
318
329
|
* 获取目录下所有文件并分类
|
|
@@ -320,23 +331,23 @@ exit
|
|
|
320
331
|
*/
|
|
321
332
|
getFilesInDirectory(e) {
|
|
322
333
|
const o = [], t = [];
|
|
323
|
-
return new Promise((
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
let
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
localPath:
|
|
334
|
+
return new Promise((i, l) => {
|
|
335
|
+
const s = (n, a, c) => {
|
|
336
|
+
const m = S.readdirSync(n);
|
|
337
|
+
let w = !1;
|
|
338
|
+
m.forEach((d) => {
|
|
339
|
+
const p = N.join(n, d), y = S.statSync(p).isDirectory();
|
|
340
|
+
y ? w = !0 : o.push({
|
|
341
|
+
localPath: p,
|
|
331
342
|
// 本地路径
|
|
332
|
-
remotePath: `${
|
|
343
|
+
remotePath: `${a}${d}`,
|
|
333
344
|
// 远程路径
|
|
334
|
-
level:
|
|
345
|
+
level: c
|
|
335
346
|
// 文件或目录层级
|
|
336
|
-
}),
|
|
337
|
-
}),
|
|
347
|
+
}), y && s(p, `${a}${d}/`, c + 1);
|
|
348
|
+
}), w || t.push(a);
|
|
338
349
|
};
|
|
339
|
-
|
|
350
|
+
s(e, "/", 0), i({
|
|
340
351
|
localFileList: o,
|
|
341
352
|
dirList: t
|
|
342
353
|
});
|
|
@@ -349,8 +360,68 @@ exit
|
|
|
349
360
|
getArgv(e) {
|
|
350
361
|
const o = process.argv;
|
|
351
362
|
let t;
|
|
352
|
-
return o.forEach((
|
|
353
|
-
|
|
363
|
+
return o.forEach((i) => {
|
|
364
|
+
i.indexOf(e) > -1 && (t = i.split("=")[1] || "");
|
|
354
365
|
}), t;
|
|
355
366
|
}
|
|
356
367
|
};
|
|
368
|
+
const j = new R.Agent({
|
|
369
|
+
rejectUnauthorized: !1
|
|
370
|
+
});
|
|
371
|
+
async function q(g, e) {
|
|
372
|
+
try {
|
|
373
|
+
const t = {
|
|
374
|
+
// baseUrl: 'https://paas-test.ymygz.com:10443/v3', // Rancher API 基础地址
|
|
375
|
+
// token: 'token-2gqbg:pwpqvzqfbj26h9flwzbptcpz5sqn7jwh5c9ht2lfx46j4xz58875fr', // Rancher API 访问令牌
|
|
376
|
+
// clusterId: 'local:p-r7j28', // Rancher 集群 ID
|
|
377
|
+
// namespace: 'dev-wutaishan-onetravel', // 服务所在的命名空间
|
|
378
|
+
// workload: 'web-ymy-img', // 工作负载名称
|
|
379
|
+
...{
|
|
380
|
+
baseUrl: "https://paas-test.ymygz.com:10443/v3",
|
|
381
|
+
// Rancher API 基础地址
|
|
382
|
+
clusterId: "local:p-r7j28"
|
|
383
|
+
// Rancher 集群 ID
|
|
384
|
+
},
|
|
385
|
+
...e
|
|
386
|
+
};
|
|
387
|
+
if (!t.baseUrl)
|
|
388
|
+
return console.error("- Rancher API 基础地址未配置,请检查 rancherConfig.baseUrl 是否正确");
|
|
389
|
+
if (!t.token)
|
|
390
|
+
return console.error("- Rancher API 访问令牌未配置,请检查 rancherConfig.token 是否正确");
|
|
391
|
+
if (!t.clusterId)
|
|
392
|
+
return console.error("- Rancher 集群 ID 未配置,请检查 rancherConfig.clusterId 是否正确");
|
|
393
|
+
if (!t.namespace)
|
|
394
|
+
return console.error("- 服务所在的命名空间未配置,请检查 rancherConfig.namespace 是否正确");
|
|
395
|
+
if (!t.workload)
|
|
396
|
+
return console.error("- 工作负载名称未配置,请检查 rancherConfig.workload 是否正确");
|
|
397
|
+
if (!g)
|
|
398
|
+
return console.error("- 内部错误");
|
|
399
|
+
const i = g.match(/- 镜像地址:\s*(.+)/);
|
|
400
|
+
let l = "";
|
|
401
|
+
if (i && i[1])
|
|
402
|
+
l = i[1].trim();
|
|
403
|
+
else
|
|
404
|
+
return console.error("- 未提取到镜像地址");
|
|
405
|
+
console.log("- 开始调用 Rancher API 更新服务镜像地址");
|
|
406
|
+
const s = `${t.baseUrl}/projects/${t.clusterId}/workloads/deployment:${t.namespace}:${t.workload}`, n = await A.get(s, {
|
|
407
|
+
headers: { Authorization: `Bearer ${t.token}` },
|
|
408
|
+
httpsAgent: j
|
|
409
|
+
});
|
|
410
|
+
if (!n.data || !n.data.containers || n.data.containers.length === 0)
|
|
411
|
+
return console.error("- 未找到工作负载的容器信息,请检查 namespace 和 workload 是否正确");
|
|
412
|
+
const a = n.data;
|
|
413
|
+
a.containers[0].image = l, await A.put(s, a, {
|
|
414
|
+
headers: { Authorization: `Bearer ${t.token}` },
|
|
415
|
+
httpsAgent: j
|
|
416
|
+
}), console.log("- 镜像地址更新成功,正在重启服务..."), await A.post(
|
|
417
|
+
`${s}?action=redeploy`,
|
|
418
|
+
{},
|
|
419
|
+
{
|
|
420
|
+
headers: { Authorization: `Bearer ${t.token}` },
|
|
421
|
+
httpsAgent: j
|
|
422
|
+
}
|
|
423
|
+
), console.log("- 服务重启成功");
|
|
424
|
+
} catch (o) {
|
|
425
|
+
console.error("- 调用 Rancher API 失败, 请手动重启服务:", o.message);
|
|
426
|
+
}
|
|
427
|
+
}
|
package/dist/yj-deploy.umd.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
(function(h){typeof define=="function"&&define.amd?define(h):h()})(function(){"use strict";var
|
|
2
|
-
`+
|
|
3
|
-
`+
|
|
1
|
+
(function(h){typeof define=="function"&&define.amd?define(h):h()})(function(){"use strict";var R=Object.defineProperty;var q=(h,u,f)=>u in h?R(h,u,{enumerable:!0,configurable:!0,writable:!0,value:f}):h[u]=f;var y=(h,u,f)=>(q(h,typeof u!="symbol"?u+"":u,f),f);const{stdout:h}=require("single-line-log"),u=require("path"),f=require("fs"),{Client:v}=require("ssh2"),b=require("axios"),L=require("https");module.exports=class{constructor(e){y(this,"config",{host:"",port:"",username:"",password:"",namespace:"",imageStore:"dev-images",tmpName:"dev",delay:0,fileDir:"",rootDir:"/home/yjweb",dockerfile:{name:"Dockerfile",content:["FROM harbor.yunjingtech.cn:30002/yj-base/nginx:latest","RUN rm -rf /usr/share/nginx/html/*","COPY dist /usr/share/nginx/html/"]},upload:{name:"upload.sh",content:["#!/bin/sh","tag=`basename \\`pwd\\``:$2-`date +%Y%m%d`",'echo "- 镜像仓库: $1"','echo "- 镜像环境: $2"',"docker build -t harbor.yunjingtech.cn:30002/$1/$tag .","docker push harbor.yunjingtech.cn:30002/$1/$tag","if [ $? -eq 0 ];then","echo - 推送成功","echo - 镜像地址: harbor.yunjingtech.cn:30002/$1/$tag","else","echo - 镜像推送失败,请重试或联系运维人员,如需查看全部docker日志,请在命令后添加 --allLog查看更多信息","fi"]},dist:{name:"dist"},parallelDir:20,parallelFile:50,allLog:!1,lazyUpload:!1,rePushNum:3,rancherConfig:{token:"",namespace:"",workload:""}});y(this,"ssh2Conn",null);y(this,"uploading",!1);y(this,"trim",null);return this.config=Object.assign(this.config,e),{name:"yj-deploy",isPut:this.isPut.bind(this),upload:this.upload.bind(this),apply:this.apply.bind(this),closeBundle:this.isPut.bind(this)}}apply(e){return e&&e.hooks&&e.hooks.done&&e.hooks.done.tap("yj-deploy",()=>{this.isPut()}),"build"}isPut(){this.getArgv("--deploy")!=null&&(clearTimeout(this.trim),this.trim=setTimeout(()=>{this.upload()},this.config.delay||0))}upload(){if(console.log("-------------- deploy-start --------------"),!this.config.host){console.log("- 请配置跳板机地址 host");return}if(!this.config.port){console.log("- 请配置跳板机端口 port");return}if(!this.config.username){console.log("- 请配置跳板机账号 username");return}if(!this.config.password){console.log("- 请配置跳板机密码 password");return}this.uploading||(this.ssh2Conn=new v,this.ssh2Conn.on("ready",()=>{console.log("- 跳板机连接成功!"),this.ssh2Conn.sftp(async(e,t)=>{e&&(console.log("- sftp连接失败"),this.breakConnect()),this.config.namespace||(console.log("- 请配置项目命名空间 namespace"),this.breakConnect()),this.uploading=!0,this.getArgv("--reset")!==void 0&&(console.log("- 重置项目"),await this.onShell(`rm -r ${this.config.rootDir}/${this.config.namespace}`)),this.upLoadProject(t,`${this.config.rootDir}/${this.config.namespace}`)})}).connect({host:this.config.host,port:this.config.port,username:this.config.username,password:this.config.password}),this.ssh2Conn.on("error",e=>{console.log(`- 连接失败: ${e}`),this.breakConnect()}),this.ssh2Conn.on("end",()=>{this.uploading=!1}))}breakConnect(){this.uploading=!1,console.log("- 已断开连接"),console.log("-------------- deploy-end --------------"),this.ssh2Conn.end()}onShell(e){const t=this.getArgv("--allLog")!==void 0;return new Promise((o,i)=>{this.ssh2Conn.shell((l,s)=>{if(l){console.log("- 远程命令错误:"+l,e),this.breakConnect(),i(l);return}let n=[];s.on("close",()=>{o(n.toString())}).on("data",a=>{let c=a.toString();(t||this.config.allLog||c.startsWith("- "))&&n.push(`
|
|
2
|
+
`+c)}).stderr.on("data",a=>{console.log(`- 远程命令错误:
|
|
3
|
+
`+a),this.breakConnect(),i(a)}),s.end(e+`
|
|
4
4
|
exit
|
|
5
|
-
`)})})}projectInit(e,t){return new Promise(async(o,
|
|
6
|
-
`)}' > ${s}`),console.log("- 正在创建upload.sh");const
|
|
7
|
-
`)}' > ${
|
|
8
|
-
- warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`),console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");const
|
|
9
|
-
- warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`),o(
|
|
5
|
+
`)})})}projectInit(e,t){return new Promise(async(o,i)=>{e.mkdir(t,async l=>{if(l)i(l);else{console.log("- 正在创建dockerfile");const s=`${t}/${this.config.dockerfile.name}`;await this.onShell(`touch ${s}`),await this.onShell(`echo '${this.config.dockerfile.content.join(`
|
|
6
|
+
`)}' > ${s}`),console.log("- 正在创建upload.sh");const n=`${t}/${this.config.upload.name}`;await this.onShell(`touch ${n}`),await this.onShell(`echo '${this.config.upload.content.join(`
|
|
7
|
+
`)}' > ${n}`),o()}})})}checkRemoteFile(e,t){return new Promise(o=>{e.stat(t,i=>{o(!i)})})}upLoadProject(e,t){let o=0;typeof this.config.rePushNum=="number"&&this.config.rePushNum>0&&(o=this.config.rePushNum),e.readdir(t,async i=>{if(i){console.log("- 跳板机不存在项目, 开始创建项目"),this.projectInit(e,t).then(()=>{console.log(`- ${this.config.namespace}项目创建成功`),this.upLoadProject(e,t)}).catch(r=>{console.log(`- 创建项目失败: ${r}`),this.breakConnect()});return}const l=this.getArgv("--lazyUpload")!==void 0||this.config.lazyUpload,s=`${t}/${this.config.dist.name}`;if(l||await this.onShell(`rm -rf ${s}`),await this.onShell(`mkdir ${s}`),!this.config.fileDir){console.log("- 请配置待上传文件目录 fileDir"),this.breakConnect();return}const{localFileList:n,dirList:a}=await this.getFilesInDirectory(this.config.fileDir);if(n.length===0){console.log("- 待上传目录没有获取到文件,请检查"),this.breakConnect();return}let c=0,w=n.length;const k=r=>new Promise(async(p,d)=>{try{if(l&&await this.checkRemoteFile(e,`${s}${r.remotePath}`))return p();const g=f.createReadStream(r.localPath),D=e.createWriteStream(`${s}${r.remotePath}`);D.on("close",()=>{this.progressBar(++c,w),p()}),D.on("error",I=>{console.log(`- 文件 ${r.remotePath} 上传失败:${I}`),d(I),this.breakConnect()}),g.pipe(D)}catch(g){d(g)}}),m=(r,p)=>{let d=[];for(let g=0;g<r.length;g+=p)d.push(r.slice(g,g+p));return d},$=r=>{if(r.length===0)return Promise.resolve();const p=r.shift();return Promise.all(p.map(d=>k(d))).then(()=>$(r))},A=r=>{if(r.length===0)return Promise.resolve();let d=r.shift().map(g=>`${s}${g}`).join(" ");return this.onShell(`mkdir -p ${d}`).then(()=>A(r))};let P=[...m(a,this.config.parallelDir||20)];await A(P),console.log("- 创建目录完成"),await new Promise(r=>{setTimeout(()=>{r()},500)});let N=[...m(n,this.config.parallelFile||50)];await $(N),l&&(this.progressBar(w,w),console.log("\x1B[32m%s\x1B[0m",`- 已开启懒上传,本次共上传 ${c} 个文件`)),console.log("- 文件上传成功"),console.log("- 开始推送镜像");let j=`cd ${t} && sh ${this.config.upload.name}`;if(await this.isNewShell(e,t)){const r=this.getArgv("--imageStore")||this.config.imageStore;j+=` ${r}`}else console.log(`
|
|
8
|
+
- warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`),console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");const U=this.getArgv("--tmpName")||this.config.tmpName;j+=` ${U}`;const x=Array.from(new Array(o+1)).map(()=>this.onShell(j));await this.pushMirrorImage(x,x.length),this.breakConnect()})}async pushMirrorImage(e,t){if(e.length===0)return console.log("- 镜像推送失败,请添加 --allLog 查看全部日志"),null;const o=await e[0];return o.includes("- 镜像推送失败")?(console.log(o),e.length>1&&console.log(`- 正在尝试第 ${t-e.length+1} 次推送...`),this.pushMirrorImage(e.slice(1),t)):(console.log(o),console.log("- 镜像推送结束"),this.getArgv("--restart")!==void 0&&await F(o,this.config.rancherConfig),o)}async isNewShell(e,t){return new Promise((o,i)=>{e.readFile(`${t}/${this.config.upload.name}`,(l,s)=>{if(l)return i(!1);const n=s.toString();n.includes("- 镜像推送失败")||console.log(`
|
|
9
|
+
- warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`),o(n.includes("$2"))})})}progressBar(e,t,o=25){let i=(e/t).toFixed(4),l=Math.floor(i*o),s="";for(let c=0;c<l;c++)s+="█";let n="";for(let c=0;c<o-l;c++)n+="░";let a=`- 文件上传进度: ${s}${n} (${e}/${t}) ${(100*i).toFixed(2)}%`;h(a),e==t&&console.log("")}getFilesInDirectory(e){const t=[],o=[];return new Promise((i,l)=>{const s=(n,a,c)=>{const w=f.readdirSync(n);let k=!1;w.forEach(m=>{const $=u.join(n,m),P=f.statSync($).isDirectory();P?k=!0:t.push({localPath:$,remotePath:`${a}${m}`,level:c}),P&&s($,`${a}${m}/`,c+1)}),k||o.push(a)};s(e,"/",0),i({localFileList:t,dirList:o})})}getArgv(e){const t=process.argv;let o;return t.forEach(i=>{i.indexOf(e)>-1&&(o=i.split("=")[1]||"")}),o}};const C=new L.Agent({rejectUnauthorized:!1});async function F(S,e){try{const o={...{baseUrl:"https://paas-test.ymygz.com:10443/v3",clusterId:"local:p-r7j28"},...e};if(!o.baseUrl)return console.error("- Rancher API 基础地址未配置,请检查 rancherConfig.baseUrl 是否正确");if(!o.token)return console.error("- Rancher API 访问令牌未配置,请检查 rancherConfig.token 是否正确");if(!o.clusterId)return console.error("- Rancher 集群 ID 未配置,请检查 rancherConfig.clusterId 是否正确");if(!o.namespace)return console.error("- 服务所在的命名空间未配置,请检查 rancherConfig.namespace 是否正确");if(!o.workload)return console.error("- 工作负载名称未配置,请检查 rancherConfig.workload 是否正确");if(!S)return console.error("- 内部错误");const i=S.match(/- 镜像地址:\s*(.+)/);let l="";if(i&&i[1])l=i[1].trim();else return console.error("- 未提取到镜像地址");console.log("- 开始调用 Rancher API 更新服务镜像地址");const s=`${o.baseUrl}/projects/${o.clusterId}/workloads/deployment:${o.namespace}:${o.workload}`,n=await b.get(s,{headers:{Authorization:`Bearer ${o.token}`},httpsAgent:C});if(!n.data||!n.data.containers||n.data.containers.length===0)return console.error("- 未找到工作负载的容器信息,请检查 namespace 和 workload 是否正确");const a=n.data;a.containers[0].image=l,await b.put(s,a,{headers:{Authorization:`Bearer ${o.token}`},httpsAgent:C}),console.log("- 镜像地址更新成功,正在重启服务..."),await b.post(`${s}?action=redeploy`,{},{headers:{Authorization:`Bearer ${o.token}`},httpsAgent:C}),console.log("- 服务重启成功")}catch(t){console.error("- 调用 Rancher API 失败, 请手动重启服务:",t.message)}}});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yj-deploy",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "ssh sftp",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "vite build"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"author": "",
|
|
22
22
|
"license": "ISC",
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"axios": "^1.13.5",
|
|
24
25
|
"single-line-log": "^1.1.2",
|
|
25
26
|
"ssh2": "^1.15.0"
|
|
26
27
|
},
|