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 +8 -4
- package/lib/appLayerCreator.js +3 -5
- package/lib/cli.js +35 -19
- package/lib/registry.js +26 -8
- package/lib/version.js +1 -1
- package/package.json +1 -1
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.
|
|
43
|
-
--envs <envs> Optional: Comma-separated list of key value
|
|
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
|
|
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
|
-
|
|
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
|
package/lib/appLayerCreator.js
CHANGED
|
@@ -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
|
-
|
|
191
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 && !((
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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