yj-deploy 0.0.9 → 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.
@@ -1,7 +1,7 @@
1
1
  var x = Object.defineProperty;
2
- var L = (f, e, t) => e in f ? x(f, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : f[e] = t;
3
- var $ = (f, e, t) => (L(f, typeof e != "symbol" ? e + "" : e, t), t);
4
- const { stdout: A } = require("single-line-log"), F = require("path"), C = require("fs"), { Client: N } = require("ssh2"), I = require("./rancher");
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", {
@@ -125,9 +125,9 @@ module.exports = class {
125
125
  console.log("- 请配置跳板机密码 password");
126
126
  return;
127
127
  }
128
- this.uploading || (this.ssh2Conn = new N(), this.ssh2Conn.on("ready", () => {
129
- console.log("- 跳板机连接成功!"), this.ssh2Conn.sftp(async (e, t) => {
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(t, `${this.config.rootDir}/${this.config.namespace}`);
128
+ this.uploading || (this.ssh2Conn = new U(), this.ssh2Conn.on("ready", () => {
129
+ console.log("- 跳板机连接成功!"), this.ssh2Conn.sftp(async (e, o) => {
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}`);
131
131
  });
132
132
  }).connect({
133
133
  host: this.config.host,
@@ -152,23 +152,23 @@ module.exports = class {
152
152
  * @returns Promise
153
153
  */
154
154
  onShell(e) {
155
- const t = this.getArgv("--allLog") !== void 0;
156
- return new Promise((o, r) => {
155
+ const o = this.getArgv("--allLog") !== void 0;
156
+ return new Promise((t, i) => {
157
157
  this.ssh2Conn.shell((l, s) => {
158
158
  if (l) {
159
- console.log("- 远程命令错误:" + l, e), this.breakConnect(), r(l);
159
+ console.log("- 远程命令错误:" + l, e), this.breakConnect(), i(l);
160
160
  return;
161
161
  }
162
- let i = [];
162
+ let n = [];
163
163
  s.on("close", () => {
164
- o(i.toString());
165
- }).on("data", (c) => {
166
- let a = c.toString();
167
- (t || this.config.allLog || a.startsWith("- ")) && i.push(`
168
- ` + a);
169
- }).stderr.on("data", (c) => {
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) => {
170
170
  console.log(`- 远程命令错误:
171
- ` + c), this.breakConnect(), r(c);
171
+ ` + a), this.breakConnect(), i(a);
172
172
  }), s.end(e + `
173
173
  exit
174
174
  `);
@@ -180,19 +180,19 @@ exit
180
180
  * @param {*} sftp
181
181
  * @param {*} dir
182
182
  */
183
- projectInit(e, t) {
184
- return new Promise(async (o, r) => {
185
- e.mkdir(t, async (l) => {
183
+ projectInit(e, o) {
184
+ return new Promise(async (t, i) => {
185
+ e.mkdir(o, async (l) => {
186
186
  if (l)
187
- r(l);
187
+ i(l);
188
188
  else {
189
189
  console.log("- 正在创建dockerfile");
190
- const s = `${t}/${this.config.dockerfile.name}`;
190
+ const s = `${o}/${this.config.dockerfile.name}`;
191
191
  await this.onShell(`touch ${s}`), await this.onShell(`echo '${this.config.dockerfile.content.join(`
192
192
  `)}' > ${s}`), console.log("- 正在创建upload.sh");
193
- const i = `${t}/${this.config.upload.name}`;
194
- await this.onShell(`touch ${i}`), await this.onShell(`echo '${this.config.upload.content.join(`
195
- `)}' > ${i}`), o();
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();
196
196
  }
197
197
  });
198
198
  });
@@ -203,10 +203,10 @@ exit
203
203
  * @param {*} remotePath
204
204
  * @returns
205
205
  */
206
- checkRemoteFile(e, t) {
207
- return new Promise((o) => {
208
- e.stat(t, (r) => {
209
- o(!r);
206
+ checkRemoteFile(e, o) {
207
+ return new Promise((t) => {
208
+ e.stat(o, (i) => {
209
+ t(!i);
210
210
  });
211
211
  });
212
212
  }
@@ -215,98 +215,98 @@ exit
215
215
  * @param {*} sftp
216
216
  * @param {*} dir
217
217
  */
218
- upLoadProject(e, t) {
219
- let o = 0;
220
- typeof this.config.rePushNum == "number" && this.config.rePushNum > 0 && (o = this.config.rePushNum), e.readdir(t, async (r) => {
221
- if (r) {
222
- console.log("- 跳板机不存在项目, 开始创建项目"), this.projectInit(e, t).then(() => {
223
- console.log(`- ${this.config.namespace}项目创建成功`), this.upLoadProject(e, t);
224
- }).catch((n) => {
225
- console.log(`- 创建项目失败: ${n}`), this.breakConnect();
218
+ upLoadProject(e, o) {
219
+ let t = 0;
220
+ typeof this.config.rePushNum == "number" && this.config.rePushNum > 0 && (t = this.config.rePushNum), e.readdir(o, async (i) => {
221
+ if (i) {
222
+ console.log("- 跳板机不存在项目, 开始创建项目"), this.projectInit(e, o).then(() => {
223
+ console.log(`- ${this.config.namespace}项目创建成功`), this.upLoadProject(e, o);
224
+ }).catch((r) => {
225
+ console.log(`- 创建项目失败: ${r}`), this.breakConnect();
226
226
  });
227
227
  return;
228
228
  }
229
- const l = this.getArgv("--lazyUpload") !== void 0 || this.config.lazyUpload, s = `${t}/${this.config.dist.name}`;
229
+ const l = this.getArgv("--lazyUpload") !== void 0 || this.config.lazyUpload, s = `${o}/${this.config.dist.name}`;
230
230
  if (l || await this.onShell(`rm -rf ${s}`), await this.onShell(`mkdir ${s}`), !this.config.fileDir) {
231
231
  console.log("- 请配置待上传文件目录 fileDir"), this.breakConnect();
232
232
  return;
233
233
  }
234
- const { localFileList: i, dirList: c } = await this.getFilesInDirectory(this.config.fileDir);
235
- if (i.length === 0) {
234
+ const { localFileList: n, dirList: a } = await this.getFilesInDirectory(this.config.fileDir);
235
+ if (n.length === 0) {
236
236
  console.log("- 待上传目录没有获取到文件,请检查"), this.breakConnect();
237
237
  return;
238
238
  }
239
- let a = 0, p = i.length;
240
- const y = (n) => new Promise(async (u, g) => {
239
+ let c = 0, m = n.length;
240
+ const w = (r) => new Promise(async (f, u) => {
241
241
  try {
242
- if (l && await this.checkRemoteFile(e, `${s}${n.remotePath}`))
243
- return u();
244
- const h = C.createReadStream(n.localPath), k = e.createWriteStream(`${s}${n.remotePath}`);
245
- k.on("close", () => {
246
- this.progressBar(++a, p), u();
247
- }), k.on("error", (b) => {
248
- console.log(`- 文件 ${n.remotePath} 上传失败:${b}`), g(b), this.breakConnect();
249
- }), h.pipe(k);
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}`);
245
+ b.on("close", () => {
246
+ this.progressBar(++c, m), f();
247
+ }), b.on("error", (C) => {
248
+ console.log(`- 文件 ${r.remotePath} 上传失败:${C}`), u(C), this.breakConnect();
249
+ }), h.pipe(b);
250
250
  } catch (h) {
251
- g(h);
251
+ u(h);
252
252
  }
253
- }), d = (n, u) => {
254
- let g = [];
255
- for (let h = 0; h < n.length; h += u)
256
- g.push(n.slice(h, h + u));
257
- return g;
258
- }, m = (n) => {
259
- if (n.length === 0)
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)
260
260
  return Promise.resolve();
261
- const u = n.shift();
262
- return Promise.all(u.map((g) => y(g))).then(() => m(n));
263
- }, P = (n) => {
264
- if (n.length === 0)
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)
265
265
  return Promise.resolve();
266
- let g = n.shift().map((h) => `${s}${h}`).join(" ");
267
- return this.onShell(`mkdir -p ${g}`).then(() => P(n));
266
+ let u = r.shift().map((h) => `${s}${h}`).join(" ");
267
+ return this.onShell(`mkdir -p ${u}`).then(() => k(r));
268
268
  };
269
- let w = [...d(c, this.config.parallelDir || 20)];
270
- await P(w), console.log("- 创建目录完成"), await new Promise((n) => {
269
+ let y = [...d(a, this.config.parallelDir || 20)];
270
+ await k(y), console.log("- 创建目录完成"), await new Promise((r) => {
271
271
  setTimeout(() => {
272
- n();
272
+ r();
273
273
  }, 500);
274
274
  });
275
- let D = [...d(i, this.config.parallelFile || 50)];
276
- await m(D), l && (this.progressBar(p, p), console.log("\x1B[32m%s\x1B[0m", `- 已开启懒上传,本次共上传 ${a} 个文件`)), console.log("- 文件上传成功"), console.log("- 开始推送镜像");
277
- let S = `cd ${t} && sh ${this.config.upload.name}`;
278
- if (await this.isNewShell(e, t)) {
279
- const n = this.getArgv("--imageStore") || this.config.imageStore;
280
- S += ` ${n}`;
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}`;
278
+ if (await this.isNewShell(e, o)) {
279
+ const r = this.getArgv("--imageStore") || this.config.imageStore;
280
+ P += ` ${r}`;
281
281
  } else
282
282
  console.log(`
283
283
  - warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`), console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");
284
284
  const v = this.getArgv("--tmpName") || this.config.tmpName;
285
- S += ` ${v}`;
286
- const j = Array.from(new Array(o + 1)).map(() => this.onShell(S));
287
- await this.pushMirrorImage(j, j.length), this.breakConnect();
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();
288
288
  });
289
289
  }
290
290
  // 推送镜像
291
- async pushMirrorImage(e, t) {
291
+ async pushMirrorImage(e, o) {
292
292
  if (e.length === 0)
293
293
  return console.log("- 镜像推送失败,请添加 --allLog 查看全部日志"), null;
294
- const o = await e[0];
295
- 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 I(o, this.config.rancherConfig), o);
294
+ const t = await e[0];
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);
296
296
  }
297
297
  /**
298
298
  * 获取脚本内容是不是自动创建的脚本
299
299
  * 兼容用户手动根据运维文档创建的推送脚本,因参数不同可能报错的问题
300
300
  * 有 $2代表是自动创建的版本,否则老版本
301
301
  */
302
- async isNewShell(e, t) {
303
- return new Promise((o, r) => {
304
- e.readFile(`${t}/${this.config.upload.name}`, (l, s) => {
302
+ async isNewShell(e, o) {
303
+ return new Promise((t, i) => {
304
+ e.readFile(`${o}/${this.config.upload.name}`, (l, s) => {
305
305
  if (l)
306
- return r(!1);
307
- const i = s.toString();
308
- i.includes("- 镜像推送失败") || console.log(`
309
- - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`), o(i.includes("$2"));
306
+ return i(!1);
307
+ const n = s.toString();
308
+ n.includes("- 镜像推送失败") || console.log(`
309
+ - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`), t(n.includes("$2"));
310
310
  });
311
311
  });
312
312
  }
@@ -315,41 +315,41 @@ exit
315
315
  * @param description 命令行开头的文字信息
316
316
  * @param bar_length 进度条的长度(单位:字符),默认设为 25
317
317
  */
318
- progressBar(e, t, o = 25) {
319
- let r = (e / t).toFixed(4), l = Math.floor(r * o), s = "";
320
- for (let a = 0; a < l; a++)
318
+ progressBar(e, o, t = 25) {
319
+ let i = (e / o).toFixed(4), l = Math.floor(i * t), s = "";
320
+ for (let c = 0; c < l; c++)
321
321
  s += "█";
322
- let i = "";
323
- for (let a = 0; a < o - l; a++)
324
- i += "░";
325
- let c = `- 文件上传进度: ${s}${i} (${e}/${t}) ${(100 * r).toFixed(2)}%`;
326
- A(c), e == t && console.log("");
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("");
327
327
  }
328
328
  /**
329
329
  * 获取目录下所有文件并分类
330
330
  * @param {*} dir
331
331
  */
332
332
  getFilesInDirectory(e) {
333
- const t = [], o = [];
334
- return new Promise((r, l) => {
335
- const s = (i, c, a) => {
336
- const p = C.readdirSync(i);
337
- let y = !1;
338
- p.forEach((d) => {
339
- const m = F.join(i, d), w = C.statSync(m).isDirectory();
340
- w ? y = !0 : t.push({
341
- localPath: m,
333
+ const o = [], t = [];
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,
342
342
  // 本地路径
343
- remotePath: `${c}${d}`,
343
+ remotePath: `${a}${d}`,
344
344
  // 远程路径
345
- level: a
345
+ level: c
346
346
  // 文件或目录层级
347
- }), w && s(m, `${c}${d}/`, a + 1);
348
- }), y || o.push(c);
347
+ }), y && s(p, `${a}${d}/`, c + 1);
348
+ }), w || t.push(a);
349
349
  };
350
- s(e, "/", 0), r({
351
- localFileList: t,
352
- dirList: o
350
+ s(e, "/", 0), i({
351
+ localFileList: o,
352
+ dirList: t
353
353
  });
354
354
  });
355
355
  }
@@ -358,10 +358,70 @@ exit
358
358
  * @param { string } name
359
359
  */
360
360
  getArgv(e) {
361
- const t = process.argv;
362
- let o;
363
- return t.forEach((r) => {
364
- r.indexOf(e) > -1 && (o = r.split("=")[1] || "");
365
- }), o;
361
+ const o = process.argv;
362
+ let t;
363
+ return o.forEach((i) => {
364
+ i.indexOf(e) > -1 && (t = i.split("=")[1] || "");
365
+ }), t;
366
366
  }
367
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
+ }
@@ -1,9 +1,9 @@
1
- (function(h){typeof define=="function"&&define.amd?define(h):h()})(function(){"use strict";var F=Object.defineProperty;var N=(h,u,f)=>u in h?F(h,u,{enumerable:!0,configurable:!0,writable:!0,value:f}):h[u]=f;var w=(h,u,f)=>(N(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"),x=require("./rancher");module.exports=class{constructor(e){w(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:""}});w(this,"ssh2Conn",null);w(this,"uploading",!1);w(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,r)=>{this.ssh2Conn.shell((l,i)=>{if(l){console.log("- 远程命令错误:"+l,e),this.breakConnect(),r(l);return}let s=[];i.on("close",()=>{o(s.toString())}).on("data",c=>{let a=c.toString();(t||this.config.allLog||a.startsWith("- "))&&s.push(`
2
- `+a)}).stderr.on("data",c=>{console.log(`- 远程命令错误:
3
- `+c),this.breakConnect(),r(c)}),i.end(e+`
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,r)=>{e.mkdir(t,async l=>{if(l)r(l);else{console.log("- 正在创建dockerfile");const i=`${t}/${this.config.dockerfile.name}`;await this.onShell(`touch ${i}`),await this.onShell(`echo '${this.config.dockerfile.content.join(`
6
- `)}' > ${i}`),console.log("- 正在创建upload.sh");const s=`${t}/${this.config.upload.name}`;await this.onShell(`touch ${s}`),await this.onShell(`echo '${this.config.upload.content.join(`
7
- `)}' > ${s}`),o()}})})}checkRemoteFile(e,t){return new Promise(o=>{e.stat(t,r=>{o(!r)})})}upLoadProject(e,t){let o=0;typeof this.config.rePushNum=="number"&&this.config.rePushNum>0&&(o=this.config.rePushNum),e.readdir(t,async r=>{if(r){console.log("- 跳板机不存在项目, 开始创建项目"),this.projectInit(e,t).then(()=>{console.log(`- ${this.config.namespace}项目创建成功`),this.upLoadProject(e,t)}).catch(n=>{console.log(`- 创建项目失败: ${n}`),this.breakConnect()});return}const l=this.getArgv("--lazyUpload")!==void 0||this.config.lazyUpload,i=`${t}/${this.config.dist.name}`;if(l||await this.onShell(`rm -rf ${i}`),await this.onShell(`mkdir ${i}`),!this.config.fileDir){console.log("- 请配置待上传文件目录 fileDir"),this.breakConnect();return}const{localFileList:s,dirList:c}=await this.getFilesInDirectory(this.config.fileDir);if(s.length===0){console.log("- 待上传目录没有获取到文件,请检查"),this.breakConnect();return}let a=0,y=s.length;const P=n=>new Promise(async(m,d)=>{try{if(l&&await this.checkRemoteFile(e,`${i}${n.remotePath}`))return m();const g=f.createReadStream(n.localPath),C=e.createWriteStream(`${i}${n.remotePath}`);C.on("close",()=>{this.progressBar(++a,y),m()}),C.on("error",j=>{console.log(`- 文件 ${n.remotePath} 上传失败:${j}`),d(j),this.breakConnect()}),g.pipe(C)}catch(g){d(g)}}),p=(n,m)=>{let d=[];for(let g=0;g<n.length;g+=m)d.push(n.slice(g,g+m));return d},$=n=>{if(n.length===0)return Promise.resolve();const m=n.shift();return Promise.all(m.map(d=>P(d))).then(()=>$(n))},k=n=>{if(n.length===0)return Promise.resolve();let d=n.shift().map(g=>`${i}${g}`).join(" ");return this.onShell(`mkdir -p ${d}`).then(()=>k(n))};let S=[...p(c,this.config.parallelDir||20)];await k(S),console.log("- 创建目录完成"),await new Promise(n=>{setTimeout(()=>{n()},500)});let L=[...p(s,this.config.parallelFile||50)];await $(L),l&&(this.progressBar(y,y),console.log("\x1B[32m%s\x1B[0m",`- 已开启懒上传,本次共上传 ${a} 个文件`)),console.log("- 文件上传成功"),console.log("- 开始推送镜像");let b=`cd ${t} && sh ${this.config.upload.name}`;if(await this.isNewShell(e,t)){const n=this.getArgv("--imageStore")||this.config.imageStore;b+=` ${n}`}else console.log(`
8
- - warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`),console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");const A=this.getArgv("--tmpName")||this.config.tmpName;b+=` ${A}`;const D=Array.from(new Array(o+1)).map(()=>this.onShell(b));await this.pushMirrorImage(D,D.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 x(o,this.config.rancherConfig),o)}async isNewShell(e,t){return new Promise((o,r)=>{e.readFile(`${t}/${this.config.upload.name}`,(l,i)=>{if(l)return r(!1);const s=i.toString();s.includes("- 镜像推送失败")||console.log(`
9
- - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`),o(s.includes("$2"))})})}progressBar(e,t,o=25){let r=(e/t).toFixed(4),l=Math.floor(r*o),i="";for(let a=0;a<l;a++)i+="█";let s="";for(let a=0;a<o-l;a++)s+="░";let c=`- 文件上传进度: ${i}${s} (${e}/${t}) ${(100*r).toFixed(2)}%`;h(c),e==t&&console.log("")}getFilesInDirectory(e){const t=[],o=[];return new Promise((r,l)=>{const i=(s,c,a)=>{const y=f.readdirSync(s);let P=!1;y.forEach(p=>{const $=u.join(s,p),S=f.statSync($).isDirectory();S?P=!0:t.push({localPath:$,remotePath:`${c}${p}`,level:a}),S&&i($,`${c}${p}/`,a+1)}),P||o.push(c)};i(e,"/",0),r({localFileList:t,dirList:o})})}getArgv(e){const t=process.argv;let o;return t.forEach(r=>{r.indexOf(e)>-1&&(o=r.split("=")[1]||"")}),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.9",
3
+ "version": "0.0.10",
4
4
  "description": "ssh sftp",
5
5
  "scripts": {
6
6
  "build": "vite build"
@@ -21,11 +21,11 @@
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
  },
27
28
  "devDependencies": {
28
- "axios": "^1.13.5",
29
29
  "vite": "4.5"
30
30
  },
31
31
  "publishConfig": {