UNPKG

25.2 kBJavaScriptView Raw
1'use strict';
2
3const getProfile = require('./profile').getProfile;
4const fs = require('fs-extra');
5const path = require('path');
6const { blue, red, yellow } = require('colors');
7const debug = require('debug')('fun:local');
8const Docker = require('dockerode');
9const docker = new Docker();
10const dockerOpts = require('./docker-opts');
11const getVisitor = require('./visitor').getVisitor;
12const definition = require('./definition');
13const nas = require('./nas');
14const { parseArgsStringToArgv } = require('string-argv');
15const { addEnv, addInstallTargetEnv, resolveLibPathsFromLdConf } = require('./install/env');
16const { findPathsOutofSharedPaths } = require('./docker-support');
17const ip = require('ip');
18const tar = require('tar-fs');
19
20const _ = require('lodash');
21
22require('draftlog').into(console);
23
24var containers = new Set();
25
26const devnull = require('dev-null');
27
28// exit container, when use ctrl + c
29function waitingForContainerStopped() {
30 // see https://stackoverflow.com/questions/10021373/what-is-the-windows-equivalent-of-process-onsigint-in-node-js
31 const isRaw = process.isRaw;
32 const kpCallBack = (_char, key) => {
33 if (key & key.ctrl && key.name === 'c') {
34 process.emit('SIGINT');
35 }
36 };
37 if (process.platform === 'win32') {
38 if (process.stdin.isTTY) {
39 process.stdin.setRawMode(isRaw);
40 }
41 process.stdin.on('keypress', kpCallBack);
42 }
43
44 let stopping = false;
45
46 process.on('SIGINT', async () => {
47
48 debug('containers length: ', containers.length);
49
50 if (stopping) {
51 return;
52 }
53
54 // Just fix test on windows
55 // Because process.emit('SIGINT') in test/docker.test.js will not trigger rl.on('SIGINT')
56 // And when listening to stdin the process never finishes until you send a SIGINT signal explicitly.
57 process.stdin.destroy();
58
59 if (!containers.size) {
60 return;
61 }
62
63 stopping = true;
64
65 console.log(`\nreceived canncel request, stopping running containers.....`);
66
67 const jobs = [];
68
69 for (let container of containers) {
70 try {
71 if (container.destroy) { // container stream
72 container.destroy();
73 } else {
74 const c = docker.getContainer(container);
75
76 await c.inspect();
77
78 console.log(`stopping container ${container}`);
79
80 jobs.push(c.stop());
81 }
82 } catch (error) {
83 debug('get container instance error, ignore container to stop, error is', error);
84 }
85 }
86
87 try {
88 await Promise.all(jobs);
89 console.log('all containers stopped');
90 } catch (error) {
91 console.error(error);
92 process.exit(-1); // eslint-disable-line
93 }
94 });
95
96 return () => {
97 process.stdin.removeListener('keypress', kpCallBack);
98 if (process.stdin.isTTY) {
99 process.stdin.setRawMode(isRaw);
100 }
101 };
102}
103
104const goThrough = waitingForContainerStopped();
105
106const {
107 generateVscodeDebugConfig, generateDebugEnv
108} = require('./debug');
109
110// todo: add options for pull latest image
111const skipPullImage = true;
112
113async function resolveNasConfigToMounts(serviceName, nasConfig, nasBaseDir) {
114
115 const nasMappings = await nas.convertNasConfigToNasMappings(nasBaseDir, nasConfig, serviceName);
116
117 return nasMappings.map(nasMapping => {
118 debug('mounting local nas mock dir %s into container %s\n', nasMapping.localNasDir, nasMapping.remoteNasDir);
119 return {
120 Type: 'bind',
121 Source: nasMapping.localNasDir,
122 Target: nasMapping.remoteNasDir,
123 ReadOnly: false
124 };
125 });
126
127}
128
129async function resolveTmpDirToMount(absTmpDir) {
130 if (!absTmpDir) { return {}; }
131 return {
132 Type: 'bind',
133 Source: absTmpDir,
134 Target: '/tmp',
135 ReadOnly: false
136 };
137}
138
139async function resolveDebuggerPathToMount(debuggerPath) {
140 if (!debuggerPath) { return {}; }
141 const absDebuggerPath = path.resolve(debuggerPath);
142 return {
143 Type: 'bind',
144 Source: absDebuggerPath,
145 Target: '/tmp/debugger_files',
146 ReadOnly: false
147 };
148}
149
150// todo: 当前只支持目录以及 jar。code uri 还可能是 oss 地址、目录、jar、zip?
151async function resolveCodeUriToMount(absCodeUri, readOnly = true) {
152 let target = null;
153
154 const stats = await fs.lstat(absCodeUri);
155
156 if (stats.isDirectory()) {
157 target = '/code';
158 } else {
159 // could not use path.join('/code', xxx)
160 // in windows, it will be translate to \code\xxx, and will not be recorgnized as a valid path in linux container
161 target = path.posix.join('/code', path.basename(absCodeUri));
162 }
163
164 // Mount the code directory as read only
165 return {
166 Type: 'bind',
167 Source: absCodeUri,
168 Target: target,
169 ReadOnly: readOnly
170 };
171}
172
173function conventInstallTargetsToMounts(installTargets) {
174
175 if (!installTargets) { return []; }
176
177 const mounts = [];
178
179 _.forEach(installTargets, (target) => {
180 const { hostPath, containerPath } = target;
181
182 if (!(fs.pathExistsSync(hostPath))) {
183 fs.ensureDirSync(hostPath);
184 }
185
186 mounts.push({
187 Type: 'bind',
188 Source: hostPath,
189 Target: containerPath,
190 ReadOnly: false
191 });
192 });
193
194 return mounts;
195}
196
197async function imageExist(imageName) {
198
199 const images = await docker.listImages({
200 filters: {
201 reference: [imageName]
202 }
203 });
204
205 return images.length > 0;
206}
207
208async function listContainers(options) {
209 return await docker.listContainers(options);
210}
211
212async function getContainer(containerId) {
213 return await docker.getContainer(containerId);
214}
215
216async function renameContainer(container, name) {
217 return await container.rename({
218 name
219 });
220}
221
222// dockerode exec 在 windows 上有问题,用 exec 的 stdin 传递事件,当调用 stream.end() 时,会直接导致 exec 退出,且 ExitCode 为 null
223function generateDockerCmd(functionProps, httpMode, invokeInitializer = true, event = null) {
224 const cmd = ['-h', functionProps.Handler];
225
226 // 如果提供了 event
227 if (event !== null) {
228 cmd.push('--event', Buffer.from(event).toString('base64'));
229 cmd.push('--event-decode');
230 } else {
231 // always pass event using stdin mode
232 cmd.push('--stdin');
233 }
234
235 if (httpMode) {
236 cmd.push('--http');
237 }
238
239 const initializer = functionProps.Initializer;
240
241 if (initializer && invokeInitializer) {
242 cmd.push('-i', initializer);
243 }
244
245 const initializationTimeout = functionProps.InitializationTimeout;
246
247 // initializationTimeout is defined as integer, see lib/validate/schema/function.js
248 if (initializationTimeout) {
249 cmd.push('--initializationTimeout', initializationTimeout.toString());
250 }
251
252 debug(`docker cmd: ${cmd}`);
253
254 return cmd;
255}
256
257
258function followProgress(stream, onFinished) {
259
260 const barLines = {};
261
262 const onProgress = (event) => {
263 let status = event.status;
264
265 if (event.progress) {
266 status = `${event.status} ${event.progress}`;
267 }
268
269 if (event.id) {
270 const id = event.id;
271
272 if (!barLines[id]) {
273 barLines[id] = console.draft();
274 }
275 barLines[id](id + ': ' + status);
276 } else {
277 if (_.has(event, 'aux.ID')) {
278 event.stream = event.aux.ID + '\n';
279 }
280 // If there is no id, the line should be wrapped manually.
281 const out = event.status ? event.status + '\n' : event.stream;
282 process.stdout.write(out);
283 }
284 };
285
286 docker.modem.followProgress(stream, onFinished, onProgress);
287}
288
289async function pullImage(imageName) {
290
291 const resolveImageName = await dockerOpts.resolveImageNameForPull(imageName);
292
293 // copied from lib/edge/container.js
294 const startTime = new Date();
295
296 const stream = await docker.pull(resolveImageName);
297
298 const visitor = await getVisitor();
299
300 visitor.event({
301 ec: 'image',
302 ea: 'pull',
303 el: 'start'
304 }).send();
305
306 const registry = await dockerOpts.resolveDockerRegistry();
307
308 return new Promise((resolve, reject) => {
309
310 console.log(`begin pulling image ${resolveImageName}, you can also use ` + yellow(`'docker pull ${resolveImageName}'`) + ' to pull image by yourself.');
311
312 const onFinished = async (err) => {
313 const pullDuration = parseInt((new Date() - startTime) / 1000);
314 if (err) {
315 visitor.event({
316 ec: 'image',
317 ea: 'pull',
318 el: 'error'
319 }).send();
320
321 visitor.event({
322 ec: 'image',
323 ea: `pull from ${registry}`,
324 el: 'error'
325 }).send();
326
327 visitor.event({
328 ec: `image pull from ${registry}`,
329 ea: `used ${pullDuration}`,
330 el: 'error'
331 }).send();
332
333 reject(err);
334 return;
335 }
336
337 visitor.event({
338 ec: 'image',
339 ea: `pull from ${registry}`,
340 el: 'success'
341 }).send();
342
343 visitor.event({
344 ec: 'image',
345 ea: 'pull',
346 el: 'success'
347 }).send();
348
349 visitor.event({
350 ec: `image pull from ${registry}`,
351 ea: `used ${pullDuration}`,
352 el: 'success'
353 }).send();
354
355 dockerOpts.DOCKER_REGISTRIES;
356 for (const r of dockerOpts.DOCKER_REGISTRIES) {
357 if (resolveImageName.indexOf(r) === 0) {
358 const image = await docker.getImage(resolveImageName);
359 // rename
360 await image.tag({
361 name: resolveImageName,
362 repo: resolveImageName.slice(r.length + 1) // short name
363 });
364 break;
365 }
366 }
367 resolve(resolveImageName);
368 };
369 // pull image progress
370 followProgress(stream, onFinished);
371 });
372}
373
374function generateFunctionEnvs(functionProps) {
375 const environmentVariables = functionProps.EnvironmentVariables;
376
377 if (!environmentVariables) { return {}; }
378
379 return Object.assign({}, environmentVariables);
380}
381
382function generateRamdomContainerName() {
383 return `fun_local_${new Date().getTime()}_${Math.random().toString(36).substr(2, 7)}`;
384}
385
386async function generateDockerEnvs(baseDir, functionProps, debugPort, httpParams, nasConfig, ishttpTrigger, debugIde, debugArgs) {
387
388 const envs = {};
389
390 if (httpParams) {
391 Object.assign(envs, {
392 'FC_HTTP_PARAMS': httpParams
393 });
394 }
395
396 const confEnv = await resolveLibPathsFromLdConf(baseDir, functionProps.CodeUri);
397
398 Object.assign(envs, confEnv);
399
400 const runtime = functionProps.Runtime;
401
402 if (debugPort && !debugArgs) {
403 const debugEnv = generateDebugEnv(runtime, debugPort, debugIde);
404
405 Object.assign(envs, debugEnv);
406 } else if (debugArgs) {
407 Object.assign(envs, {
408 DEBUG_OPTIONS: debugArgs
409 });
410 }
411
412 if (ishttpTrigger && runtime === 'java8') {
413 envs['fc_enable_new_java_ca'] = 'true';
414 }
415
416 Object.assign(envs, generateFunctionEnvs(functionProps));
417
418 const profile = await getProfile();
419
420 Object.assign(envs, {
421 'local': true,
422 'FC_ACCESS_KEY_ID': profile.accessKeyId,
423 'FC_ACCESS_KEY_SECRET': profile.accessKeySecret
424 });
425
426 return addEnv(envs, nasConfig);
427}
428
429async function pullImageIfNeed(imageName) {
430 const exist = await imageExist(imageName);
431
432 if (!exist || !skipPullImage) {
433
434 await pullImage(imageName);
435 } else {
436 debug(`skip pulling image ${imageName}...`);
437 console.log(`skip pulling image ${imageName}...`);
438 }
439}
440
441async function showDebugIdeTipsForVscode(serviceName, functionName, runtime, codeSource, debugPort) {
442 const vscodeDebugConfig = await generateVscodeDebugConfig(serviceName, functionName, runtime, codeSource, debugPort);
443
444 // todo: auto detect .vscode/launch.json in codeuri path.
445 console.log(blue('you can paste these config to .vscode/launch.json, and then attach to your running function'));
446 console.log('///////////////// config begin /////////////////');
447 console.log(JSON.stringify(vscodeDebugConfig, null, 4));
448 console.log('///////////////// config end /////////////////');
449}
450
451async function showDebugIdeTipsForPycharm(codeSource, debugPort) {
452
453 const stats = await fs.lstat(codeSource);
454
455 if (!stats.isDirectory()) {
456 codeSource = path.dirname(codeSource);
457 }
458
459 console.log(yellow(`\n========= Tips for PyCharm remote debug =========
460Local host name: ${ip.address()}
461Port : ${yellow(debugPort)}
462Path mappings : ${yellow(codeSource)}=/code
463
464Debug Code needed to copy to your function code:
465
466import pydevd
467pydevd.settrace('${ip.address()}', port=${debugPort}, stdoutToServer=True, stderrToServer=True)
468
469=========================================================================\n`));
470}
471
472function writeEventToStreamAndClose(stream, event) {
473
474 if (event) {
475 stream.write(event);
476 }
477
478 stream.end();
479}
480
481async function isDockerToolBox() {
482
483 const dockerInfo = await docker.info();
484
485 const obj = (dockerInfo.Labels || []).map(e => _.split(e, '=', 2))
486 .filter(e => e.length === 2)
487 .reduce((acc, cur) => (acc[cur[0]] = cur[1], acc), {});
488
489 return process.platform === 'win32' && obj.provider === 'virtualbox';
490}
491
492async function run(opts, event, outputStream, errorStream) {
493
494 const container = await createContainer(opts);
495
496 const attachOpts = {
497 hijack: true,
498 stream: true,
499 stdin: true,
500 stdout: true,
501 stderr: true
502 };
503
504 const stream = await container.attach(attachOpts);
505
506 if (!outputStream) {
507 outputStream = process.stdout;
508 }
509
510 if (!errorStream) {
511 errorStream = process.stderr;
512 }
513
514 var isWin = process.platform === 'win32';
515 if (!isWin) {
516 container.modem.demuxStream(stream, outputStream, errorStream);
517 }
518
519 await container.start();
520
521 // dockerode bugs on windows. attach could not receive output and error
522 if (isWin) {
523 const logStream = await container.logs({
524 stdout: true,
525 stderr: true,
526 follow: true
527 });
528
529 container.modem.demuxStream(logStream, outputStream, errorStream);
530 }
531
532 containers.add(container.id);
533
534 writeEventToStreamAndClose(stream, event);
535
536 // exitRs format: {"Error":null,"StatusCode":0}
537 // see https://docs.docker.com/engine/api/v1.37/#operation/ContainerWait
538 const exitRs = await container.wait();
539
540 containers.delete(container.id);
541
542 return exitRs;
543}
544
545function resolveDockerUser(nasConfig) {
546 let uid = 10003;
547 let gid = 10003;
548
549 const isNasAuto = definition.isNasAutoConfig(nasConfig);
550
551 if (nasConfig && !isNasAuto) {
552 const userId = nasConfig.UserId;
553 const groupId = nasConfig.GroupId;
554
555 if (userId !== -1) {
556 uid = userId;
557 }
558
559 if (groupId !== -1) {
560 gid = groupId;
561 }
562 }
563
564 return `${uid}:${gid}`;
565}
566
567async function createContainer(opts) {
568 const isWin = process.platform === 'win32';
569 const isMac = process.platform === 'darwin';
570
571 if (opts && isMac) {
572 if (opts.HostConfig) {
573 const pathsOutofSharedPaths = await findPathsOutofSharedPaths(opts.HostConfig.Mounts);
574 if (isMac && pathsOutofSharedPaths.length > 0) {
575 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`));
576 }
577 }
578 }
579
580 let container;
581 try {
582 // see https://github.com/apocas/dockerode/pull/38
583 container = await docker.createContainer(opts);
584 } catch (ex) {
585
586 if (ex.message.indexOf('invalid mount config for type') !== -1 && await isDockerToolBox()) {
587 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 .`));
588 }
589 if (ex.message.indexOf('drive is not shared') !== -1 && isWin) {
590 throw new Error(red(`${ex.message}More information please refer to https://docs.docker.com/docker-for-windows/#shared-drives`));
591 }
592 throw ex;
593 }
594 return container;
595}
596
597async function createAndRunContainer(opts) {
598 const container = await createContainer(opts);
599 containers.add(container.id);
600 await container.start({});
601 return container;
602}
603
604async function execContainer(container, opts, outputStream, errorStream) {
605 outputStream = process.stdout;
606 errorStream = process.stderr;
607 const logStream = await container.logs({
608 stdout: true,
609 stderr: true,
610 follow: true,
611 since: (new Date().getTime() / 1000)
612 });
613 container.modem.demuxStream(logStream, outputStream, errorStream);
614 const exec = await container.exec(opts);
615 const stream = (await exec.start()).output;
616 // have to wait, otherwise stdin may not be readable
617 await new Promise(resolve => setTimeout(resolve, 30));
618 container.modem.demuxStream(stream, outputStream, errorStream);
619
620 await waitForExec(exec);
621 logStream.destroy();
622}
623
624async function waitForExec(exec) {
625 return await new Promise((resolve, reject) => {
626 // stream.on('end') could not receive end event on windows.
627 // so use inspect to check exec exit
628 function waitContainerExec() {
629 exec.inspect((err, data) => {
630 if (data.Running) {
631 setTimeout(waitContainerExec, 100);
632 return;
633 }
634 if (err) {
635 reject(err);
636 } else if (data.ExitCode !== 0) {
637 reject(`${data.ProcessConfig.entrypoint} exited with code ${data.ExitCode}`);
638 } else {
639 resolve(data.ExitCode);
640 }
641 });
642 }
643 waitContainerExec();
644 });
645}
646
647// outputStream, errorStream used for http invoke
648// because agent is started when container running and exec could not receive related logs
649async function startContainer(opts, outputStream, errorStream) {
650
651 const container = await createContainer(opts);
652
653 containers.add(container.id);
654
655 try {
656 await container.start({});
657 } catch (err) {
658 console.error(err);
659 }
660
661 const logs = outputStream || errorStream;
662
663 if (logs) {
664 if (!outputStream) {
665 outputStream = devnull();
666 }
667
668 if (!errorStream) {
669 errorStream = devnull();
670 }
671
672 // dockerode bugs on windows. attach could not receive output and error, must use logs
673 const logStream = await container.logs({
674 stdout: true,
675 stderr: true,
676 follow: true
677 });
678
679 container.modem.demuxStream(logStream, outputStream, errorStream);
680 }
681
682 return {
683 stop: async () => {
684 await container.stop();
685 containers.delete(container.id);
686 },
687
688 exec: async (cmd, { cwd = '', env = {}, outputStream, errorStream, verbose = false } = {}) => {
689 const options = {
690 Cmd: cmd,
691 Env: dockerOpts.resolveDockerEnv(env),
692 Tty: false,
693 AttachStdin: false,
694 AttachStdout: true,
695 AttachStderr: true,
696 WorkingDir: cwd
697 };
698
699 // docker exec
700 debug('docker exec opts: ' + JSON.stringify(options, null, 4));
701
702 const exec = await container.exec(options);
703
704 const stream = (await exec.start({ hijack: true, stdin: false })).output;
705
706 // todo: have to wait, otherwise stdin may not be readable
707 await new Promise(resolve => setTimeout(resolve, 30));
708
709 if (!outputStream) {
710 outputStream = process.stdout;
711 }
712
713 if (!errorStream) {
714 errorStream = process.stderr;
715 }
716
717 if (verbose) {
718 container.modem.demuxStream(stream, outputStream, errorStream);
719 } else {
720 container.modem.demuxStream(stream, devnull(), errorStream);
721 }
722
723 return await waitForExec(exec);
724 }
725 };
726}
727
728async function startInstallationContainer({ runtime, imageName, codeUri, targets }) {
729 debug(`runtime: ${runtime}`);
730 debug(`codeUri: ${codeUri}`);
731
732 if (await isDockerToolBox()) {
733
734 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.`));
735 }
736
737 if (!imageName) {
738 imageName = await dockerOpts.resolveRuntimeToDockerImage(runtime, true);
739 if (!imageName) {
740 throw new Error(`invalid runtime name ${runtime}`);
741 }
742 }
743
744 const codeMount = await resolveCodeUriToMount(codeUri, false);
745
746 const installMounts = conventInstallTargetsToMounts(targets);
747
748 const mounts = [codeMount, ...installMounts];
749
750 await pullImageIfNeed(imageName);
751
752 const envs = addInstallTargetEnv({}, targets);
753
754 const opts = dockerOpts.generateInstallOpts(imageName, mounts, envs);
755
756 return await startContainer(opts);
757}
758
759async function startSboxContainer({
760 runtime, imageName,
761 mounts, cmd, envs,
762 isTty, isInteractive
763}) {
764 debug(`runtime: ${runtime}`);
765 debug(`mounts: ${mounts}`);
766 debug(`isTty: ${isTty}`);
767 debug(`isInteractive: ${isInteractive}`);
768
769 if (!imageName) {
770 imageName = await dockerOpts.resolveRuntimeToDockerImage(runtime, true);
771 if (!imageName) {
772 throw new Error(`invalid runtime name ${runtime}`);
773 }
774 }
775
776 debug(`cmd: ${parseArgsStringToArgv(cmd || '')}`);
777
778 const container = await createContainer(dockerOpts.generateSboxOpts({
779 imageName,
780 hostname: `fc-${runtime}`,
781 mounts,
782 envs,
783 cmd: parseArgsStringToArgv(cmd || ''),
784 isTty,
785 isInteractive
786 }));
787
788 containers.add(container.id);
789
790 await container.start();
791
792 const stream = await container.attach({
793 logs: true,
794 stream: true,
795 stdin: isInteractive,
796 stdout: true,
797 stderr: true
798 });
799
800 // show outputs
801 let logStream;
802 if (isTty) {
803 stream.pipe(process.stdout);
804 } else {
805 if (isInteractive || process.platform === 'win32') {
806 // 这种情况很诡异,收不到 stream 的 stdout,使用 log 绕过去。
807 logStream = await container.logs({
808 stdout: true,
809 stderr: true,
810 follow: true
811 });
812 container.modem.demuxStream(logStream, process.stdout, process.stderr);
813 } else {
814 container.modem.demuxStream(stream, process.stdout, process.stderr);
815 }
816
817 }
818
819 if (isInteractive) {
820 // Connect stdin
821 process.stdin.pipe(stream);
822
823 let previousKey;
824 const CTRL_P = '\u0010', CTRL_Q = '\u0011';
825
826 process.stdin.on('data', (key) => {
827 // Detects it is detaching a running container
828 const keyStr = key.toString('ascii');
829 if (previousKey === CTRL_P && keyStr === CTRL_Q) {
830 container.stop(() => { });
831 }
832 previousKey = keyStr;
833 });
834
835 }
836
837 let resize;
838
839 const isRaw = process.isRaw;
840 if (isTty) {
841 // fix not exit process in windows
842 goThrough();
843
844 process.stdin.setRawMode(true);
845
846 resize = async () => {
847 const dimensions = {
848 h: process.stdout.rows,
849 w: process.stdout.columns
850 };
851
852 if (dimensions.h !== 0 && dimensions.w !== 0) {
853 await container.resize(dimensions);
854 }
855 };
856
857 await resize();
858 process.stdout.on('resize', resize);
859
860 // 在不加任何 cmd 的情况下 shell prompt 需要输出一些字符才会显示,
861 // 这里输入一个空格+退格,绕过这个怪异的问题。
862 stream.write(' \b');
863 }
864
865
866 await container.wait();
867
868 // cleanup
869 if (isTty) {
870 process.stdout.removeListener('resize', resize);
871 process.stdin.setRawMode(isRaw);
872 }
873
874 if (isInteractive) {
875 process.stdin.removeAllListeners();
876 process.stdin.unpipe(stream);
877
878 /**
879 * https://stackoverflow.com/questions/31716784/nodejs-process-never-ends-when-piping-the-stdin-to-a-child-process?rq=1
880 * https://github.com/nodejs/node/issues/2276
881 * */
882 process.stdin.destroy();
883 }
884
885 if (logStream) {
886 logStream.removeAllListeners();
887 }
888
889 stream.unpipe(process.stdout);
890
891 // fix not exit process in windows
892 // stream is hackji socks,so need to close
893 stream.destroy();
894
895 containers.delete(container.id);
896
897 if (!isTty) {
898 goThrough();
899 process.stdin.destroy();
900 }
901}
902
903async function zipTo(archive, to) {
904
905 await fs.ensureDir(to);
906
907 await new Promise((resolve, reject) => {
908 archive.pipe(tar.extract(to)).on('error', reject).on('finish', resolve);
909 });
910}
911
912async function copyFromImage(imageName, from, to) {
913 const container = await docker.createContainer({
914 Image: imageName
915 });
916
917 const archive = await container.getArchive({
918 path: from
919 });
920
921 await zipTo(archive, to);
922
923 await container.remove();
924}
925
926function buildImage(dockerBuildDir, dockerfilePath, imageTag) {
927
928 return new Promise((resolve, reject) => {
929 var tarStream = tar.pack(dockerBuildDir);
930
931 docker.buildImage(tarStream, {
932 dockerfile: path.relative(dockerBuildDir, dockerfilePath),
933 t: imageTag
934 }, (error, stream) => {
935 containers.add(stream);
936
937 if (error) { reject(error); }
938 else {
939 stream.on('error', (e) => {
940 containers.delete(stream);
941 reject(e);
942 });
943 stream.on('end', function () {
944 containers.delete(stream);
945 resolve(imageTag);
946 });
947 }
948 followProgress(stream, (err, res) => err ? reject(err) : resolve(res));
949 });
950 });
951}
952
953module.exports = {
954 imageExist, generateDockerCmd,
955 pullImage,
956 resolveCodeUriToMount, generateFunctionEnvs, run, generateRamdomContainerName,
957 generateDockerEnvs, pullImageIfNeed,
958 showDebugIdeTipsForVscode, resolveDockerUser, resolveNasConfigToMounts,
959 startInstallationContainer, startContainer, isDockerToolBox,
960 conventInstallTargetsToMounts, startSboxContainer, buildImage, copyFromImage,
961 resolveTmpDirToMount, showDebugIdeTipsForPycharm, resolveDebuggerPathToMount,
962 listContainers, getContainer, createAndRunContainer, execContainer,
963 renameContainer
964};
\No newline at end of file