1 | 'use strict';
|
2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
3 | return new (P || (P = Promise))(function (resolve, reject) {
|
4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
7 | step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8 | });
|
9 | };
|
10 | const ip = require('ip');
|
11 | const fs = require('fs-extra');
|
12 | const tar = require('tar-fs');
|
13 | const nas = require('./nas');
|
14 | const path = require('path');
|
15 | const debug = require('debug')('fun:local');
|
16 | const Docker = require('dockerode');
|
17 | const docker = new Docker();
|
18 | const dockerOpts = require('./docker-opts');
|
19 | const getVisitor = require('./visitor').getVisitor;
|
20 | const getProfile = require('./profile').getProfile;
|
21 | const { generatePwdFile } = require('./utils/passwd');
|
22 | const { blue, red, yellow } = require('colors');
|
23 | const { getRootBaseDir } = require('./tpl');
|
24 | const { parseArgsStringToArgv } = require('string-argv');
|
25 | const { extractNasMappingsFromNasYml } = require('./nas/support');
|
26 | const { addEnv, addInstallTargetEnv, resolveLibPathsFromLdConf } = require('./install/env');
|
27 | const { findPathsOutofSharedPaths } = require('./docker-support');
|
28 | const { processorTransformFactory } = require('./error-processor');
|
29 | const isWin = process.platform === 'win32';
|
30 | const _ = require('lodash');
|
31 | require('draftlog').into(console);
|
32 | var containers = new Set();
|
33 | const devnull = require('dev-null');
|
34 |
|
35 | function waitingForContainerStopped() {
|
36 |
|
37 | const isRaw = process.isRaw;
|
38 | const kpCallBack = (_char, key) => {
|
39 | if (key & key.ctrl && key.name === 'c') {
|
40 | process.emit('SIGINT');
|
41 | }
|
42 | };
|
43 | if (process.platform === 'win32') {
|
44 | if (process.stdin.isTTY) {
|
45 | process.stdin.setRawMode(isRaw);
|
46 | }
|
47 | process.stdin.on('keypress', kpCallBack);
|
48 | }
|
49 | let stopping = false;
|
50 | process.on('SIGINT', () => __awaiter(this, void 0, void 0, function* () {
|
51 | debug('containers length: ', containers.length);
|
52 | if (stopping) {
|
53 | return;
|
54 | }
|
55 |
|
56 |
|
57 |
|
58 | process.stdin.destroy();
|
59 | if (!containers.size) {
|
60 | return;
|
61 | }
|
62 | stopping = true;
|
63 | console.log(`\nreceived canncel request, stopping running containers.....`);
|
64 | const jobs = [];
|
65 | for (let container of containers) {
|
66 | try {
|
67 | if (container.destroy) {
|
68 | container.destroy();
|
69 | }
|
70 | else {
|
71 | const c = docker.getContainer(container);
|
72 | console.log(`stopping container ${container}`);
|
73 | jobs.push(c.kill().catch(ex => debug('kill container instance error, error is', ex)));
|
74 | }
|
75 | }
|
76 | catch (error) {
|
77 | debug('get container instance error, ignore container to stop, error is', error);
|
78 | }
|
79 | }
|
80 | try {
|
81 | yield Promise.all(jobs);
|
82 | console.log('all containers stopped');
|
83 | }
|
84 | catch (error) {
|
85 | console.error(error);
|
86 | process.exit(-1);
|
87 | }
|
88 | }));
|
89 | return () => {
|
90 | process.stdin.removeListener('keypress', kpCallBack);
|
91 | if (process.stdin.isTTY) {
|
92 | process.stdin.setRawMode(isRaw);
|
93 | }
|
94 | };
|
95 | }
|
96 | const goThrough = waitingForContainerStopped();
|
97 | const { generateVscodeDebugConfig, generateDebugEnv } = require('./debug');
|
98 |
|
99 | const skipPullImage = true;
|
100 | function resolveNasConfigToMounts(baseDir, serviceName, nasConfig, nasBaseDir) {
|
101 | return __awaiter(this, void 0, void 0, function* () {
|
102 | const nasMappings = yield nas.convertNasConfigToNasMappings(nasBaseDir, nasConfig, serviceName);
|
103 | return convertNasMappingsToMounts(getRootBaseDir(baseDir), nasMappings);
|
104 | });
|
105 | }
|
106 | function resolveTmpDirToMount(absTmpDir) {
|
107 | return __awaiter(this, void 0, void 0, function* () {
|
108 | if (!absTmpDir) {
|
109 | return {};
|
110 | }
|
111 | return {
|
112 | Type: 'bind',
|
113 | Source: absTmpDir,
|
114 | Target: '/tmp',
|
115 | ReadOnly: false
|
116 | };
|
117 | });
|
118 | }
|
119 | function resolveDebuggerPathToMount(debuggerPath) {
|
120 | return __awaiter(this, void 0, void 0, function* () {
|
121 | if (!debuggerPath) {
|
122 | return {};
|
123 | }
|
124 | const absDebuggerPath = path.resolve(debuggerPath);
|
125 | return {
|
126 | Type: 'bind',
|
127 | Source: absDebuggerPath,
|
128 | Target: '/tmp/debugger_files',
|
129 | ReadOnly: false
|
130 | };
|
131 | });
|
132 | }
|
133 |
|
134 | function resolveCodeUriToMount(absCodeUri, readOnly = true) {
|
135 | return __awaiter(this, void 0, void 0, function* () {
|
136 | let target = null;
|
137 | const stats = yield fs.lstat(absCodeUri);
|
138 | if (stats.isDirectory()) {
|
139 | target = '/code';
|
140 | }
|
141 | else {
|
142 |
|
143 |
|
144 | target = path.posix.join('/code', path.basename(absCodeUri));
|
145 | }
|
146 |
|
147 | return {
|
148 | Type: 'bind',
|
149 | Source: absCodeUri,
|
150 | Target: target,
|
151 | ReadOnly: readOnly
|
152 | };
|
153 | });
|
154 | }
|
155 | function resolvePasswdMount() {
|
156 | return __awaiter(this, void 0, void 0, function* () {
|
157 | if (process.platform === 'linux') {
|
158 | return {
|
159 | Type: 'bind',
|
160 | Source: yield generatePwdFile(),
|
161 | Target: '/etc/passwd',
|
162 | ReadOnly: true
|
163 | };
|
164 | }
|
165 | return null;
|
166 | });
|
167 | }
|
168 | function convertNasMappingsToMounts(baseDir, nasMappings) {
|
169 | return nasMappings.map(nasMapping => {
|
170 | console.log('mounting local nas mock dir %s into container %s\n', nasMapping.localNasDir, nasMapping.remoteNasDir);
|
171 | return {
|
172 | Type: 'bind',
|
173 | Source: path.resolve(baseDir, nasMapping.localNasDir),
|
174 | Target: nasMapping.remoteNasDir,
|
175 | ReadOnly: false
|
176 | };
|
177 | });
|
178 | }
|
179 | function resolveNasYmlToMount(baseDir, serviceName) {
|
180 | return __awaiter(this, void 0, void 0, function* () {
|
181 | const nasMappings = yield extractNasMappingsFromNasYml(baseDir, serviceName);
|
182 | return convertNasMappingsToMounts(getRootBaseDir(baseDir), nasMappings);
|
183 | });
|
184 | }
|
185 | function conventInstallTargetsToMounts(installTargets) {
|
186 | if (!installTargets) {
|
187 | return [];
|
188 | }
|
189 | const mounts = [];
|
190 | _.forEach(installTargets, (target) => {
|
191 | const { hostPath, containerPath } = target;
|
192 | if (!(fs.pathExistsSync(hostPath))) {
|
193 | fs.ensureDirSync(hostPath);
|
194 | }
|
195 | mounts.push({
|
196 | Type: 'bind',
|
197 | Source: hostPath,
|
198 | Target: containerPath,
|
199 | ReadOnly: false
|
200 | });
|
201 | });
|
202 | return mounts;
|
203 | }
|
204 | function imageExist(imageName) {
|
205 | return __awaiter(this, void 0, void 0, function* () {
|
206 | const images = yield docker.listImages({
|
207 | filters: {
|
208 | reference: [imageName]
|
209 | }
|
210 | });
|
211 | return images.length > 0;
|
212 | });
|
213 | }
|
214 | function listContainers(options) {
|
215 | return __awaiter(this, void 0, void 0, function* () {
|
216 | return yield docker.listContainers(options);
|
217 | });
|
218 | }
|
219 | function getContainer(containerId) {
|
220 | return __awaiter(this, void 0, void 0, function* () {
|
221 | return yield docker.getContainer(containerId);
|
222 | });
|
223 | }
|
224 | function renameContainer(container, name) {
|
225 | return __awaiter(this, void 0, void 0, function* () {
|
226 | return yield container.rename({
|
227 | name
|
228 | });
|
229 | });
|
230 | }
|
231 |
|
232 | function generateDockerCmd(functionProps, httpMode, invokeInitializer = true, event = null) {
|
233 | const cmd = ['-h', functionProps.Handler];
|
234 |
|
235 | if (event !== null) {
|
236 | cmd.push('--event', Buffer.from(event).toString('base64'));
|
237 | cmd.push('--event-decode');
|
238 | }
|
239 | else {
|
240 |
|
241 | cmd.push('--stdin');
|
242 | }
|
243 | if (httpMode) {
|
244 | cmd.push('--http');
|
245 | }
|
246 | const initializer = functionProps.Initializer;
|
247 | if (initializer && invokeInitializer) {
|
248 | cmd.push('-i', initializer);
|
249 | }
|
250 | const initializationTimeout = functionProps.InitializationTimeout;
|
251 |
|
252 | if (initializationTimeout) {
|
253 | cmd.push('--initializationTimeout', initializationTimeout.toString());
|
254 | }
|
255 | debug(`docker cmd: ${cmd}`);
|
256 | return cmd;
|
257 | }
|
258 | function followProgress(stream, onFinished) {
|
259 | const barLines = {};
|
260 | const onProgress = (event) => {
|
261 | let status = event.status;
|
262 | if (event.progress) {
|
263 | status = `${event.status} ${event.progress}`;
|
264 | }
|
265 | if (event.id) {
|
266 | const id = event.id;
|
267 | if (!barLines[id]) {
|
268 | barLines[id] = console.draft();
|
269 | }
|
270 | barLines[id](id + ': ' + status);
|
271 | }
|
272 | else {
|
273 | if (_.has(event, 'aux.ID')) {
|
274 | event.stream = event.aux.ID + '\n';
|
275 | }
|
276 |
|
277 | const out = event.status ? event.status + '\n' : event.stream;
|
278 | process.stdout.write(out);
|
279 | }
|
280 | };
|
281 | docker.modem.followProgress(stream, onFinished, onProgress);
|
282 | }
|
283 | function pullImage(imageName) {
|
284 | return __awaiter(this, void 0, void 0, function* () {
|
285 | const resolveImageName = yield dockerOpts.resolveImageNameForPull(imageName);
|
286 |
|
287 | const startTime = new Date();
|
288 | const stream = yield docker.pull(resolveImageName);
|
289 | const visitor = yield getVisitor();
|
290 | visitor.event({
|
291 | ec: 'image',
|
292 | ea: 'pull',
|
293 | el: 'start'
|
294 | }).send();
|
295 | const registry = yield dockerOpts.resolveDockerRegistry();
|
296 | return yield new Promise((resolve, reject) => {
|
297 | console.log(`begin pulling image ${resolveImageName}, you can also use ` + yellow(`'docker pull ${resolveImageName}'`) + ' to pull image by yourself.');
|
298 | const onFinished = (err) => __awaiter(this, void 0, void 0, function* () {
|
299 | containers.delete(stream);
|
300 | const pullDuration = parseInt((new Date() - startTime) / 1000);
|
301 | if (err) {
|
302 | visitor.event({
|
303 | ec: 'image',
|
304 | ea: 'pull',
|
305 | el: 'error'
|
306 | }).send();
|
307 | visitor.event({
|
308 | ec: 'image',
|
309 | ea: `pull from ${registry}`,
|
310 | el: 'error'
|
311 | }).send();
|
312 | visitor.event({
|
313 | ec: `image pull from ${registry}`,
|
314 | ea: `used ${pullDuration}`,
|
315 | el: 'error'
|
316 | }).send();
|
317 | reject(err);
|
318 | return;
|
319 | }
|
320 | visitor.event({
|
321 | ec: 'image',
|
322 | ea: `pull from ${registry}`,
|
323 | el: 'success'
|
324 | }).send();
|
325 | visitor.event({
|
326 | ec: 'image',
|
327 | ea: 'pull',
|
328 | el: 'success'
|
329 | }).send();
|
330 | visitor.event({
|
331 | ec: `image pull from ${registry}`,
|
332 | ea: `used ${pullDuration}`,
|
333 | el: 'success'
|
334 | }).send();
|
335 | for (const r of dockerOpts.DOCKER_REGISTRIES) {
|
336 | if (resolveImageName.indexOf(r) === 0) {
|
337 | const image = yield docker.getImage(resolveImageName);
|
338 | const newImageName = resolveImageName.slice(r.length + 1);
|
339 | const repoTag = newImageName.split(':');
|
340 |
|
341 | yield image.tag({
|
342 | name: resolveImageName,
|
343 | repo: _.first(repoTag),
|
344 | tag: _.last(repoTag)
|
345 | });
|
346 | break;
|
347 | }
|
348 | }
|
349 | resolve(resolveImageName);
|
350 | });
|
351 | containers.add(stream);
|
352 |
|
353 | followProgress(stream, onFinished);
|
354 | });
|
355 | });
|
356 | }
|
357 | function generateFunctionEnvs(functionProps) {
|
358 | const environmentVariables = functionProps.EnvironmentVariables;
|
359 | if (!environmentVariables) {
|
360 | return {};
|
361 | }
|
362 | return Object.assign({}, environmentVariables);
|
363 | }
|
364 | function generateRamdomContainerName() {
|
365 | return `fun_local_${new Date().getTime()}_${Math.random().toString(36).substr(2, 7)}`;
|
366 | }
|
367 | function generateDockerEnvs(baseDir, serviceName, serviceProps, functionName, functionProps, debugPort, httpParams, nasConfig, ishttpTrigger, debugIde, debugArgs) {
|
368 | return __awaiter(this, void 0, void 0, function* () {
|
369 | const envs = {};
|
370 | if (httpParams) {
|
371 | Object.assign(envs, {
|
372 | 'FC_HTTP_PARAMS': httpParams
|
373 | });
|
374 | }
|
375 | const confEnv = yield resolveLibPathsFromLdConf(baseDir, functionProps.CodeUri);
|
376 | Object.assign(envs, confEnv);
|
377 | const runtime = functionProps.Runtime;
|
378 | if (debugPort && !debugArgs) {
|
379 | const debugEnv = generateDebugEnv(runtime, debugPort, debugIde);
|
380 | Object.assign(envs, debugEnv);
|
381 | }
|
382 | else if (debugArgs) {
|
383 | Object.assign(envs, {
|
384 | DEBUG_OPTIONS: debugArgs
|
385 | });
|
386 | }
|
387 | if (ishttpTrigger && runtime === 'java8') {
|
388 | envs['fc_enable_new_java_ca'] = 'true';
|
389 | }
|
390 | Object.assign(envs, generateFunctionEnvs(functionProps));
|
391 | const profile = yield getProfile();
|
392 | Object.assign(envs, {
|
393 | 'local': true,
|
394 | 'FC_ACCESS_KEY_ID': profile.accessKeyId,
|
395 | 'FC_ACCESS_KEY_SECRET': profile.accessKeySecret,
|
396 | 'FC_ACCOUND_ID': profile.accountId,
|
397 | 'FC_REGION': profile.defaultRegion,
|
398 | 'FC_FUNCTION_NAME': functionName,
|
399 | 'FC_HANDLER': functionProps.Handler,
|
400 | 'FC_MEMORY_SIZE': functionProps.MemorySize || 128,
|
401 | 'FC_TIMEOUT': functionProps.Timeout || 3,
|
402 | 'FC_INITIALIZER': functionProps.Initializer,
|
403 | 'FC_INITIALIZATIONIMEOUT': functionProps.InitializationTimeout || 3,
|
404 | 'FC_SERVICE_NAME': serviceName,
|
405 | 'FC_SERVICE_LOG_PROJECT': ((serviceProps || {}).LogConfig || {}).Project,
|
406 | 'FC_SERVICE_LOG_STORE': ((serviceProps || {}).LogConfig || {}).Logstore
|
407 | });
|
408 | return addEnv(envs, nasConfig);
|
409 | });
|
410 | }
|
411 | function pullImageIfNeed(imageName) {
|
412 | return __awaiter(this, void 0, void 0, function* () {
|
413 | const exist = yield imageExist(imageName);
|
414 | if (!exist || !skipPullImage) {
|
415 | yield pullImage(imageName);
|
416 | }
|
417 | else {
|
418 | debug(`skip pulling image ${imageName}...`);
|
419 | console.log(`skip pulling image ${imageName}...`);
|
420 | }
|
421 | });
|
422 | }
|
423 | function showDebugIdeTipsForVscode(serviceName, functionName, runtime, codeSource, debugPort) {
|
424 | return __awaiter(this, void 0, void 0, function* () {
|
425 | const vscodeDebugConfig = yield generateVscodeDebugConfig(serviceName, functionName, runtime, codeSource, debugPort);
|
426 |
|
427 | console.log(blue('you can paste these config to .vscode/launch.json, and then attach to your running function'));
|
428 | console.log('///////////////// config begin /////////////////');
|
429 | console.log(JSON.stringify(vscodeDebugConfig, null, 4));
|
430 | console.log('///////////////// config end /////////////////');
|
431 | });
|
432 | }
|
433 | function showDebugIdeTipsForPycharm(codeSource, debugPort) {
|
434 | return __awaiter(this, void 0, void 0, function* () {
|
435 | const stats = yield fs.lstat(codeSource);
|
436 | if (!stats.isDirectory()) {
|
437 | codeSource = path.dirname(codeSource);
|
438 | }
|
439 | console.log(yellow(`\n========= Tips for PyCharm remote debug =========
|
440 | Local host name: ${ip.address()}
|
441 | Port : ${yellow(debugPort)}
|
442 | Path mappings : ${yellow(codeSource)}=/code
|
443 |
|
444 | Debug Code needed to copy to your function code:
|
445 |
|
446 | import pydevd
|
447 | pydevd.settrace('${ip.address()}', port=${debugPort}, stdoutToServer=True, stderrToServer=True)
|
448 |
|
449 | =========================================================================\n`));
|
450 | });
|
451 | }
|
452 | function writeEventToStreamAndClose(stream, event) {
|
453 | if (event) {
|
454 | stream.write(event);
|
455 | }
|
456 | stream.end();
|
457 | }
|
458 | function isDockerToolBoxAndEnsureDockerVersion() {
|
459 | return __awaiter(this, void 0, void 0, function* () {
|
460 | const dockerInfo = yield docker.info();
|
461 | yield detectDockerVersion(dockerInfo.ServerVersion || '');
|
462 | const obj = (dockerInfo.Labels || []).map(e => _.split(e, '=', 2))
|
463 | .filter(e => e.length === 2)
|
464 | .reduce((acc, cur) => (acc[cur[0]] = cur[1], acc), {});
|
465 | return process.platform === 'win32' && obj.provider === 'virtualbox';
|
466 | });
|
467 | }
|
468 | function run(opts, event, outputStream, errorStream, context = {}) {
|
469 | return __awaiter(this, void 0, void 0, function* () {
|
470 | const container = yield createContainer(opts);
|
471 | const attachOpts = {
|
472 | hijack: true,
|
473 | stream: true,
|
474 | stdin: true,
|
475 | stdout: true,
|
476 | stderr: true
|
477 | };
|
478 | const stream = yield container.attach(attachOpts);
|
479 | if (!outputStream) {
|
480 | outputStream = process.stdout;
|
481 | }
|
482 | if (!errorStream) {
|
483 | errorStream = process.stderr;
|
484 | }
|
485 | const errorTransform = processorTransformFactory({
|
486 | serviceName: context.serviceName,
|
487 | functionName: context.functionName,
|
488 | errorStream: errorStream
|
489 | });
|
490 | if (!isWin) {
|
491 | container.modem.demuxStream(stream, outputStream, errorTransform);
|
492 | }
|
493 | yield container.start();
|
494 |
|
495 | if (isWin) {
|
496 | const logStream = yield container.logs({
|
497 | stdout: true,
|
498 | stderr: true,
|
499 | follow: true
|
500 | });
|
501 | container.modem.demuxStream(logStream, outputStream, errorTransform);
|
502 | }
|
503 | containers.add(container.id);
|
504 | writeEventToStreamAndClose(stream, event);
|
505 |
|
506 |
|
507 | const exitRs = yield container.wait();
|
508 | containers.delete(container.id);
|
509 | return exitRs;
|
510 | });
|
511 | }
|
512 | function createContainer(opts) {
|
513 | return __awaiter(this, void 0, void 0, function* () {
|
514 | const isWin = process.platform === 'win32';
|
515 | const isMac = process.platform === 'darwin';
|
516 | if (opts && isMac) {
|
517 | if (opts.HostConfig) {
|
518 | const pathsOutofSharedPaths = yield findPathsOutofSharedPaths(opts.HostConfig.Mounts);
|
519 | if (isMac && pathsOutofSharedPaths.length > 0) {
|
520 | throw new Error(red(`Please add directory '${pathsOutofSharedPaths}' to Docker File sharing list, more information please refer to https://github.com/alibaba/funcraft/blob/master/docs/usage/faq-zh.md`));
|
521 | }
|
522 | }
|
523 | }
|
524 | const dockerToolBox = yield isDockerToolBoxAndEnsureDockerVersion();
|
525 | let container;
|
526 | try {
|
527 |
|
528 | container = yield docker.createContainer(opts);
|
529 | }
|
530 | catch (ex) {
|
531 | if (ex.message.indexOf('invalid mount config for type') !== -1 && dockerToolBox) {
|
532 | throw new Error(red(`The default host machine path for docker toolbox is under 'C:\\Users', Please make sure your project is in this directory. If you want to mount other disk paths, please refer to https://github.com/alibaba/funcraft/blob/master/docs/usage/faq-zh.md .`));
|
533 | }
|
534 | if (ex.message.indexOf('drive is not shared') !== -1 && isWin) {
|
535 | throw new Error(red(`${ex.message}More information please refer to https://docs.docker.com/docker-for-windows/#shared-drives`));
|
536 | }
|
537 | throw ex;
|
538 | }
|
539 | return container;
|
540 | });
|
541 | }
|
542 | function createAndRunContainer(opts) {
|
543 | return __awaiter(this, void 0, void 0, function* () {
|
544 | const container = yield createContainer(opts);
|
545 | containers.add(container.id);
|
546 | yield container.start({});
|
547 | return container;
|
548 | });
|
549 | }
|
550 | function execContainer(container, opts, outputStream, errorStream) {
|
551 | return __awaiter(this, void 0, void 0, function* () {
|
552 | outputStream = process.stdout;
|
553 | errorStream = process.stderr;
|
554 | const logStream = yield container.logs({
|
555 | stdout: true,
|
556 | stderr: true,
|
557 | follow: true,
|
558 | since: (new Date().getTime() / 1000)
|
559 | });
|
560 | container.modem.demuxStream(logStream, outputStream, errorStream);
|
561 | const exec = yield container.exec(opts);
|
562 | const stream = yield exec.start();
|
563 |
|
564 | yield new Promise(resolve => setTimeout(resolve, 30));
|
565 | container.modem.demuxStream(stream, outputStream, errorStream);
|
566 | yield waitForExec(exec);
|
567 | logStream.destroy();
|
568 | });
|
569 | }
|
570 | function waitForExec(exec) {
|
571 | return __awaiter(this, void 0, void 0, function* () {
|
572 | return yield new Promise((resolve, reject) => {
|
573 |
|
574 |
|
575 | function waitContainerExec() {
|
576 | exec.inspect((err, data) => {
|
577 | if (data.Running) {
|
578 | setTimeout(waitContainerExec, 100);
|
579 | return;
|
580 | }
|
581 | if (err) {
|
582 | reject(err);
|
583 | }
|
584 | else if (data.ExitCode !== 0) {
|
585 | reject(`${data.ProcessConfig.entrypoint} exited with code ${data.ExitCode}`);
|
586 | }
|
587 | else {
|
588 | resolve(data.ExitCode);
|
589 | }
|
590 | });
|
591 | }
|
592 | waitContainerExec();
|
593 | });
|
594 | });
|
595 | }
|
596 |
|
597 |
|
598 | function startContainer(opts, outputStream, errorStream, context = {}) {
|
599 | return __awaiter(this, void 0, void 0, function* () {
|
600 | const container = yield createContainer(opts);
|
601 | containers.add(container.id);
|
602 | try {
|
603 | yield container.start({});
|
604 | }
|
605 | catch (err) {
|
606 | console.error(err);
|
607 | }
|
608 | const logs = outputStream || errorStream;
|
609 | if (logs) {
|
610 | if (!outputStream) {
|
611 | outputStream = devnull();
|
612 | }
|
613 | if (!errorStream) {
|
614 | errorStream = devnull();
|
615 | }
|
616 |
|
617 | const logStream = yield container.logs({
|
618 | stdout: true,
|
619 | stderr: true,
|
620 | follow: true
|
621 | });
|
622 | container.modem.demuxStream(logStream, outputStream, processorTransformFactory({
|
623 | serviceName: context.serviceName,
|
624 | functionName: context.functionName,
|
625 | errorStream
|
626 | }));
|
627 | }
|
628 | return {
|
629 | stop: () => __awaiter(this, void 0, void 0, function* () {
|
630 | yield container.stop();
|
631 | containers.delete(container.id);
|
632 | }),
|
633 | exec: (cmd, { cwd = '', env = {}, outputStream, errorStream, verbose = false, context = {}, event = null } = {}) => __awaiter(this, void 0, void 0, function* () {
|
634 | const stdin = event ? true : false;
|
635 | const options = {
|
636 | Cmd: cmd,
|
637 | Env: dockerOpts.resolveDockerEnv(env),
|
638 | Tty: false,
|
639 | AttachStdin: stdin,
|
640 | AttachStdout: true,
|
641 | AttachStderr: true,
|
642 | WorkingDir: cwd
|
643 | };
|
644 |
|
645 | debug('docker exec opts: ' + JSON.stringify(options, null, 4));
|
646 | const exec = yield container.exec(options);
|
647 | const stream = yield exec.start({ hijack: true, stdin });
|
648 |
|
649 | yield new Promise(resolve => setTimeout(resolve, 30));
|
650 | if (event !== null) {
|
651 | writeEventToStreamAndClose(stream, event);
|
652 | }
|
653 | if (!outputStream) {
|
654 | outputStream = process.stdout;
|
655 | }
|
656 | if (!errorStream) {
|
657 | errorStream = process.stderr;
|
658 | }
|
659 | if (verbose) {
|
660 | container.modem.demuxStream(stream, outputStream, errorStream);
|
661 | }
|
662 | else {
|
663 | container.modem.demuxStream(stream, devnull(), errorStream);
|
664 | }
|
665 | return yield waitForExec(exec);
|
666 | })
|
667 | };
|
668 | });
|
669 | }
|
670 | function startInstallationContainer({ runtime, imageName, codeUri, targets, context }) {
|
671 | return __awaiter(this, void 0, void 0, function* () {
|
672 | debug(`runtime: ${runtime}`);
|
673 | debug(`codeUri: ${codeUri}`);
|
674 | if (yield isDockerToolBoxAndEnsureDockerVersion()) {
|
675 | throw new Error(red(`\nWe detected that you are using docker toolbox. For a better experience, please upgrade 'docker for windows'.\nYou can refer to Chinese doc https://github.com/alibaba/funcraft/blob/master/docs/usage/installation-zh.md#windows-%E5%AE%89%E8%A3%85-docker or English doc https://github.com/alibaba/funcraft/blob/master/docs/usage/installation.md.`));
|
676 | }
|
677 | if (!imageName) {
|
678 | imageName = yield dockerOpts.resolveRuntimeToDockerImage(runtime, true);
|
679 | if (!imageName) {
|
680 | throw new Error(`invalid runtime name ${runtime}`);
|
681 | }
|
682 | }
|
683 | const codeMount = yield resolveCodeUriToMount(codeUri, false);
|
684 | const installMounts = conventInstallTargetsToMounts(targets);
|
685 | const passwdMount = yield resolvePasswdMount();
|
686 | const mounts = _.compact([codeMount, ...installMounts, passwdMount]);
|
687 | yield pullImageIfNeed(imageName);
|
688 | const envs = addInstallTargetEnv({}, targets);
|
689 | const opts = dockerOpts.generateInstallOpts(imageName, mounts, envs);
|
690 | return yield startContainer(opts);
|
691 | });
|
692 | }
|
693 | function displaySboxTips(runtime) {
|
694 | console.log(yellow(`\nWelcom to fun sbox environment.\n`));
|
695 | console.log(yellow(`You can install system dependencies like this:`));
|
696 | console.log(yellow(`fun-install apt-get install libxss1\n`));
|
697 | switch (runtime) {
|
698 | case 'nodejs6':
|
699 | case 'nodejs8':
|
700 | case 'nodejs10':
|
701 | console.log(yellow(`You can install node modules like this:`));
|
702 | console.log(yellow(`fun-install npm install puppeteer\n`));
|
703 | break;
|
704 | case 'python2.7':
|
705 | case 'python3':
|
706 | console.log(yellow(`You can install pip dependencies like this:`));
|
707 | console.log(yellow(`fun-install pip install flask`));
|
708 | break;
|
709 | default:
|
710 | break;
|
711 | }
|
712 | console.log(yellow('type \'fun-install --help\' for more help\n'));
|
713 | }
|
714 | function startSboxContainer({ runtime, imageName, mounts, cmd, envs, isTty, isInteractive }) {
|
715 | return __awaiter(this, void 0, void 0, function* () {
|
716 | debug(`runtime: ${runtime}`);
|
717 | debug(`mounts: ${mounts}`);
|
718 | debug(`isTty: ${isTty}`);
|
719 | debug(`isInteractive: ${isInteractive}`);
|
720 | if (!imageName) {
|
721 | imageName = yield dockerOpts.resolveRuntimeToDockerImage(runtime, true);
|
722 | if (!imageName) {
|
723 | throw new Error(`invalid runtime name ${runtime}`);
|
724 | }
|
725 | }
|
726 | debug(`cmd: ${parseArgsStringToArgv(cmd || '')}`);
|
727 | const container = yield createContainer(dockerOpts.generateSboxOpts({
|
728 | imageName,
|
729 | hostname: `fc-${runtime}`,
|
730 | mounts,
|
731 | envs,
|
732 | cmd: parseArgsStringToArgv(cmd || ''),
|
733 | isTty,
|
734 | isInteractive
|
735 | }));
|
736 | containers.add(container.id);
|
737 | yield container.start();
|
738 | const stream = yield container.attach({
|
739 | logs: true,
|
740 | stream: true,
|
741 | stdin: isInteractive,
|
742 | stdout: true,
|
743 | stderr: true
|
744 | });
|
745 |
|
746 | let logStream;
|
747 | if (isTty) {
|
748 | stream.pipe(process.stdout);
|
749 | }
|
750 | else {
|
751 | if (isInteractive || process.platform === 'win32') {
|
752 |
|
753 | logStream = yield container.logs({
|
754 | stdout: true,
|
755 | stderr: true,
|
756 | follow: true
|
757 | });
|
758 | container.modem.demuxStream(logStream, process.stdout, process.stderr);
|
759 | }
|
760 | else {
|
761 | container.modem.demuxStream(stream, process.stdout, process.stderr);
|
762 | }
|
763 | }
|
764 | if (isInteractive) {
|
765 | displaySboxTips(runtime);
|
766 |
|
767 | process.stdin.pipe(stream);
|
768 | let previousKey;
|
769 | const CTRL_P = '\u0010', CTRL_Q = '\u0011';
|
770 | process.stdin.on('data', (key) => {
|
771 |
|
772 | const keyStr = key.toString('ascii');
|
773 | if (previousKey === CTRL_P && keyStr === CTRL_Q) {
|
774 | container.stop(() => { });
|
775 | }
|
776 | previousKey = keyStr;
|
777 | });
|
778 | }
|
779 | let resize;
|
780 | const isRaw = process.isRaw;
|
781 | if (isTty) {
|
782 |
|
783 | goThrough();
|
784 | process.stdin.setRawMode(true);
|
785 | resize = () => __awaiter(this, void 0, void 0, function* () {
|
786 | const dimensions = {
|
787 | h: process.stdout.rows,
|
788 | w: process.stdout.columns
|
789 | };
|
790 | if (dimensions.h !== 0 && dimensions.w !== 0) {
|
791 | yield container.resize(dimensions);
|
792 | }
|
793 | });
|
794 | yield resize();
|
795 | process.stdout.on('resize', resize);
|
796 |
|
797 |
|
798 | stream.write(' \b');
|
799 | }
|
800 | yield container.wait();
|
801 |
|
802 | if (isTty) {
|
803 | process.stdout.removeListener('resize', resize);
|
804 | process.stdin.setRawMode(isRaw);
|
805 | }
|
806 | if (isInteractive) {
|
807 | process.stdin.removeAllListeners();
|
808 | process.stdin.unpipe(stream);
|
809 | |
810 |
|
811 |
|
812 |
|
813 | process.stdin.destroy();
|
814 | }
|
815 | if (logStream) {
|
816 | logStream.removeAllListeners();
|
817 | }
|
818 | stream.unpipe(process.stdout);
|
819 |
|
820 |
|
821 | stream.destroy();
|
822 | containers.delete(container.id);
|
823 | if (!isTty) {
|
824 | goThrough();
|
825 | }
|
826 | });
|
827 | }
|
828 | function zipTo(archive, to) {
|
829 | return __awaiter(this, void 0, void 0, function* () {
|
830 | yield fs.ensureDir(to);
|
831 | yield new Promise((resolve, reject) => {
|
832 | archive.pipe(tar.extract(to)).on('error', reject).on('finish', resolve);
|
833 | });
|
834 | });
|
835 | }
|
836 | function copyFromImage(imageName, from, to) {
|
837 | return __awaiter(this, void 0, void 0, function* () {
|
838 | const container = yield docker.createContainer({
|
839 | Image: imageName
|
840 | });
|
841 | const archive = yield container.getArchive({
|
842 | path: from
|
843 | });
|
844 | yield zipTo(archive, to);
|
845 | yield container.remove();
|
846 | });
|
847 | }
|
848 | function buildImage(dockerBuildDir, dockerfilePath, imageTag) {
|
849 | return new Promise((resolve, reject) => {
|
850 | var tarStream = tar.pack(dockerBuildDir);
|
851 | docker.buildImage(tarStream, {
|
852 | dockerfile: path.relative(dockerBuildDir, dockerfilePath),
|
853 | t: imageTag
|
854 | }, (error, stream) => {
|
855 | containers.add(stream);
|
856 | if (error) {
|
857 | reject(error);
|
858 | return;
|
859 | }
|
860 | stream.on('error', (e) => {
|
861 | containers.delete(stream);
|
862 | reject(e);
|
863 | return;
|
864 | });
|
865 | stream.on('end', function () {
|
866 | containers.delete(stream);
|
867 | resolve(imageTag);
|
868 | return;
|
869 | });
|
870 | followProgress(stream, (err, res) => err ? reject(err) : resolve(res));
|
871 | });
|
872 | });
|
873 | }
|
874 | function detectDockerVersion(serverVersion) {
|
875 | return __awaiter(this, void 0, void 0, function* () {
|
876 | let cur = serverVersion.split('.');
|
877 |
|
878 | if (Number.parseInt(cur[0]) === 1 && Number.parseInt(cur[1]) <= 13) {
|
879 | throw new Error(red(`\nWe detected that your docker version is ${serverVersion}, for a better experience, please upgrade the docker version.`));
|
880 | }
|
881 | });
|
882 | }
|
883 | module.exports = {
|
884 | imageExist, generateDockerCmd,
|
885 | pullImage,
|
886 | resolveCodeUriToMount, generateFunctionEnvs, run, generateRamdomContainerName,
|
887 | generateDockerEnvs, pullImageIfNeed,
|
888 | showDebugIdeTipsForVscode, resolveNasConfigToMounts,
|
889 | startInstallationContainer, startContainer, isDockerToolBoxAndEnsureDockerVersion,
|
890 | conventInstallTargetsToMounts, startSboxContainer, buildImage, copyFromImage,
|
891 | resolveTmpDirToMount, showDebugIdeTipsForPycharm, resolveDebuggerPathToMount,
|
892 | listContainers, getContainer, createAndRunContainer, execContainer,
|
893 | renameContainer, detectDockerVersion, resolveNasYmlToMount, resolvePasswdMount
|
894 | };
|