@soft-artel/ci 2.3.87 → 2.3.89
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/Project.d.ts +57 -0
- package/Project.d.ts.map +1 -0
- package/Project.js +173 -0
- package/Project.js.map +1 -0
- package/commands/incrementBuild.d.ts +3 -0
- package/commands/incrementBuild.d.ts.map +1 -0
- package/commands/incrementBuild.js +34 -0
- package/commands/incrementBuild.js.map +1 -0
- package/commands/k8s-build.d.ts +30 -0
- package/commands/k8s-build.d.ts.map +1 -0
- package/commands/k8s-build.js +291 -0
- package/commands/k8s-build.js.map +1 -0
- package/commands/k8s-deploy.d.ts +11 -0
- package/commands/k8s-deploy.d.ts.map +1 -0
- package/commands/k8s-deploy.js +145 -0
- package/commands/k8s-deploy.js.map +1 -0
- package/commands/xcode.d.ts +3 -0
- package/commands/xcode.d.ts.map +1 -0
- package/commands/xcode.js +128 -0
- package/commands/xcode.js.map +1 -0
- package/package.json +3 -3
- package/services/Git.d.ts +46 -0
- package/services/Git.d.ts.map +1 -0
- package/services/Git.js +138 -0
- package/services/Git.js.map +1 -0
- package/services/Gitlab.d.ts +14 -0
- package/services/Gitlab.d.ts.map +1 -0
- package/services/Gitlab.js +101 -0
- package/services/Gitlab.js.map +1 -0
- package/services/Jira.d.ts +32 -0
- package/services/Jira.d.ts.map +1 -0
- package/services/Jira.js +136 -0
- package/services/Jira.js.map +1 -0
- package/services/Reporter.d.ts +43 -0
- package/services/Reporter.d.ts.map +1 -0
- package/services/Reporter.js +134 -0
- package/services/Reporter.js.map +1 -0
- package/utils/Exception.d.ts +5 -0
- package/utils/Exception.d.ts.map +1 -0
- package/utils/Exception.js +14 -0
- package/utils/Exception.js.map +1 -0
- package/utils/Logger.d.ts +11 -0
- package/utils/Logger.d.ts.map +1 -0
- package/utils/Logger.js +62 -0
- package/utils/Logger.js.map +1 -0
- package/utils/Shell.d.ts +39 -0
- package/utils/Shell.d.ts.map +1 -0
- package/utils/Shell.js +89 -0
- package/utils/Shell.js.map +1 -0
- package/utils/helpers.d.ts +16 -0
- package/utils/helpers.d.ts.map +1 -0
- package/utils/helpers.js +109 -0
- package/utils/helpers.js.map +1 -0
- package/utils/prototype.d.ts +9 -0
- package/utils/prototype.d.ts.map +1 -0
- package/utils/prototype.js +186 -0
- package/utils/prototype.js.map +1 -0
- package/.env +0 -21
- package/.eslintcache +0 -1
- package/.eslintignore +0 -4
- package/.eslintrc +0 -246
- package/.gitlab-ci.yml +0 -12
- package/README.md +0 -33
- package/_publish.sh +0 -11
- package/src/Project.ts +0 -286
- package/src/commands/incrementBuild.ts +0 -46
- package/src/commands/k8s-build.ts +0 -441
- package/src/commands/k8s-deploy.ts +0 -215
- package/src/commands/xcode.ts +0 -192
- package/src/services/Git.ts +0 -203
- package/src/services/Gitlab.ts +0 -129
- package/src/services/Jira.ts +0 -228
- package/src/services/Reporter.ts +0 -230
- package/src/utils/Exception.ts +0 -19
- package/src/utils/Logger.ts +0 -85
- package/src/utils/Shell.ts +0 -120
- package/src/utils/helpers.ts +0 -126
- package/src/utils/prototype.ts +0 -313
- package/test.ts +0 -0
- package/tsconfig.json +0 -25
|
@@ -1,441 +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
|
-
|
|
76
|
-
for (const stage of Object.keys( prj.$stagesState )) {
|
|
77
|
-
|
|
78
|
-
const k8s_data: Record<string, any> = {};
|
|
79
|
-
|
|
80
|
-
k8s_data.stage = stage;
|
|
81
|
-
k8s_data.app = app;
|
|
82
|
-
k8s_data.image = image;
|
|
83
|
-
k8s_data.prj = prj;
|
|
84
|
-
k8s_data.spec = app.k8s_spec[ stage ] || {};
|
|
85
|
-
|
|
86
|
-
if(!k8s_data.spec.replicas){
|
|
87
|
-
k8s_data.spec.replicas = 1;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
log.dbg(`${app.name} k8s data`, k8s_data);
|
|
91
|
-
// k8s_data.spec.volumeMounts = k8s_data.spec.volumes;
|
|
92
|
-
const manifest = Mustache.render(app.k8s_tpl, k8s_data);
|
|
93
|
-
|
|
94
|
-
yaml.loadAll(manifest); // Test yaml
|
|
95
|
-
|
|
96
|
-
await Shell.mkdir(`${ prj.rootPath}/_k8s/${stage}`, {ignoreError: false});
|
|
97
|
-
await fsAsync.writeFile(`${ prj.rootPath}/_k8s/${stage}/${app.name}.yml`, manifest);
|
|
98
|
-
|
|
99
|
-
log.dbg(`[${stage}] ${app.name} writed -> ${ prj.rootPath}/_k8s/${stage}/${app.name}.yml `);
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
imagesToBuild[ image.key ] = image
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// -----------------------------
|
|
107
|
-
// 5. Build images
|
|
108
|
-
await Shell.execRepeat(`docker login -u ${ ENV.CI_REGISTRY_USER } -p ${ ENV.CI_REGISTRY_PASSWORD } ${ ENV.CI_REGISTRY }`);
|
|
109
|
-
|
|
110
|
-
for (const image of Object.values( imagesToBuild )) {
|
|
111
|
-
|
|
112
|
-
// 5.1. distrib
|
|
113
|
-
log.info(`${ image.tag } distrib`);
|
|
114
|
-
await reporter.append(` + ${ image.tag } run tests and distrib`);
|
|
115
|
-
const distribCommand = image.distribRun || 'npm run distrib'
|
|
116
|
-
log.info( distribCommand )
|
|
117
|
-
await Shell.exec( distribCommand );
|
|
118
|
-
|
|
119
|
-
// 5.2. build
|
|
120
|
-
log.info(`${ image.tag } build docker image from root context dir: ${ prj.rootPath }`);
|
|
121
|
-
await reporter.append(` + ${ image.tag } build docker image`);
|
|
122
|
-
Shell.cd(image.path);
|
|
123
|
-
await Shell.exec(`docker build --platform amd64 -t ${ image.fullTag } -f ${ image.path } ${ prj.rootPath }/.`);
|
|
124
|
-
|
|
125
|
-
// 5.3 push
|
|
126
|
-
log.dbg(`${ image.tag } Docker push`);
|
|
127
|
-
await reporter.append(` + ${ image.tag } push image to registry`);
|
|
128
|
-
await Shell.execRepeat(`docker login -u ${ ENV.CI_REGISTRY_USER } -p ${ ENV.CI_REGISTRY_PASSWORD } ${ ENV.CI_REGISTRY }`);
|
|
129
|
-
await Shell.execRepeat(`docker push ${ image.fullTag }`);
|
|
130
|
-
log.dbg(`Clear local docker image: ${ image.tag }`);
|
|
131
|
-
await Shell.exec(`docker rmi -f ${ image.fullTag }`);
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// -----------------------------
|
|
136
|
-
// 5. Save new build to GitLab CI Variable
|
|
137
|
-
|
|
138
|
-
await reporter.append(` + Save current build: ${ prj.build }`);
|
|
139
|
-
log.dbg(`7. Put current build:${ prj.build } to GitLab`);
|
|
140
|
-
await prj.saveGitLabBuild();
|
|
141
|
-
|
|
142
|
-
// -----------------------
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if(opts.changelog){
|
|
146
|
-
await prj.updateChangeLog(release.changelog);
|
|
147
|
-
await reporter.append(` + Update <a href=\"${ prj.url }/-/blob/${ ENV.CI_COMMIT_REF_NAME || 'dev' }/CHANGELOG.md\">changelog</a>.`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
await Git.setOrigin();
|
|
151
|
-
await Shell.exec(`git add -A`, { ignoreError: true, silent: false });
|
|
152
|
-
await Shell.exec(`git commit -a -m "v${ prj.version }"`, { ignoreError: true, silent: false });
|
|
153
|
-
|
|
154
|
-
try{
|
|
155
|
-
await Git.push(ENV.CI_COMMIT_REF_NAME || 'dev');
|
|
156
|
-
}catch(e){
|
|
157
|
-
log.error(e);
|
|
158
|
-
await Shell.exec(`git checkout --ours .`, { ignoreError: true, silent: false });
|
|
159
|
-
await Shell.exec(`git add -u`, { ignoreError: true, silent: false });
|
|
160
|
-
await Shell.exec(`git commit -a -m "v${ prj.version } resolved"`, { ignoreError: true, silent: false });
|
|
161
|
-
await Git.push(ENV.CI_COMMIT_REF_NAME || 'dev');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if(opts.gittag){
|
|
166
|
-
await reporter.append(` + Git tag new version`);
|
|
167
|
-
await Git.makeTag(prj.version);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
await reporter.succses();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// ========================================================
|
|
175
|
-
// Command
|
|
176
|
-
|
|
177
|
-
async function main() {
|
|
178
|
-
|
|
179
|
-
program
|
|
180
|
-
.option('--path <project dir>', 'Path to project directory [default - current]')
|
|
181
|
-
|
|
182
|
-
.option('--no-prebuild', 'Skip prebuild phase')
|
|
183
|
-
|
|
184
|
-
.option('--no-changelog', 'Skip create gitlog')
|
|
185
|
-
.option('--no-gittag', 'Skip create and push new git tag')
|
|
186
|
-
.action( async () => { await Project.run( program.opts() || {}, runHandler ) });
|
|
187
|
-
|
|
188
|
-
await program.parseAsync(process.argv);
|
|
189
|
-
}
|
|
190
|
-
main();
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// ================================================================================================
|
|
194
|
-
// Clasess
|
|
195
|
-
|
|
196
|
-
class App{
|
|
197
|
-
|
|
198
|
-
group = '';
|
|
199
|
-
name = '';
|
|
200
|
-
app = '';
|
|
201
|
-
path = '';
|
|
202
|
-
|
|
203
|
-
$pathComp: string[] = [ './' ];
|
|
204
|
-
|
|
205
|
-
dockerImageKey = ''
|
|
206
|
-
|
|
207
|
-
k8s_tpl = ''
|
|
208
|
-
k8s_spec: Record<string, any> = {}
|
|
209
|
-
|
|
210
|
-
// ----------
|
|
211
|
-
|
|
212
|
-
constructor(path: string = '', prj: Project){
|
|
213
|
-
let pathComp = path.split('/');
|
|
214
|
-
log.dbg( 'App try load by path:'+ path, { rootPath: prj.rootPath, pathComp } )
|
|
215
|
-
|
|
216
|
-
this.path = path;
|
|
217
|
-
|
|
218
|
-
if( pathComp.length > 1 && pathComp[0] === 'src' ){
|
|
219
|
-
// Remove src folder reference
|
|
220
|
-
pathComp = pathComp.splice(1)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if( pathComp.length > 1 && pathComp[1] === 'src' ){
|
|
224
|
-
// Remove src folder reference
|
|
225
|
-
pathComp = pathComp.splice(0,-1)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if(path === prj.rootPath ){
|
|
230
|
-
this.$pathComp = [ prj.rootPath ];
|
|
231
|
-
}
|
|
232
|
-
else if(pathComp.length > 1 && pathComp[1].length > 1 ){
|
|
233
|
-
|
|
234
|
-
this.group = pathComp[0];
|
|
235
|
-
this.app = pathComp[1];
|
|
236
|
-
this.name = `${this.group}-${this.app}`;
|
|
237
|
-
this.$pathComp.push(`${ this.group }/`);
|
|
238
|
-
this.$pathComp.push(`${ this.path }/`);
|
|
239
|
-
|
|
240
|
-
}else{
|
|
241
|
-
this.app = pathComp.length == 1 ? pathComp[0] : path;
|
|
242
|
-
this.group = prj.name !== this.app ? prj.name : ''
|
|
243
|
-
|
|
244
|
-
this.name = this.group === '' ? this.app : `${this.group}-${this.app}`;
|
|
245
|
-
|
|
246
|
-
this.$pathComp = [ this.path ]
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if( this.app === ''){
|
|
250
|
-
this.group = '';
|
|
251
|
-
this.app = prj.name;
|
|
252
|
-
this.name = this.app ;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
log.dbg( this.name, this )
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// ------------------------------------------------------------------
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
export class DockerImage{
|
|
264
|
-
|
|
265
|
-
path = '';
|
|
266
|
-
key = '';
|
|
267
|
-
|
|
268
|
-
srcImage = '';
|
|
269
|
-
|
|
270
|
-
tag = '';
|
|
271
|
-
fullTag = '';
|
|
272
|
-
|
|
273
|
-
distribRun?: string;
|
|
274
|
-
|
|
275
|
-
// -----------
|
|
276
|
-
constructor( path: string, appName:string, ver: string, build: number, dockerfile?: string ){
|
|
277
|
-
if( !dockerfile ){
|
|
278
|
-
throw new Error('Wrong dockerfile: Empty! by path:'+path)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
this.path = path
|
|
282
|
-
this.key = md5( dockerfile )
|
|
283
|
-
|
|
284
|
-
const dockerImage = /FROM (.+)/.exec(dockerfile);
|
|
285
|
-
if(!dockerImage || dockerImage.length < 2){
|
|
286
|
-
throw new Error(`Wrong dockerfile format:\n${dockerfile}`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
this.srcImage = dockerImage[1];
|
|
290
|
-
|
|
291
|
-
const dockerName = /#[ ]?NAME (.+)/.exec(dockerfile);
|
|
292
|
-
const name = dockerName && dockerName.length > 1 ? dockerName[1] : appName || this.key;
|
|
293
|
-
|
|
294
|
-
// api-v1.2:#321
|
|
295
|
-
this.tag = `${name}-v${ver}:${build}`;
|
|
296
|
-
this.fullTag = `${ ENV.CI_REGISTRY_IMAGE }/${ this.tag }`;
|
|
297
|
-
|
|
298
|
-
const distribAction = /#[ ]?DISTRIB RUN (.+)/.exec(dockerfile);
|
|
299
|
-
this.distribRun = distribAction && distribAction.length > 1 ? distribAction[1] : undefined;
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// ================================================================================================
|
|
306
|
-
// Helpers:
|
|
307
|
-
|
|
308
|
-
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> }>{
|
|
309
|
-
|
|
310
|
-
const appsPath = config.appsPath ? resolvePath( prj.rootPath + '/' + config.appsPath ) : prj.rootPath
|
|
311
|
-
|
|
312
|
-
Shell.cd( appsPath );
|
|
313
|
-
|
|
314
|
-
const appFile = config.appFile || 'app.k8s.yaml'
|
|
315
|
-
|
|
316
|
-
const appsPaths = await Shell.find( appsPath, appFile, maxdepth, ignore);
|
|
317
|
-
|
|
318
|
-
log.dbg( 'Apps paths by appFile:'+ appFile, appsPaths )
|
|
319
|
-
|
|
320
|
-
const ignorePaths = [ ...config.ignorePaths || [], ...config.sharedPaths || [] ];
|
|
321
|
-
|
|
322
|
-
const apps: Record<string, App> = {};
|
|
323
|
-
const watchPaths: Record<string, string> = {};
|
|
324
|
-
|
|
325
|
-
const dockerImages: Record<string, DockerImage> = {}
|
|
326
|
-
|
|
327
|
-
// Load rootDocker Image
|
|
328
|
-
let rootDockerFileKey = ''
|
|
329
|
-
const dockerfileRootPath = resolvePath( prj.rootPath )+'/dockerfile'
|
|
330
|
-
if( await Shell.test('-e', dockerfileRootPath ) ){
|
|
331
|
-
const image = new DockerImage( dockerfileRootPath, prj.name, prj.shortVersion, prj.build, await Shell.cat( dockerfileRootPath ) );
|
|
332
|
-
dockerImages[ image.key ] = image
|
|
333
|
-
rootDockerFileKey= image.key
|
|
334
|
-
log.dbg( 'Dokerfile at root path - found! '+ image.key )
|
|
335
|
-
}
|
|
336
|
-
else{
|
|
337
|
-
log.info( 'Dokerfile at root path not found! path:'+ dockerfileRootPath )
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const k8s_tpl_fileName = 'app.k8s.yaml';
|
|
341
|
-
const k8s_spec_fileName = 'app.k8s.json';
|
|
342
|
-
|
|
343
|
-
for(const file of appsPaths.sort()) {
|
|
344
|
-
const appFullPath = resolvePath( file.replace( '/'+appFile, '') )
|
|
345
|
-
|
|
346
|
-
const path = appFullPath.replace(appsPath+'/', '').replace('/src', '')
|
|
347
|
-
|
|
348
|
-
let skip = false;
|
|
349
|
-
for(const i_path of ignorePaths){
|
|
350
|
-
if(path.includes(i_path)){
|
|
351
|
-
skip = true;
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
if(skip){
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
const app = new App(path, prj);
|
|
361
|
-
|
|
362
|
-
// ------------
|
|
363
|
-
// get app manifest
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
app.k8s_tpl = await Shell.cat( appFullPath + '/' + k8s_tpl_fileName, {silent: true, ignoreError: false }) || ''
|
|
367
|
-
|
|
368
|
-
const k8s_specFile = await Shell.cat( appFullPath + '/' + k8s_spec_fileName, {silent: true, ignoreError:true });
|
|
369
|
-
|
|
370
|
-
if(k8s_specFile && k8s_specFile !== ''){
|
|
371
|
-
try {
|
|
372
|
-
app.k8s_spec = JSON.parse( k8s_specFile ) as Record<string, any>;
|
|
373
|
-
} catch(e){
|
|
374
|
-
log.error(e);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// ------------
|
|
379
|
-
// get app dockerfile
|
|
380
|
-
|
|
381
|
-
const dockerfileAppPath = appFullPath+'/dockerfile'
|
|
382
|
-
if( app.path !== '' && await Shell.test('-e', dockerfileAppPath ) ){
|
|
383
|
-
log.info( 'Dokerfile at app "'+ app.name +'" found! path:'+ dockerfileAppPath )
|
|
384
|
-
const image = new DockerImage( dockerfileAppPath, app.name, prj.shortVersion, prj.build, await Shell.cat( dockerfileAppPath ) );
|
|
385
|
-
dockerImages[ image.key ] = image
|
|
386
|
-
app.dockerImageKey = image.key
|
|
387
|
-
}
|
|
388
|
-
else{
|
|
389
|
-
log.dbg( 'App dokerfile not found at:"'+ dockerfileAppPath +'", and set as default:'+ rootDockerFileKey )
|
|
390
|
-
app.dockerImageKey = rootDockerFileKey
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
apps[ app.name ] = app;
|
|
394
|
-
watchPaths[ app.path ] = app.name;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
log.dbg(`Find apps by ${ appFile }`, Object.keys( apps ).sort());
|
|
398
|
-
|
|
399
|
-
if( Object.keys( dockerImages ).length === 0 ){
|
|
400
|
-
throw new Exception('No any dockerfile found in project! Pleace collect it on root or app path')
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const sharedPaths = config.sharedPaths || [];
|
|
404
|
-
|
|
405
|
-
sharedPaths.push('package.json');
|
|
406
|
-
sharedPaths.push( k8s_tpl_fileName );
|
|
407
|
-
sharedPaths.push( k8s_spec_fileName );
|
|
408
|
-
sharedPaths.push('dockerfile');
|
|
409
|
-
sharedPaths.push('.gitlab-ci.yml')
|
|
410
|
-
sharedPaths.push('config.ts')
|
|
411
|
-
|
|
412
|
-
const watchPathsArr = Object.keys(watchPaths);
|
|
413
|
-
|
|
414
|
-
let appsChanged: Record<string, App> = {};
|
|
415
|
-
|
|
416
|
-
for (const file of updatedPaths) {
|
|
417
|
-
|
|
418
|
-
let allApps = false;
|
|
419
|
-
for(const i_path of sharedPaths){
|
|
420
|
-
if(file.includes(i_path)){
|
|
421
|
-
allApps = true;
|
|
422
|
-
break;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
if(allApps){
|
|
426
|
-
appsChanged = apps;
|
|
427
|
-
break;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
for(const testPath of watchPathsArr){
|
|
431
|
-
const appName = watchPaths[ testPath ];
|
|
432
|
-
if((RegExp(testPath).exec(file)) && apps[ appName ] && appsChanged[ appName ] === undefined){
|
|
433
|
-
appsChanged[ appName ] = apps[ appName ];
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const changed = Object.keys(appsChanged);
|
|
439
|
-
return { apps, updated: changed.length > 0 ? changed : undefined, images: dockerImages };
|
|
440
|
-
|
|
441
|
-
}
|
|
@@ -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( async () => { 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
|
-
}
|