containerify 2.0.1 → 2.2.0

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/README.md CHANGED
@@ -27,6 +27,7 @@ Options:
27
27
  --fromImage <name:tag> Required: Image name of base image - [path/]image:tag
28
28
  --toImage <name:tag> Required: Image name of target image - [path/]image:tag
29
29
  --folder <full path> Required: Base folder of node application (contains package.json)
30
+ --file <path> Optional: Name of configuration file (defaults to containerify.json if found on path)
30
31
  --fromRegistry <registry url> Optional: URL of registry to pull base image from - Default: https://registry-1.docker.io/v2/
31
32
  --fromToken <token> Optional: Authentication token for from registry
32
33
  --toRegistry <registry url> Optional: URL of registry to push base image to - Default: https://registry-1.docker.io/v2/
@@ -39,17 +40,20 @@ Options:
39
40
  --workdir <directory> Optional: Workdir where node app will be added and run from - default: /app
40
41
  --entrypoint <entrypoint> Optional: Entrypoint when starting container - default: npm start
41
42
  --labels <labels> Optional: Comma-separated list of key value pairs to use as labels
42
- --label <label> Optional: Single label (name=value). This option can be used multiple times. Wrap in double quotes if value has spaces or other characters that can cause arugment parsing issues.
43
- --envs <envs> Optional: Comma-separated list of key value paris to use av environment variables.
43
+ --label <label> Optional: Single label (name=value). This option can be used multiple times.
44
+ --envs <envs> Optional: Comma-separated list of key value pairs to use av environment variables.
44
45
  --env <env> Optional: Single environment variable (name=value). This option can be used multiple times.
45
- --setTimeStamp <timestamp> Optional: Set a specific ISO 8601 timestamp on all entries (e.g. git commit hash). Default: 1970 in tar files, and current time on manifest/config
46
+ --setTimeStamp <timestamp> Optional: Set a specific ISO 8601 timestamp on all entries (e.g. git commit hash). Default: 1970 in tar files, and current time on
47
+ manifest/config
46
48
  --verbose Verbose logging
47
49
  --allowInsecureRegistries Allow insecure registries (with self-signed/untrusted cert)
48
50
  --customContent <dirs/files> Optional: Skip normal node_modules and applayer and include specified root folder files/directories instead
49
51
  --extraContent <dirs/files> Optional: Add specific content. Specify as local-path:absolute-container-path,local-path2:absolute-container-path2 etc
50
52
  --layerOwner <gid:uid> Optional: Set specific gid and uid on files in the added layers
51
53
  --buildFolder <path> Optional: Use a specific build folder when creating the image
