@soft-artel/ci 2.0.59 → 2.0.62

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.
Files changed (80) hide show
  1. package/Project.d.ts +57 -0
  2. package/Project.d.ts.map +1 -0
  3. package/Project.js +176 -0
  4. package/Project.js.map +1 -0
  5. package/commands/incrementBuild.d.ts +3 -0
  6. package/commands/incrementBuild.d.ts.map +1 -0
  7. package/commands/incrementBuild.js +34 -0
  8. package/commands/incrementBuild.js.map +1 -0
  9. package/commands/k8s-build.d.ts +28 -0
  10. package/commands/k8s-build.d.ts.map +1 -0
  11. package/commands/k8s-build.js +295 -0
  12. package/commands/k8s-build.js.map +1 -0
  13. package/commands/k8s-deploy.d.ts +11 -0
  14. package/commands/k8s-deploy.d.ts.map +1 -0
  15. package/commands/k8s-deploy.js +145 -0
  16. package/commands/k8s-deploy.js.map +1 -0
  17. package/commands/xcode.d.ts +3 -0
  18. package/commands/xcode.d.ts.map +1 -0
  19. package/commands/xcode.js +128 -0
  20. package/commands/xcode.js.map +1 -0
  21. package/package.json +1 -1
  22. package/services/Git.d.ts +46 -0
  23. package/services/Git.d.ts.map +1 -0
  24. package/services/Git.js +138 -0
  25. package/services/Git.js.map +1 -0
  26. package/services/Gitlab.d.ts +14 -0
  27. package/services/Gitlab.d.ts.map +1 -0
  28. package/services/Gitlab.js +101 -0
  29. package/services/Gitlab.js.map +1 -0
  30. package/services/Jira.d.ts +32 -0
  31. package/services/Jira.d.ts.map +1 -0
  32. package/services/Jira.js +136 -0
  33. package/services/Jira.js.map +1 -0
  34. package/services/Reporter.d.ts +43 -0
  35. package/services/Reporter.d.ts.map +1 -0
  36. package/services/Reporter.js +139 -0
  37. package/services/Reporter.js.map +1 -0
  38. package/utils/Exception.d.ts +5 -0
  39. package/utils/Exception.d.ts.map +1 -0
  40. package/utils/Exception.js +14 -0
  41. package/utils/Exception.js.map +1 -0
  42. package/utils/Logger.d.ts +11 -0
  43. package/utils/Logger.d.ts.map +1 -0
  44. package/utils/Logger.js +62 -0
  45. package/utils/Logger.js.map +1 -0
  46. package/utils/Shell.d.ts +39 -0
  47. package/utils/Shell.d.ts.map +1 -0
  48. package/utils/Shell.js +89 -0
  49. package/utils/Shell.js.map +1 -0
  50. package/utils/helpers.d.ts +15 -0
  51. package/utils/helpers.d.ts.map +1 -0
  52. package/utils/helpers.js +99 -0
  53. package/utils/helpers.js.map +1 -0
  54. package/utils/prototype.d.ts +9 -0
  55. package/utils/prototype.d.ts.map +1 -0
  56. package/utils/prototype.js +186 -0
  57. package/utils/prototype.js.map +1 -0
  58. package/.env +0 -21
  59. package/.eslintcache +0 -1
  60. package/.eslintignore +0 -4
  61. package/.eslintrc +0 -246
  62. package/.gitlab-ci.yml +0 -12
  63. package/README.md +0 -33
  64. package/_publish.sh +0 -25
  65. package/src/Project.ts +0 -291
  66. package/src/commands/incrementBuild.ts +0 -46
  67. package/src/commands/k8s-build.ts +0 -447
  68. package/src/commands/k8s-deploy.ts +0 -215
  69. package/src/commands/xcode.ts +0 -192
  70. package/src/services/Git.ts +0 -203
  71. package/src/services/Gitlab.ts +0 -129
  72. package/src/services/Jira.ts +0 -228
  73. package/src/services/Reporter.ts +0 -238
  74. package/src/utils/Exception.ts +0 -19
  75. package/src/utils/Logger.ts +0 -85
  76. package/src/utils/Shell.ts +0 -120
  77. package/src/utils/helpers.ts +0 -118
  78. package/src/utils/prototype.ts +0 -313
  79. package/test.ts +0 -0
  80. package/tsconfig.json +0 -25
