1 | var path = require('path');
|
2 | var child = require('child_process');
|
3 | var fs = require('fs');
|
4 | var _ = require('lodash');
|
5 | var uuid = require('node-uuid');
|
6 | var crypto = require('crypto');
|
7 | var os = require('os');
|
8 | var cli = require('heroku-cli-util');
|
9 | var directory = require('./directory');
|
10 | require('prototypes');
|
11 |
|
12 | const FILENAME = 'Dockerfile';
|
13 |
|
14 | module.exports = {
|
15 | filename: FILENAME,
|
16 | silent: false,
|
17 | buildImage: buildImage,
|
18 | ensureExecImage: ensureExecImage,
|
19 | ensureStartImage: ensureStartImage,
|
20 | runImage: runImage
|
21 | };
|
22 |
|
23 | function runImage(imageId, cwd, command, mount) {
|
24 | if (!imageId) return;
|
25 | var mountDir = crossPlatformCwd(cwd);
|
26 | var mountComponent = mount ? `-v "${mountDir}:/app/src"` : '';
|
27 | var envArgComponent = directory.getFormattedEnvArgComponent(cwd);
|
28 | var runCommand = `docker run -w /app/src -p 3000:3000 --rm -it ${mountComponent} ${envArgComponent} ${imageId} sh -c "${command}" || true`;
|
29 | var result = child.execSync(runCommand, {
|
30 | stdio: this.silent ? [0, 'pipe', 'pipe'] : 'inherit'
|
31 | });
|
32 | return Buffer.isBuffer(result) ?
|
33 | result.toString().trim() : result;
|
34 | }
|
35 |
|
36 | function buildImage(dir, id, dockerfile) {
|
37 | cli.log('building image...');
|
38 | var dockerfile = dockerfile || path.join(dir, FILENAME);
|
39 | var build = child.execSync(`docker build --force-rm --file="${dockerfile}" --tag="${id}" "${dir}"`, {
|
40 | stdio: this.silent ? [0, 'pipe', 'pipe'] : 'inherit'
|
41 | });
|
42 | return id;
|
43 | }
|
44 |
|
45 | function ensureExecImage(dir) {
|
46 | var dockerfile = path.join(dir, FILENAME);
|
47 | try {
|
48 | var contents = fs.readFileSync(dockerfile, { encoding: 'utf8' });
|
49 | var hash = createHash(contents);
|
50 | var imageId = getImageId(hash);
|
51 | imageExists(imageId) || this.buildImage(dir, imageId);
|
52 | return imageId;
|
53 | }
|
54 | catch (e) {
|
55 | if (e.code === 'ENOENT') {
|
56 | cli.error('No Dockerfile found, did you run `heroku docker:init`?');
|
57 | return;
|
58 | }
|
59 | else {
|
60 | throw e;
|
61 | }
|
62 | }
|
63 | }
|
64 |
|
65 | function ensureStartImage(dir) {
|
66 | var execImageId = this.ensureExecImage(dir);
|
67 | if (!execImageId) {
|
68 | return;
|
69 | }
|
70 | var contents = `FROM ${execImageId}`;
|
71 | var imageId = `${execImageId}-start`;
|
72 | var filename = `.Dockerfile-${uuid.v1()}`;
|
73 | var filepath = path.join(dir, filename);
|
74 | fs.writeFileSync(filepath, contents, { encoding: 'utf8' });
|
75 | try {
|
76 | this.buildImage(dir, imageId, filepath);
|
77 | }
|
78 | catch (e) {
|
79 | fs.unlinkSync(filepath);
|
80 | throw new Error('Unable to create start image');
|
81 | }
|
82 | fs.unlinkSync(filepath);
|
83 | return imageId;
|
84 | }
|
85 |
|
86 | function createHash(contents) {
|
87 | var md5 = crypto.createHash('md5');
|
88 | md5.update(contents, 'utf8');
|
89 | var digest = md5.digest('hex');
|
90 | return digest;
|
91 | }
|
92 |
|
93 | function getImageId(hash) {
|
94 | return `heroku-docker-${hash}`;
|
95 | }
|
96 |
|
97 | function imageExists(id) {
|
98 | return getAllImages().indexOf(id) !== -1;
|
99 | }
|
100 |
|
101 | function getAllImages() {
|
102 | var stdout = child.execSync(`docker images`, { encoding: 'utf8' });
|
103 | return _.map(_.filter(stdout.split('\n'), isImage), lineToId);
|
104 |
|
105 | function isImage(line) {
|
106 | return line.indexOf('heroku-docker') === 0;
|
107 | }
|
108 |
|
109 | function lineToId(line) {
|
110 | return line.split(' ')[0];
|
111 | }
|
112 | }
|
113 |
|
114 | function crossPlatformCwd(cwd){
|
115 | if (os.platform() == 'win32') {
|
116 |
|
117 | var p = path.parse(cwd);
|
118 | return path.posix.sep + p.root.split(':')[0].toLowerCase() + path.posix.sep
|
119 | + p.dir.substring(p.root.length).replaceAll(path.sep, path.posix.sep) + path.posix.sep + p.base;
|
120 | }
|
121 | return cwd;
|
122 | }
|