52
- -h, --help output usage information
54
+ --layerCacheFolder <path> Optional: Folder to cache base layers between builds
55
+ --version Get containerify version
56
+ -h, --help display help for command
53
57
  ```
54
58
 
55
59
  ## Detailed info
@@ -166,7 +166,7 @@ function parseCommandLineToParts(entrypoint) {
166
166
  }
167
167
  function addAppLayers(options, config, todir, manifest, tmpdir) {
168
168
  return __awaiter(this, void 0, void 0, function* () {
169
- if (options.customContent) {
169
+ if (options.customContent.length > 0) {
170
170
  yield addEnvsLayer(options, config);
171
171
  yield addLabelsLayer(options, config);
172
172
  yield addDataLayer(tmpdir, todir, options, config, manifest, options.customContent, "custom");
@@ -187,10 +187,8 @@ function addAppLayers(options, config, todir, manifest, tmpdir) {
187
187
  yield addDataLayer(tmpdir, todir, options, config, manifest, depLayerContent, "dependencies");
188
188
  yield addDataLayer(tmpdir, todir, options, config, manifest, appLayerContent, "app");
189
189
  }
190
- if (options.extraContent) {
191
- for (const i in options.extraContent) {
192
- yield addDataLayer(tmpdir, todir, options, config, manifest, [options.extraContent[i]], "extra");
193
- }
190
+ for (const extraContent of Object.entries(options.extraContent)) {
191
+ yield addDataLayer(tmpdir, todir, options, config, manifest, [extraContent], "extra");
194
192
  }
195
193
  });
196
194
  }
package/lib/cli.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  step((generator = generator.apply(thisArg, _arguments || [])).next());
10
10
  });
11
11
  };
12
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
12
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  const os = require("os");
15
15
  const commander_1 = require("commander");
@@ -50,10 +50,11 @@ const possibleArgs = {
50
50
  "--extraContent <dirs/files>": "Optional: Add specific content. Specify as local-path:absolute-container-path,local-path2:absolute-container-path2 etc",
51
51
  "--layerOwner <gid:uid>": "Optional: Set specific gid and uid on files in the added layers",
52
52
  "--buildFolder <path>": "Optional: Use a specific build folder when creating the image",
53
+ "--layerCacheFolder <path>": "Optional: Folder to cache base layers between builds",
53
54
  "--version": "Get containerify version",
54
55
  };
55
- function setKeyValue(target, keyValue) {
56
- const [k, v] = keyValue.split("=", 2);
56
+ function setKeyValue(target, keyValue, separator = "=") {
57
+ const [k, v] = keyValue.split(separator, 2);
57
58
  target[k.trim()] = v.trim();
58
59
  }
59
60
  const cliLabels = {};
@@ -110,7 +111,13 @@ Object.keys(envOpt)
110
111
  .forEach((l) => {
111
112
  exitWithErrorIf(true, `Env ${l} specified both with --envs and --env`);
112
113
  });
113
- const envs = Object.assign(Object.assign(Object.assign({}, configFromFile.envs), envOpt), cliEnv); //Let cli arguments overide file
114
+ const envs = Object.assign(Object.assign(Object.assign({}, configFromFile.envs), envOpt), cliEnv); //Let cli arguments override file
115
+ const customContent = [];
116
+ (_e = configFromFile.customContent) === null || _e === void 0 ? void 0 : _e.forEach((c) => customContent.push(c));
117
+ (_f = cliOptions.customContent) === null || _f === void 0 ? void 0 : _f.split(",").forEach((c) => customContent.push(c));
118
+ const cliExtraContent = {};
119
+ (_g = cliOptions.extraContent) === null || _g === void 0 ? void 0 : _g.split(",").forEach((x) => setKeyValue(cliExtraContent, x, ":"));
120
+ const extraContent = Object.assign(Object.assign({}, configFromFile.extraContent), cliExtraContent);
114
121
  const cliParams = (0, utils_1.omit)(cliOptions, [
115
122
  "label",
116
123
  "labels",
@@ -119,9 +126,9 @@ const cliParams = (0, utils_1.omit)(cliOptions, [
119
126
  "customContent",
120
127
  "extraContent",
121
128
  ]);
122
- cliParams.customContent = (_e = cliOptions.customContent) === null || _e === void 0 ? void 0 : _e.split(",");
123
- cliParams.extraContent = (_f = cliOptions.extraContent) === null || _f === void 0 ? void 0 : _f.split(",").map((x) => x.split(":"));
124
- const options = Object.assign(Object.assign(Object.assign(Object.assign({}, defaultOptions), configFromFile), cliParams), { labels, envs: Object.entries(envs).map(([k, v]) => `${k}=${v}`) });
129
+ const options = Object.assign(Object.assign(Object.assign(Object.assign({}, defaultOptions), configFromFile), cliParams), { customContent,
130
+ extraContent,
131
+ labels, envs: Object.entries(envs).map(([k, v]) => `${k}=${v}`) });
125
132
  function exitWithErrorIf(check, error) {
126
133
  if (check) {
127
134
  logger_1.default.error("ERROR: " + error);
@@ -167,20 +174,29 @@ if (options.toRegistry && !options.toRegistry.endsWith("/"))
167
174
  options.toRegistry += "/";
168
175
  if (options.fromRegistry && !options.fromRegistry.endsWith("/"))
169
176
  options.fromRegistry += "/";
170
- if (!options.fromRegistry && !((_j = (_h = (_g = options.fromImage) === null || _g === void 0 ? void 0 : _g.split(":")) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.includes("/"))) {
177
+ if (!options.fromRegistry && !((_k = (_j = (_h = options.fromImage) === null || _h === void 0 ? void 0 : _h.split(":")) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.includes("/"))) {
171
178
  options.fromImage = "library/" + options.fromImage;
172
179
  }
173
- if (options.customContent) {
174
- options.customContent.forEach((p) => {
175
- exitWithErrorIf(!fs.existsSync(p), "Could not find " + p + " in the base folder " + options.folder);
176
- });
177
- }
178
- if (options.extraContent) {
179
- options.extraContent.forEach((p) => {
180
- exitWithErrorIf(p.length != 2, "Invalid extraContent - use comma between files/dirs, and : to separate local path and container path");
181
- exitWithErrorIf(!fs.existsSync(options.folder + p[0]), "Could not find `" + p[0] + "` in the folder " + options.folder);
182
- });
180
+ options.customContent.forEach((p) => {
181
+ exitWithErrorIf(!fs.existsSync(p), "Could not find " + p + " in the base folder " + options.folder);
182
+ });
183
+ if (options.layerCacheFolder) {
184
+ if (!fs.existsSync(options.layerCacheFolder)) {
185
+ try {
186
+ logger_1.default.info(`Layer cache folder does not exist. Creating ${options.layerCacheFolder} ...`);
187
+ fs.mkdirSync(options.layerCacheFolder, { recursive: true });
188
+ }
189
+ catch (e) {
190
+ exitWithErrorIf(true, "Failed to create layer cache folder");
191
+ }
192
+ }
193
+ if (!options.layerCacheFolder.endsWith("/")) {
194
+ options.layerCacheFolder += "/";
195
+ }
183
196
  }
197
+ Object.keys(options.extraContent).forEach(k => {
198
+ exitWithErrorIf(!fs.existsSync(options.folder + k), "Could not find `" + k + "` in the folder " + options.folder);
199
+ });
184
200
  function run(options) {
185
201
  var _a, _b;
186
202
  return __awaiter(this, void 0, void 0, function* () {
@@ -193,7 +209,7 @@ function run(options) {
193
209
  const fromRegistry = options.fromRegistry
194
210
  ? (0, registry_1.createRegistry)(options.fromRegistry, (_a = options.fromToken) !== null && _a !== void 0 ? _a : "")
195
211
  : (0, registry_1.createDockerRegistry)(options.fromToken);
196
- yield fromRegistry.download(options.fromImage, fromdir, (0, utils_1.getPreferredPlatform)(options.platform));
212
+ yield fromRegistry.download(options.fromImage, fromdir, (0, utils_1.getPreferredPlatform)(options.platform), options.layerCacheFolder);
197
213
  yield appLayerCreator_1.default.addLayers(tmpdir, fromdir, todir, options);
198
214
  if (options.toTar) {
199
215
  yield tarExporter_1.default.saveToTar(todir, tmpdir, options.toTar, [options.toImage], options);
package/lib/registry.js CHANGED
@@ -68,8 +68,23 @@ function dlJson(uri, headers) {
68
68
  return JSON.parse(Buffer.from(data).toString("utf-8"));
69
69
  });
70
70
  }
71
- function dlToFile(uri, file, headers) {
71
+ function dlToFile(uri, file, headers, cacheFolder, skipCache = false) {
72
72
  return new Promise((resolve, reject) => {
73
+ const [filename] = file.split("/").slice(-1);
74
+ if (cacheFolder && !skipCache) {
75
+ fss
76
+ .createReadStream(cacheFolder + filename)
77
+ .on("error", () => {
78
+ logger_1.default.debug("Not found in layer cache " + cacheFolder + filename + " - Downloading...");
79
+ dlToFile(uri, file, headers, cacheFolder, true).then(() => resolve());
80
+ })
81
+ .pipe(fss.createWriteStream(file))
82
+ .on("finish", () => {
83
+ logger_1.default.debug("Found in layer cache " + cacheFolder + filename);
84
+ resolve();
85
+ });
86
+ return;
87
+ }
73
88
  followRedirects(uri, headers, (result) => {
74
89
  var _a;
75
90
  if ("error" in result)
@@ -80,6 +95,10 @@ function dlToFile(uri, file, headers) {
80
95
  return reject(toError(res));
81
96
  res.pipe(fss.createWriteStream(file)).on("finish", () => {
82
97
  logger_1.default.debug("Done " + file + " - " + res.headers["content-length"] + " bytes ");
98
+ if (cacheFolder) {
99
+ logger_1.default.debug(`Writing ${file} to cache ${cacheFolder + filename}`);
100
+ fss.createReadStream(file).pipe(fss.createWriteStream(cacheFolder + filename));
101
+ }
83
102
  resolve();
84
103
  });
85
104
  });
@@ -245,11 +264,10 @@ function createRegistry(registryBaseUrl, token) {
245
264
  return yield dlJson(`${registryBaseUrl}${image.path}/blobs/${config.digest}`, buildHeaders("*/*", auth));
246
265
  });
247
266
  }
248
- function dlLayer(image, layer, folder) {
267
+ function dlLayer(image, layer, folder, cacheFolder) {
249
268
  return __awaiter(this, void 0, void 0, function* () {
250
- logger_1.default.info(layer.digest);
251
269
  const file = getHash(layer.digest) + (0, utils_1.getLayerTypeFileEnding)(layer);
252
- yield dlToFile(`${registryBaseUrl}${image.path}/blobs/${layer.digest}`, path.join(folder, file), buildHeaders(layer.mediaType, auth));
270
+ yield dlToFile(`${registryBaseUrl}${image.path}/blobs/${layer.digest}`, path.join(folder, file), buildHeaders(layer.mediaType, auth), cacheFolder);
253
271
  return file;
254
272
  });
255
273
  }
@@ -278,7 +296,7 @@ function createRegistry(registryBaseUrl, token) {
278
296
  yield uploadContent(`${registryBaseUrl}${image.path}/manifests/${image.tag}`, manifestFile, { mediaType: manifest.mediaType, size: manifestSize }, auth);
279
297
  });
280
298
  }
281
- function download(imageStr, folder, preferredPlatform) {
299
+ function download(imageStr, folder, preferredPlatform, cacheFolder) {
282
300
  return __awaiter(this, void 0, void 0, function* () {
283
301
  const image = parseImage(imageStr);
284
302
  logger_1.default.info("Downloading manifest...");
@@ -294,7 +312,7 @@ function createRegistry(registryBaseUrl, token) {
294
312
  }
295
313
  yield fs_1.promises.writeFile(path.join(folder, "config.json"), JSON.stringify(config));
296
314
  logger_1.default.info("Downloading layers...");
297
- yield Promise.all(manifest.layers.map((layer) => dlLayer(image, layer, folder)));
315
+ yield Promise.all(manifest.layers.map((layer) => dlLayer(image, layer, folder, cacheFolder)));
298
316
  logger_1.default.info("Image downloaded.");
299
317
  });
300
318
  }
@@ -312,12 +330,12 @@ function createDockerRegistry(auth) {
312
330
  return resp.token;
313
331
  });
314
332
  }
315
- function download(imageStr, folder, platform) {
333
+ function download(imageStr, folder, platform, cacheFolder) {
316
334
  return __awaiter(this, void 0, void 0, function* () {
317
335
  const image = parseImage(imageStr);
318
336
  if (!auth)
319
337
  auth = yield getToken(image);
320
- yield createRegistry(registryBaseUrl, auth).download(imageStr, folder, platform);
338
+ yield createRegistry(registryBaseUrl, auth).download(imageStr, folder, platform, cacheFolder);
321
339
  });
322
340
  }
323
341
  function upload(imageStr, folder) {
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VERSION = void 0;
4
- exports.VERSION = "2.0.1";
4
+ exports.VERSION = "2.2.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "containerify",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "Build node.js docker images without docker",
5
5
  "main": "./lib/cli.js",
6
6
  "scripts": {