@@ -1,447 +0,0 @@
1
- #!/usr/bin/env node
2
- import '../utils/prototype';
3
-
4
- import {program, OptionValues} from 'commander';
5
- import { promises as fsAsync } from 'fs';
6
- import Mustache from 'mustache';
7
- import yaml from 'js-yaml';
8
-
9
- import Shell from '../utils/Shell';
10
- import { Jira } from '../services/Jira';
11
- import Git from '../services/Git';
12
- import { Config, Project, RunCommandHandler } from '../Project';
13
- import { Reporter } from '../services/Reporter';
14
- import { log } from '../utils/Logger';
15
- import { md5, resolvePath } from '../utils/helpers';
16
- import { Exception } from '../utils/Exception';
17
-
18
- const ENV = process.env;
19
-
20
- // ============================================
21
- // Comand flow
22
-
23
- const runHandler: RunCommandHandler = async ( prj: Project, reporter: Reporter, opts: OptionValues, config: Config ) => {
24
-
25
- reporter.$message_footer_changes_from = prj.version;
26
- reporter.$message_footer_changes_to = ENV.CI_COMMIT_REF_NAME || 'dev';
27
-
28
- const release = opts.changelog ? await Jira.resolve(prj) : { tasks:[], bugs:[], changelog:''};
29
-
30
- // --build: Build and push docker image to registry
31
- Shell.cd( prj.rootPath );
32
-
33
- // -----------------------------
34
- // 1. Start
35
- prj.incrementBuild();
36
-
37
- const pkg = JSON.parse(await Shell.cat(prj.rootPath +'/package.json') || 'no package.json file!');
38
- pkg.version = prj.version;
39
- await fsAsync.writeFile(prj.rootPath +'/package.json', JSON.stringify(pkg, null, 3), { encoding: 'utf8', flag: 'w+'});
40
-
41
- await reporter.startBuild();
42
- log.info(`1. build ${ prj.stage }`);
43
-
44
- // -----------------------------
45
- // 2. Chek updated apps
46
- await reporter.append(` + Load apps and check updated`);
47
- log.info(`2. Load apps and check updated`);
48
-
49
- const updateFiles = await Git.getDiffFiles();
50
- log.info(`\n\nUPDATED FILES:\n$`, updateFiles);
51
-
52
- const { apps, updated, images } = await getAppsAndImages(prj, config, updateFiles);
53
-
54
- if(!updated){
55
- log.info('SKIP BUILD: NO Apps to update!');
56
- await reporter.skip(`SKIP - no apps to update\n ${ updateFiles.join('\n ')}`);
57
- process.exit(0);
58
- }
59
-
60
- // -----------------------------
61
- // 4. write k8s manifests!
62
- await reporter.append(` + Write K8s manifests`);
63
- log.info(`3. Write K8s manifests`);
64
-
65
- let imagesToBuild: Record<string, DockerImage> = {}
66
-
67
- for (const appName of updated) {
68
- const app = apps[ appName ]
69
- const image = images[ app.dockerImageKey ]
70
-
71
- if( !image ){
72
- throw new Exception('No dockerfile found for app: '+ app.name, { app, images })
73
- }
74
-
75
- await writeManifest(prj, app, image);
76
- imagesToBuild[ image.key ] = image
77
- }
78
-
79
- // -----------------------------
80
- // 5. Build images
81
- await Shell.execRepeat(`docker login -u ${ ENV.CI_REGISTRY_USER } -p ${ ENV.CI_REGISTRY_PASSWORD } ${ ENV.CI_REGISTRY }`);
82
-
83
- for (const image of Object.values( imagesToBuild )) {
84
-
85
- // 5.1. distrib
86
- log.info(`${ image.tag } distrib`);
87
- await reporter.append(` + ${ image.tag } distrib`);
88
- const distribCommand = image.distribRun || 'npm run distrib'
89
- log.info( distribCommand )
90
- await Shell.exec( distribCommand );
91
-
92
- // 5.2. build
93
- log.info(`${ image.tag } build docker image`);
94
- await reporter.append(` + ${ image.tag } build docker image`);
95
- Shell.cd(image.path);
96
- await Shell.exec(`docker build --platform amd64 -t ${ image.fullTag } -f ${ image.path }/dockerfile ${ image.path }/.`);
97
-
98
- // 5.3 push
99
- log.dbg(`${ image.tag } Docker push`);
100
- await reporter.append(` + ${ image.tag } push image to registry`);
101
- await Shell.execRepeat(`docker login -u ${ ENV.CI_REGISTRY_USER } -p ${ ENV.CI_REGISTRY_PASSWORD } ${ ENV.CI_REGISTRY }`);
102
- await Shell.execRepeat(`docker push ${ image.fullTag }`);
103
- log.dbg(`Clear local docker image: ${ image.tag }`);
104
- await Shell.exec(`docker rmi -f ${ image.fullTag }`);
105
-
106
- }
107
-
108
- // -----------------------------
109
- // 5. Save new build to GitLab CI Variable
110
-
111
- await reporter.append(` + Save current build: ${ prj.build }`);
112
- log.dbg(`7. Put current build:${ prj.build } to GitLab`);
113
- await prj.saveGitLabBuild();
114
-
115
- // -----------------------
116
-
117
-
118
- if(opts.changelog){
119
- await prj.updateChangeLog(release.changelog);
120
- await reporter.append(` + Update <a href=\"${ prj.url }/-/blob/${ ENV.CI_COMMIT_REF_NAME || 'dev' }/CHANGELOG.md\">changelog</a>.`);
121
- }
122
-
123
- await Git.setOrigin();
124
- await Shell.exec(`git add -A`, { ignoreError: true, silent: false });
125
- await Shell.exec(`git commit -a -m "v${ prj.version }"`, { ignoreError: true, silent: false });
126
-
127
- try{
128
- await Git.push(ENV.CI_COMMIT_REF_NAME || 'dev');
129
- }catch(e){
130
- log.error(e);
131
- await Shell.exec(`git checkout --ours .`, { ignoreError: true, silent: false });
132
- await Shell.exec(`git add -u`, { ignoreError: true, silent: false });
133
- await Shell.exec(`git commit -a -m "v${ prj.version } resolved"`, { ignoreError: true, silent: false });
134
- await Git.push(ENV.CI_COMMIT_REF_NAME || 'dev');
135
- }
136
-
137
-
138
- if(opts.gittag){
139
- await reporter.append(` + Git tag new version`);
140
- await Git.makeTag(prj.version);
141
- }
142
-
143
- await reporter.succses();
144
- }
145
-
146
-
147
- // ========================================================
148
- // Command
149
-
150
- async function main() {
151
-
152
- program
153
- .option('--path <project dir>', 'Path to project directory [default - current]')
154
-
155
- .option('--no-prebuild', 'Skip prebuild phase')
156
-
157
- .option('--no-changelog', 'Skip create gitlog')
158
- .option('--no-gittag', 'Skip create and push new git tag')
159
- .action( await Project.run(program.opts() || {}, runHandler) );
160
-
161
- await program.parseAsync(process.argv);
162
- }
163
- main();
164
-
165
-
166
- // ================================================================================================
167
- // Clasess
168
-
169
- class App{
170
-
171
- group = '';
172
- name = '';
173
- app = '';
174
- path = '';
175
-
176
- $pathComp: string[] = [ './' ];
177
-
178
- dockerImageKey = ''
179
-
180
- // ----------
181
-
182
- constructor(path: string = ''){
183
- let pathComp = path.split('/');
184
-
185
- log.dbg( 'App try load by path:'+ path, pathComp )
186
-
187
- if( pathComp.length > 1 && pathComp[0] === 'src' ){
188
- // Remove src folder reference
189
- pathComp = pathComp.splice(1)
190
- }
191
-
192
- if( pathComp.length > 1 && pathComp[1] === 'src' ){
193
- // Remove src folder reference
194
- pathComp = pathComp.splice(0,-1)
195
- }
196
-
197
- if(pathComp.length > 1 && pathComp[1].length > 1 ){
198
-
199
- this.group = pathComp[0];
200
- this.app = pathComp[1];
201
- this.name = `${this.group}-${this.app}`;
202
- this.path = path;
203
-
204
- this.$pathComp.push(`./${ this.group }/`);
205
- this.$pathComp.push(`./${ this.path }/`);
206
-
207
- }else{
208
- this.app = pathComp.length == 1 ? pathComp[0] : path;
209
- this.name = this.app;
210
- this.group = ''
211
- this.path = '/'+path;
212
- this.$pathComp = [ this.path ]
213
- }
214
-
215
- log.dbg( this.name, this )
216
- }
217
-
218
- }
219
-
220
- // ------------------------------------------------------------------
221
-
222
-
223
- export class DockerImage{
224
-
225
- path = '';
226
- key = '';
227
-
228
- srcImage = '';
229
-
230
- tag = '';
231
- fullTag = '';
232
-
233
- distribRun?: string;
234
-
235
- // -----------
236
- constructor( path: string, appName:string, ver: string, build: number, dockerfile?: string ){
237
- if( !dockerfile ){
238
- throw new Error('Wrong dockerfile: Empty! by path:'+path)
239
- }
240
-
241
- this.path = path
242
- this.key = md5( dockerfile )
243
-
244
- const dockerImage = /FROM (.+)/.exec(dockerfile);
245
- if(!dockerImage || dockerImage.length < 2){
246
- throw new Error(`Wrong dockerfile format:\n${dockerfile}`);
247
- }
248
-
249
- this.srcImage = dockerImage[1];
250
-
251
- const dockerName = /#[ ]?NAME (.+)/.exec(dockerfile);
252
- const name = dockerName && dockerName.length > 1 ? dockerName[1] : appName || this.key;
253
-
254
- // api-v1.2:#321
255
- this.tag = `${name}-v${ver}:${build}`;
256
- this.fullTag = `${ ENV.CI_REGISTRY_IMAGE }/${ this.tag }`;
257
-
258
- const action = /#[ ]?DISTRIB RUN (.+)/.exec(dockerfile);
259
- this.distribRun = action && action.length > 1 ? action[1] : undefined;
260
- }
261
- }
262
-
263
-
264
- // ================================================================================================
265
- // Helpers:
266
-
267
- export async function getAppsAndImages(prj: Project, config: Config, updatedPaths: string[], maxdepth: number = 4, ignore: string[] = ['*/_*', '*/\.*', '*/node_modules*']): Promise<{ apps: Record<string, App>; updated: string[] | undefined; images: Record<string, DockerImage> }>{
268
-
269
- Shell.cd( config.appsPath || prj.rootPath );
270
-
271
- const appFile = config.appFile || 'app_manifest_tpl.yaml'
272
-
273
- const appsPaths = await Shell.find( config.appsPath || prj.rootPath, appFile, maxdepth, ignore);
274
-
275
- log.dbg( 'Apps paths by appFile:'+ appFile, appsPaths )
276
-
277
- const ignorePaths = [ ...config.ignorePaths || [], ...config.sharedPaths || [] ];
278
-
279
- const apps: Record<string, App> = {};
280
- const watchPaths: Record<string, string> = {};
281
-
282
- const dockerImages: Record<string, DockerImage> = {}
283
-
284
- // Load rootDocker Image
285
- let rootDockerFileKey = ''
286
- const dockerfileRootPath = resolvePath( prj.rootPath )+'/dockerfile'
287
- if( await Shell.test('-e', dockerfileRootPath ) ){
288
- const image = new DockerImage( prj.rootPath, prj.name, prj.shortVersion, prj.build, await Shell.cat( dockerfileRootPath ) );
289
- dockerImages[ image.key ] = image
290
- rootDockerFileKey= image.key
291
- log.dbg( 'Dokerfile at root path - found! '+ image.key )
292
- }
293
- else{
294
- log.info( 'Dokerfile at root path not found! path:'+ dockerfileRootPath )
295
- }
296
-
297
-
298
- for(const file of appsPaths.sort()) {
299
- const path = file.replace(prj.rootPath+'/', '').replace('/src', '').replace( '/'+appFile, '');
300
-
301
- let skip = false;
302
- for(const i_path of ignorePaths){
303
- if(path.includes(i_path)){
304
- skip = true;
305
- break;
306
- }
307
- }
308
-
309
- if(skip){
310
- continue;
311
- }
312
-
313
- const app = new App(path);
314
-
315
- if( app.group === '' ){
316
- app.group = prj.name
317
- }
318
-
319
- if(app.name === '' ){
320
- app.name = prj.name;
321
- app.app = prj.name;
322
- app.group = '';
323
- app.path = '';
324
- }
325
-
326
- const dockerfileAppPath = resolvePath( prj.rootPath + '/' + app.path )+'/dockerfile'
327
- if( app.path !== '' && await Shell.test('-e', dockerfileAppPath ) ){
328
- log.info( 'Dokerfile at app "'+ app.name +'" found! path:'+ dockerfileAppPath )
329
- const image = new DockerImage( app.path, app.name, prj.shortVersion, prj.build, await Shell.cat( dockerfileAppPath ) );
330
- dockerImages[ image.key ] = image
331
- app.dockerImageKey = image.key
332
-
333
- }
334
- else{
335
- log.dbg( 'App dokerfile not found at:"'+ dockerfileAppPath +'", and set as default:'+ rootDockerFileKey )
336
- app.dockerImageKey = rootDockerFileKey
337
- }
338
-
339
- apps[ app.name ] = app;
340
- watchPaths[ app.path ] = app.name;
341
- }
342
-
343
- log.dbg(`Find apps by ${ appFile }`, Object.keys( apps ).sort());
344
-
345
- if( Object.keys( dockerImages ).length === 0 ){
346
- throw new Exception('No any dockerfile found in project! Pleace collect it on root or app path')
347
- }
348
-
349
- const sharedPaths = config.sharedPaths || [];
350
-
351
- sharedPaths.push('package.json');
352
- sharedPaths.push('app_manifest_spec.json');
353
- sharedPaths.push('app_manifest_tpl.yaml');
354
- sharedPaths.push('dockerfile');
355
-
356
- const watchPathsArr = Object.keys(watchPaths);
357
-
358
- let appsChanged: Record<string, App> = {};
359
-
360
- for (const file of updatedPaths) {
361
-
362
- let allApps = false;
363
- for(const i_path of sharedPaths){
364
- if(file.includes(i_path)){
365
- allApps = true;
366
- break;
367
- }
368
- }
369
- if(allApps){
370
- appsChanged = apps;
371
- break;
372
- }
373
-
374
- for(const testPath of watchPathsArr){
375
- const appName = watchPaths[ testPath ];
376
- if((RegExp(testPath).exec(file)) && apps[ appName ] && appsChanged[ appName ] === undefined){
377
- appsChanged[ appName ] = apps[ appName ];
378
- }
379
- }
380
- }
381
-
382
- const changed = Object.keys(appsChanged);
383
- return { apps, updated: changed.length > 0 ? changed : undefined, images: dockerImages };
384
-
385
- }
386
-
387
-
388
- // ------------------------------------------------------------------
389
-
390
-
391
- async function writeManifest(prj: Project, app: App, image: DockerImage){
392
-
393
- const k8s_tpl_fileName = 'app_manifest_tpl.yaml';
394
- const k8s_spec_fileName = 'app_manifest_spec.json';
395
-
396
- // 1. Находим шаблон манифеста для app и специфицации ресурсов
397
-
398
- const appFullPath = resolvePath( prj.rootPath + '/' + app.path + '/' )
399
-
400
-
401
- let k8s_spec: Record<string, any> = {};
402
-
403
- const k8s_tpl = await Shell.cat( appFullPath + k8s_tpl_fileName, {silent: !log.isDebug, ignoreError:true });
404
- const k8s_specFile = await Shell.cat( appFullPath + k8s_spec_fileName, {silent: !log.isDebug, ignoreError:true });
405
-
406
- if(k8s_specFile && k8s_specFile !== ''){
407
- try {
408
- k8s_spec = JSON.parse( k8s_specFile ) as Record<string, any>;
409
- } catch(e){
410
- log.error(e);
411
- }
412
- }
413
-
414
-
415
- if(!k8s_tpl || k8s_tpl.length < 10){
416
- throw new Exception(`No K8s template [${k8s_tpl_fileName}] found for app:${ app.name }`, { appFullPath });
417
- }
418
-
419
- // 4.2. Ходим по стейджам, формируем и сохраняем манифесты
420
-
421
- for (const stage of Object.keys( prj.$stagesState )) {
422
-
423
- const k8s_data: Record<string, any> = {};
424
-
425
- k8s_data.stage = stage;
426
- k8s_data.app = app;
427
- k8s_data.image = image;
428
- k8s_data.prj = prj;
429
- k8s_data.spec = k8s_spec[ stage ] || {};
430
-
431
- if(!k8s_data.spec.replicas){
432
- k8s_data.spec.replicas = 1;
433
- }
434
-
435
- log.dbg(`${app.name} k8s specs`, k8s_spec);
436
- // k8s_data.spec.volumeMounts = k8s_data.spec.volumes;
437
- const manifest = Mustache.render(k8s_tpl, k8s_data);
438
-
439
- yaml.loadAll(manifest); // Test yaml
440
-
441
- await Shell.mkdir(`${ prj.rootPath}/_k8s/${stage}`, {ignoreError: false});
442
- await fsAsync.writeFile(`${ prj.rootPath}/_k8s/${stage}/${app.name}.yml`, manifest);
443
-
444
- log.dbg(`[${stage}] ${app.name} writed -> ${ prj.rootPath}/_k8s/${stage}/${app.name}.yml `);
445
-
446
- }
447
- }
@@ -1,215 +0,0 @@
1
- #!/usr/bin/env node
2
- import '../utils/prototype';
3
-
4
- import {program, OptionValues} from 'commander';
5
-
6
- import Shell, { ExecOptions } from '../utils/Shell';
7
- import { Jira, ReleaseInfo } from '../services/Jira';
8
- import Git from '../services/Git';
9
- import { Config, Project } from '../Project';
10
- import { Reporter } from '../services/Reporter';
11
- import { log } from '../utils/Logger';
12
- import { RunCommandHandler } from '../Project';
13
- import { Exception } from '../utils/Exception';
14
- import { resolvePath } from '../utils/helpers';
15
-
16
- const ENV = process.env;
17
-
18
-
19
- // ============================================
20
- // Command flow
21
-
22
- const runHandler: RunCommandHandler = async ( prj: Project, reporter: Reporter, opts: OptionValues, config: Config ) => {
23
-
24
- // ------------------
25
- // Инициализируемся
26
-
27
- reporter.$message_footer_changes_from = prj.prevVersion || prj.version;
28
- reporter.$message_footer_changes_to = prj.version;
29
-
30
- const release: ReleaseInfo = opts.changelog ? await Jira.resolve(prj) : { commits:[], components:[], tasks:[], bugs:[], changelog:''};
31
-
32
- // 1. Меняем стейдж на новый, потому что деплоим в него!
33
- const stage = opts.stage;
34
-
35
- const configKey = `K8S_${ stage.toUpperCase() }_CONFIG`;
36
- let configFile = ENV[ configKey ];
37
-
38
- if(!configFile){
39
- throw new Exception(`CI VARIABLE K8s config file not found: ${ configKey }`);
40
- }
41
-
42
- configFile = resolvePath(configFile) ;
43
-
44
-
45
- prj.stage = stage;
46
- await reporter.startDeploy(stage);
47
-
48
- // предполагаем для для каждого стейджа у нас отдельныая k8s нода!
49
- // namespace = default для всех ресурсов
50
-
51
- // 2. Получаем путь
52
- const k8s_dir = `${ prj.rootPath }/_k8s/${stage}/`;
53
- Shell.cd(k8s_dir);
54
-
55
- // 3. Обходим ВСЕ файлы - те что не поменялись не применятся
56
- const deployedApps: Record<string, string> = {};
57
- const k8s_files = await Shell.ls(k8s_dir);
58
-
59
- try{
60
-
61
- // Деплоим все(!) аппы - те что не поменялись не применятся
62
- for (const k8s_file of k8s_files) {
63
-
64
- const appName = k8s_file.replace('.yml', '');
65
- const status = await k8sApply( configFile, `${ k8s_dir }/${k8s_file}`, appName, opts.namespace );
66
-
67
- let msg = '';
68
- if(status.valid){
69
- deployedApps[ appName ] = status.msg;
70
- msg = ` + <b>${appName}</b>`;
71
- await reporter.append(msg);
72
- } else {
73
- msg = ` ❌ <b>${appName}</b> yaml is invalid!\n${ status.msg }`;
74
- await reporter.append(msg);
75
- throw new Error(`FAIL: Deployment app: ${appName}`);
76
- }
77
-
78
- }
79
-
80
- } catch(error){
81
-
82
- // Деплой сфейлился - надо откатыватся
83
- if(Object.keys(deployedApps).length > 0){
84
-
85
- log.fail('FAIL: Deployments to rollout', Object.keys(deployedApps));
86
- await reporter.append(` 🚫 <b>APPLAY FAIL</b>: Rollout all deployed apps`);
87
-
88
- for (const appName of Object.keys(deployedApps)) {
89
- await k8sExec(`rollout undo deployment/${ appName }`, configFile, undefined, opts.namespace );
90
- }
91
-
92
- }
93
-
94
- throw error;
95
- }
96
-
97
- // deploy succsess, push changes to stage brunch
98
- if(opts.gitpush){
99
- await Git.setOrigin();
100
- await Git.push(stage);
101
- }
102
-
103
- // await reporter.append(` + Save ${ prj.stage} -> ${ prj.version} to GitLab`);
104
- await prj.saveGitLabStagesVersions();
105
-
106
- if(opts.changelog){
107
- await reporter.append(` ----------------\n+ Resolved tasks:${ release.tasks.length }, bugs: ${ release.bugs.length }`);
108
-
109
- const bugsDeployed = await Jira.moveBugsAsDeployed(release.bugs);
110
- if(bugsDeployed.length > 0){
111
- await reporter.append(` + Deployed ${ bugsDeployed.length } bugs.`);
112
- } else {
113
- // await reporter.append(` - Deploy bugs. [SKIPED]`);
114
- }
115
- }
116
-
117
- await reporter.succses(release);
118
-
119
- }
120
-
121
- // ========================================================
122
- // Command
123
-
124
- async function main() {
125
-
126
- program
127
- .option('--path <project dir>', 'Path to project directory [default - current]')
128
- .option('--stage <stage>', 'K8s node stage <stage>')
129
- .option('--namespace <namespace>', 'Deploy manifests to K8s specific namespace')
130
-
131
- .option('--no-changelog', 'Skip create gitlog')
132
- .option('--no-gitpush', 'Skip pushing to stage branch after deploy')
133
-
134
- .option('--debug', 'Output extra debugging info')
135
- .action( await Project.run( program.opts() || {}, runHandler ) );
136
-
137
- await program.parseAsync(process.argv);
138
- }
139
- main();
140
-
141
-
142
- // ================================================================================================
143
- // Helpers:
144
-
145
- export async function k8sApply( configFile: string, manifestFile: string, appName: string, namespace?: string): Promise<{valid: boolean; msg: string}>{
146
-
147
- let status: string;
148
-
149
- try{
150
- await k8sExec(`apply -f ${ manifestFile } --dry-run=server --validate`, configFile, undefined, namespace);
151
- status = (await Shell.exec(`cat ${ manifestFile } | grep 'image:'`)).replace('image: ', '').replace(ENV.CI_REGISTRY_IMAGE+'/', '').trim();
152
- }catch(e){
153
- log.error(e);
154
- log.fail('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
155
- log.fail(`VALIDATE FAIL: ${appName} at ${manifestFile}`);
156
- log.fail(e.message);
157
- log.fail(`${ await Shell.cat(manifestFile) }`);
158
- log.fail('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
159
-
160
- return { valid:false, msg: e.message };
161
- }
162
-
163
- try{
164
- status = await k8sExec(`apply -f ${ manifestFile }`, configFile, undefined, namespace);
165
- await k8sExec(`rollout status deploy/${ appName } --watch=true --timeout=300s`, configFile);
166
-
167
- log.info('+++++++++++++++++++++++++++++');
168
- log.info(`DEPLOYED: ${appName}`);
169
- log.info(await k8sExec(`get pods --no-headers -l app=${appName} -o wide`, configFile, { ignoreError:true, silent:true }), namespace);
170
- log.info('+++++++++++++++++++++++++++++');
171
-
172
- return { valid:true, msg: status };
173
-
174
- } catch(error){
175
- log.fail('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
176
- log.fail(`DEPLOY FAIL: ${appName}`);
177
- log.fail('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
178
- log.fail(await k8sExec(`get pods --no-headers -l app=${appName} -o wide`, configFile, { ignoreError:true, silent:true }), namespace);
179
- throw error;
180
- }
181
-
182
- }
183
-
184
- // ------------------------------
185
-
186
- export async function k8sStatus(configFile: string, stage: string, reporter: Reporter | undefined = undefined, appsOnly: boolean = true, namespace?: string): Promise<string[]>{
187
-
188
- const cmd = `get pods -o=jsonpath='{range .items[*]}{"\\n"}{"<b>"}{.metadata.labels.app}{"</b> "}{range .spec.containers[*]}{.image}{end}{" "}{.metadata.labels.pod-template-hash}{" "}{.status.phase}{end}'`;
189
- const statusArr = (await k8sExec(cmd, configFile, undefined, namespace)).split('\n');
190
-
191
- const result: string[] = [];
192
- for (const row of statusArr) {
193
- if(row.length < 4 || (appsOnly && (RegExp((ENV.CI_REGISTRY_IMAGE || '')).exec(row)) === null)){
194
- continue;
195
- }
196
- result.push(row.replace(ENV.CI_REGISTRY_IMAGE+'/', ''));
197
- }
198
-
199
- log.info(`K8S Node: ${ stage } [${ namespace || 'default' }]`, result);
200
-
201
- if(reporter){
202
- const msg = `\n<b>${ stage.toUpperCase() }</b> Pods:\n-----------------\n ` + result.join('\n-----------------\n ');
203
- await reporter.append(msg, '');
204
- }
205
-
206
- return result;
207
- }
208
-
209
-
210
- // ------------------------------
211
-
212
-
213
- export async function k8sExec( cmd: string, configFile: string, opt: ExecOptions | undefined = undefined, namespace?: string, cmdPrefix = ''): Promise<string>{
214
- return Shell.exec(`${cmdPrefix}kubectl --kubeconfig ${ configFile } ${ namespace ? `-n ${ namespace }` : '' } ${ cmd }`, opt);
215
- }