UNPKG

9.05 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6const Doer_1 = __importDefault(require("./Doer"));
7const VERSION = require('../package').version;
8/**
9 * Generates a Dockerfile for a `SoftwareEnvironment` instance
10 */
11class Generator extends Doer_1.default {
12 /**
13 * Generate a Dockerfile for a `SoftwareEnvironment` instance
14 *
15 * @param comments Should a comments be added to the Dockerfile?
16 * @param stencila Should relevant Stencila language packages be installed in the image?
17 */
18 generate(comments = true, stencila = false) {
19 let dockerfile = '';
20 if (comments) {
21 dockerfile += `# Generated by Dockter ${VERSION} at ${new Date().toISOString()}
22# To stop Dockter generating this file and start editing it yourself,
23# rename it to "Dockerfile".\n`;
24 }
25 if (comments)
26 dockerfile += '\n# This tells Docker which base image to use.\n';
27 const baseIdentifier = this.baseIdentifier();
28 dockerfile += `FROM ${baseIdentifier}\n`;
29 if (!this.applies())
30 return dockerfile;
31 const aptRepos = this.aptRepos(baseIdentifier);
32 let aptKeysCommand = this.aptKeysCommand(baseIdentifier);
33 if (aptRepos.length || aptKeysCommand) {
34 if (comments)
35 dockerfile += '\n# This section installs system packages needed to add extra system repositories.';
36 dockerfile += `
37RUN apt-get update \\
38 && DEBIAN_FRONTEND=noninteractive apt-get install -y \\
39 apt-transport-https \\
40 ca-certificates \\
41 curl \\
42 software-properties-common
43`;
44 }
45 if (comments && (aptKeysCommand || aptRepos.length)) {
46 dockerfile += '\n# This section adds system repositories required to install extra system packages.';
47 }
48 if (aptKeysCommand)
49 dockerfile += `\nRUN ${aptKeysCommand}`;
50 if (aptRepos.length)
51 dockerfile += `\nRUN ${aptRepos.map(repo => `apt-add-repository "${repo}"`).join(' \\\n && ')}\n`;
52 // Set env vars after previous section to improve caching
53 const envVars = this.envVars(baseIdentifier);
54 if (envVars.length) {
55 if (comments)
56 dockerfile += '\n# This section sets environment variables within the image.';
57 const pairs = envVars.map(([key, value]) => `${key}="${value.replace('"', '\\"')}"`);
58 dockerfile += `\nENV ${pairs.join(' \\\n ')}\n`;
59 }
60 let aptPackages = this.aptPackages(baseIdentifier);
61 if (aptPackages.length) {
62 if (comments) {
63 dockerfile += `
64# This section installs system packages required for your project
65# If you need extra system packages add them here.`;
66 }
67 dockerfile += `
68RUN apt-get update \\
69 && DEBIAN_FRONTEND=noninteractive apt-get install -y \\
70 ${aptPackages.join(' \\\n ')} \\
71 && apt-get autoremove -y \\
72 && apt-get clean \\
73 && rm -rf /var/lib/apt/lists/*
74`;
75 }
76 if (stencila) {
77 let stencilaInstall = this.stencilaInstall(baseIdentifier);
78 if (stencilaInstall) {
79 if (comments)
80 dockerfile += '\n# This section runs commands to install Stencila execution hosts.';
81 dockerfile += `\nRUN ${stencilaInstall}\n`;
82 }
83 }
84 // Once everything that needs root permissions is installed, switch the user to non-root for installing the rest of the packages.
85 if (comments) {
86 dockerfile += `
87# It's good practice to run Docker images as a non-root user.
88# This section creates a new user and its home directory as the default working directory.`;
89 }
90 dockerfile += `
91RUN useradd --create-home --uid 1001 -s /bin/bash dockteruser
92WORKDIR /home/dockteruser
93`;
94 const installFiles = this.installFiles(baseIdentifier);
95 const installCommand = this.installCommand(baseIdentifier);
96 const projectFiles = this.projectFiles(baseIdentifier);
97 const runCommand = this.runCommand(baseIdentifier);
98 // Add Dockter special comment for managed installation of language packages
99 if (installCommand) {
100 if (comments)
101 dockerfile += '\n# This is a special comment to tell Dockter to manage the build from here on';
102 dockerfile += `\n# dockter\n`;
103 }
104 // Copy files needed for installation of language packages
105 if (installFiles.length) {
106 if (comments)
107 dockerfile += '\n# This section copies package requirement files into the image';
108 dockerfile += '\n' + installFiles.map(([src, dest]) => `COPY ${src} ${dest}`).join('\n') + '\n';
109 }
110 // Run command to install packages
111 if (installCommand) {
112 if (comments)
113 dockerfile += '\n# This section runs commands to install the packages specified in the requirement file/s';
114 dockerfile += `\nRUN ${installCommand}\n`;
115 }
116 // Copy files needed to run project
117 if (projectFiles.length) {
118 if (comments)
119 dockerfile += '\n# This section copies your project\'s files into the image';
120 dockerfile += '\n' + projectFiles.map(([src, dest]) => `COPY ${src} ${dest}`).join('\n') + '\n';
121 }
122 // Now all installation is finished set the user
123 if (comments)
124 dockerfile += '\n# This sets the default user when the container is run';
125 dockerfile += '\nUSER dockteruser\n';
126 // Add any CMD
127 if (runCommand) {
128 if (comments)
129 dockerfile += '\n# This tells Docker the default command to run when the container is started';
130 dockerfile += `\nCMD ${runCommand}\n`;
131 }
132 // Write `.Dockerfile` for use by Docker
133 this.write('.Dockerfile', dockerfile);
134 return dockerfile;
135 }
136 // Methods that are overridden in derived classes
137 /**
138 * Does this generator apply to the package?
139 */
140 applies() {
141 return false;
142 }
143 /**
144 * Name of the base image
145 */
146 baseName() {
147 return 'ubuntu';
148 }
149 /**
150 * Version of the base image
151 */
152 baseVersion() {
153 return '18.04';
154 }
155 /**
156 * Get the version name for a base image
157 * @param baseIdentifier The base image name e.g. `ubuntu:18.04`
158 */
159 baseVersionName(baseIdentifier) {
160 let [name, version] = baseIdentifier.split(':');
161 const lookup = {
162 '14.04': 'trusty',
163 '16.04': 'xenial',
164 '18.04': 'bionic'
165 };
166 return lookup[version];
167 }
168 /**
169 * Generate a base image identifier
170 */
171 baseIdentifier() {
172 const joiner = this.baseVersion() === '' ? '' : ':';
173 return `${this.baseName()}${joiner}${this.baseVersion()}`;
174 }
175 /**
176 * A list of environment variables to set in the image
177 * as `name`, `value` pairs
178 *
179 * @param sysVersion The Ubuntu system version being used
180 */
181 envVars(sysVersion) {
182 return [];
183 }
184 /**
185 * A Bash command to run to install required apt keys
186 *
187 * @param sysVersion The Ubuntu system version being used
188 */
189 aptKeysCommand(sysVersion) {
190 return;
191 }
192 /**
193 * A list of any required apt repositories
194 *
195 * @param sysVersion The Ubuntu system version being used
196 */
197 aptRepos(sysVersion) {
198 return [];
199 }
200 /**
201 * A list of any required apt packages
202 *
203 * @param sysVersion The Ubuntu system version being used
204 */
205 aptPackages(sysVersion) {
206 return [];
207 }
208 /**
209 * A Bash command to run to install Stencila execution host package/s
210 *
211 * @param sysVersion The Ubuntu system version being used
212 */
213 stencilaInstall(sysVersion) {
214 return;
215 }
216 /**
217 * A list of files that need to be be copied
218 * into the image before running `installCommand`
219 *
220 * @param sysVersion The Ubuntu system version being used
221 * @returns An array of [src, dest] tuples
222 */
223 installFiles(sysVersion) {
224 return [];
225 }
226 /**
227 * The Bash command to run to install required language packages
228 *
229 * @param sysVersion The Ubuntu system version being used
230 */
231 installCommand(sysVersion) {
232 return;
233 }
234 /**
235 * The project's files that should be copied across to the image
236 *
237 * @param sysVersion The Ubuntu system version being used
238 * @returns An array of [src, dest] tuples
239 */
240 projectFiles(sysVersion) {
241 return [];
242 }
243 /**
244 * The default command to run containers created from this image
245 *
246 * @param sysVersion The Ubuntu system version being used
247 */
248 runCommand(sysVersion) {
249 return;
250 }
251}
252exports.default = Generator;