containerify 3.2.1 → 3.3.2

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.
@@ -1,5 +1,5 @@
1
- import { Options } from "./types";
2
- declare function addLayers(tmpdir: string, fromdir: string, todir: string, options: Options): Promise<void>;
1
+ import { ManifestDescriptor, Options } from "./types";
2
+ declare function addLayers(tmpdir: string, fromdir: string, todir: string, options: Options): Promise<ManifestDescriptor>;
3
3
  declare const _default: {
4
4
  addLayers: typeof addLayers;
5
5
  };
@@ -22,39 +22,25 @@ const utils_1 = require("./utils");
22
22
  const version_1 = require("./version");
23
23
  const depLayerPossibles = ["package.json", "package-lock.json", "node_modules"];
24
24
  const ignore = [".git", ".gitignore", ".npmrc", ".DS_Store", "npm-debug.log", ".svn", ".hg", "CVS"];
25
- function statCache(layerOwner) {
25
+ function createOnWriteEntry(layerOwner) {
26
26
  if (!layerOwner)
27
- return null;
28
- // We use the stat cache to overwrite uid and gid in image.
29
- // A bit hacky
30
- const statCacheMap = new Map();
31
- const a = layerOwner.split(":");
32
- const gid = parseInt(a[0]);
33
- const uid = parseInt(a[1]);
34
- return {
35
- get: function (name) {
36
- if (statCacheMap.has(name))
37
- return statCacheMap.get(name);
38
- const stat = fss.statSync(name);
39
- stat.uid = uid;
40
- stat.gid = gid;
41
- stat.atime = new Date(0);
42
- stat.mtime = new Date(0);
43
- stat.ctime = new Date(0);
44
- stat.birthtime = new Date(0);
45
- stat.atimeMs = 0;
46
- stat.mtimeMs = 0;
47
- stat.ctimeMs = 0;
48
- stat.birthtimeMs = 0;
49
- statCacheMap.set(name, stat);
50
- return stat;
51
- },
52
- set: function (name, stat) {
53
- statCacheMap.set(name, stat);
54
- },
55
- has: function () {
56
- return true;
57
- },
27
+ return undefined;
28
+ // We use onWriteEntry to overwrite uid and gid in the tar archive
29
+ // Format is already validated in cli.ts to be "gid:uid"
30
+ const parts = layerOwner.split(":");
31
+ const gid = parseInt(parts[0], 10);
32
+ const uid = parseInt(parts[1], 10);
33
+ return (entry) => {
34
+ if (entry.header) {
35
+ entry.header.uid = uid;
36
+ entry.header.gid = gid;
37
+ entry.header.uname = "";
38
+ entry.header.gname = "";
39
+ // Set all timestamps to epoch to match original behavior
40
+ entry.header.atime = new Date(0);
41
+ entry.header.mtime = new Date(0);
42
+ entry.header.ctime = new Date(0);
43
+ }
58
44
  };
59
45
  }
60
46
  const tarDefaultConfig = {
@@ -127,7 +113,7 @@ function addDataLayer(tmpdir, todir, options, config, manifest, files, comment)
127
113
  comment +
128
114
  (comment == "dependencies" ? ". Did you forget to run npm install?" : ""));
129
115
  }
130
- yield tar.create(Object.assign(Object.assign({}, tarDefaultConfig), Object.assign({ statCache: statCache(options.layerOwner), portable: !options.layerOwner, prefix: "/", cwd: buildDir, file: layerFile, gzip: true, noMtime: !(options.setTimeStamp || options.preserveTimeStamp) }, (options.setTimeStamp ? { mtime: new Date(options.setTimeStamp) } : {}))), filesToTar);
116
+ yield tar.create(Object.assign(Object.assign({}, tarDefaultConfig), Object.assign({ onWriteEntry: createOnWriteEntry(options.layerOwner), portable: !options.layerOwner, prefix: "/", cwd: buildDir, file: layerFile, gzip: true, noMtime: !(options.setTimeStamp || options.preserveTimeStamp) }, (options.setTimeStamp ? { mtime: new Date(options.setTimeStamp) } : {}))), filesToTar);
131
117
  const fhash = yield calculateHash(layerFile);
132
118
  const finalName = path.join(todir, fhash + ".tar.gz");
133
119
  yield fse.move(layerFile, finalName);
@@ -253,7 +239,14 @@ function addLayers(tmpdir, fromdir, todir, options) {
253
239
  yield fs_1.promises.writeFile(configFile, configContent);
254
240
  manifest.config.digest = "sha256:" + configHash;
255
241
  manifest.config.size = yield fileutil.sizeOf(configFile);
256
- yield fs_1.promises.writeFile(path.join(todir, "manifest.json"), JSON.stringify(manifest));
242
+ const manifestJson = JSON.stringify(manifest);
243
+ yield fs_1.promises.writeFile(path.join(todir, "manifest.json"), manifestJson);
244
+ const manifestBuffer = Buffer.from(manifestJson, "utf-8");
245
+ return {
246
+ mediaType: manifest.mediaType,
247
+ digest: calculateHashOfBuffer(manifestBuffer),
248
+ size: manifestBuffer.byteLength,
249
+ };
257
250
  });
258
251
  }
259
252
  exports.default = {
package/lib/cli.js CHANGED
@@ -61,6 +61,7 @@ program
61
61
  .option("--layerOwner <gid:uid>", "Optional: Set specific gid and uid on files in the added layers")
62
62
  .option("--buildFolder <path>", "Optional: Use a specific build folder when creating the image")
63
63
  .option("--layerCacheFolder <path>", "Optional: Folder to cache base layers between builds")
64
+ .option("--writeDigestTo <path>", "Optional: Write the resulting image digest to the file path provided")
64
65
  .version(version_1.VERSION, "--version", "Get containerify version");
65
66
  program.parse(process.argv);
66
67
  function setKeyValue(target, keyValue, separator = "=", defaultValue) {
@@ -96,7 +97,6 @@ const labels = Object.assign(Object.assign({}, configFromFile.labels), labelsOpt
96
97
  const envOpt = {};
97
98
  (_b = cliOptions.envs) === null || _b === void 0 ? void 0 : _b.forEach((envs) => envs.split(",").forEach((env) => setKeyValue(envOpt, env)));
98
99
  const envs = Object.assign(Object.assign({}, configFromFile.envs), envOpt); //Let cli arguments override file
99
- console.log(envs);
100
100
  const customContent = {};
101
101
  (_c = configFromFile.customContent) === null || _c === void 0 ? void 0 : _c.forEach((c) => setKeyValue(customContent, c, ":", c));
102
102
  (_d = cliOptions.customContent) === null || _d === void 0 ? void 0 : _d.forEach((contents) => contents.split(",").forEach((content) => setKeyValue(customContent, content, ":", content)));
@@ -118,7 +118,7 @@ const options = Object.assign(Object.assign(Object.assign({}, defaultOptions), s
118
118
  user: setOptions.user,
119
119
  workdir: setOptions.workdir,
120
120
  entrypoint: setOptions.entrypoint,
121
- } });
121
+ }, writeDigestTo: cliOptions.writeDigestTo });
122
122
  function exitWithErrorIf(check, error) {
123
123
  if (check) {
124
124
  logger_1.default.error("ERROR: " + error);
@@ -214,7 +214,7 @@ function run(options) {
214
214
  const fromRegistryUrl = (_a = options.fromRegistry) !== null && _a !== void 0 ? _a : registry_1.DEFAULT_DOCKER_REGISTRY;
215
215
  const fromRegistry = yield (0, registry_1.createRegistry)(fromRegistryUrl, options.fromImage, allowInsecure, options.fromToken);
216
216
  const originalManifest = yield fromRegistry.download(options.fromImage, fromdir, (0, utils_1.getPreferredPlatform)(options.platform), options.layerCacheFolder);
217
- yield appLayerCreator_1.default.addLayers(tmpdir, fromdir, todir, options);
217
+ const manifestDescriptor = yield appLayerCreator_1.default.addLayers(tmpdir, fromdir, todir, options);
218
218
  if (options.toDocker) {
219
219
  if (!(yield dockerExporter_1.default.isAvailable())) {
220
220
  throw new Error("Docker executable not found on path. Unable to export to local docker registry.");
@@ -233,6 +233,10 @@ function run(options) {
233
233
  logger_1.default.debug(`Deleting ${tmpdir} ...`);
234
234
  yield fse.remove(tmpdir);
235
235
  logger_1.default.debug("Done");
236
+ if (options.writeDigestTo) {
237
+ logger_1.default.debug(`Writing digest ${manifestDescriptor.digest} to ${options.writeDigestTo}`);
238
+ fs.writeFileSync(options.writeDigestTo, manifestDescriptor.digest);
239
+ }
236
240
  });
237
241
  }
238
242
  logger_1.default.debug("Running with config:", options);
@@ -20,7 +20,6 @@ exports.dlJson = dlJson;
20
20
  exports.followRedirects = followRedirects;
21
21
  const https = require("https");
22
22
  const http = require("http");
23
- const URL = require("url");
24
23
  const logger_1 = require("./logger");
25
24
  const types_1 = require("./types");
26
25
  exports.redirectCodes = [308, 307, 303, 302, 301];
@@ -28,9 +27,15 @@ function isOk(httpStatus) {
28
27
  return httpStatus >= 200 && httpStatus < 300;
29
28
  }
30
29
  function createHttpOptions(method, url, headers) {
31
- const options = Object.assign({}, URL.parse(url));
32
- options.headers = headers;
33
- options.method = method;
30
+ const parsedUrl = new URL(url);
31
+ const options = {
32
+ protocol: parsedUrl.protocol,
33
+ hostname: parsedUrl.hostname,
34
+ port: parsedUrl.port,
35
+ path: parsedUrl.pathname + parsedUrl.search,
36
+ headers: headers,
37
+ method: method,
38
+ };
34
39
  if (url.includes("X-Amz-Algorithm") && method == "GET") {
35
40
  //We are using a pre-signed URL, so we don't need to send the Authorization header
36
41
  options.headers["Authorization"] = "";
@@ -101,7 +106,6 @@ function followRedirects(uri, headers, allowInsecure, cb, count = 0) {
101
106
  if (count > 10)
102
107
  return cb({ error: "Too many redirects for " + uri });
103
108
  const location = res.headers.location;
104
- console.log(res.headers);
105
109
  if (!location)
106
110
  return cb({ error: "Redirect, but missing location header" });
107
111
  return followRedirects(location, headers, allowInsecure, cb, count + 1);
package/lib/registry.js CHANGED
@@ -12,7 +12,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.DEFAULT_DOCKER_REGISTRY = void 0;
13
13
  exports.createRegistry = createRegistry;
14
14
  exports.parseFullImageUrl = parseFullImageUrl;
15
- const URL = require("url");
16
15
  const fss = require("fs");
17
16
  const fs_1 = require("fs");
18
17
  const path = require("path");
@@ -130,7 +129,7 @@ function uploadContent(uploadUrl, file, fileConfig, allowInsecure, auth, content
130
129
  }
131
130
  function processToken(registryBaseUrl, allowInsecure, imagePath, token) {
132
131
  return __awaiter(this, void 0, void 0, function* () {
133
- const { hostname } = new URL.URL(registryBaseUrl);
132
+ const { hostname } = new URL(registryBaseUrl);
134
133
  const image = (0, utils_1.parseImage)(imagePath);
135
134
  if ((hostname === null || hostname === void 0 ? void 0 : hostname.endsWith(".docker.io")) && !token)
136
135
  return getDockerToken(image.path, allowInsecure);
@@ -182,8 +181,14 @@ function createRegistry(registryBaseUrl_1, imagePath_1, allowInsecure_1, auth_1)
182
181
  return new Promise((resolve, reject) => {
183
182
  const parameters = new URLSearchParams(mountParameters);
184
183
  const url = `${registryBaseUrl}${image.path}/blobs/uploads/${parameters.size > 0 ? "?" + parameters : ""}`;
185
- const options = URL.parse(url);
186
- options.method = "POST";
184
+ const parsedUrl = new URL(url);
185
+ const options = {
186
+ protocol: parsedUrl.protocol,
187
+ hostname: parsedUrl.hostname,
188
+ port: parsedUrl.port,
189
+ path: parsedUrl.pathname + parsedUrl.search,
190
+ method: "POST",
191
+ };
187
192
  if (token)
188
193
  options.headers = { authorization: token };
189
194
  (0, httpRequest_1.request)(options, allowInsecure, (res) => {
@@ -195,7 +200,7 @@ function createRegistry(registryBaseUrl_1, imagePath_1, allowInsecure_1, auth_1)
195
200
  resolve({ uploadUrl: location });
196
201
  }
197
202
  else {
198
- const regURL = URL.parse(registryBaseUrl);
203
+ const regURL = new URL(registryBaseUrl);
199
204
  resolve({
200
205
  uploadUrl: `${regURL.protocol}//${regURL.hostname}${regURL.port ? ":" + regURL.port : ""}${location}`,
201
206
  });
package/lib/types.d.ts CHANGED
@@ -91,6 +91,7 @@ export type Options = {
91
91
  workdir?: string;
92
92
  entrypoint?: string;
93
93
  };
94
+ writeDigestTo?: string;
94
95
  };
95
96
  export declare enum InsecureRegistrySupport {
96
97
  NO = 0,
@@ -101,4 +102,5 @@ export type Registry = {
101
102
  upload: (imageStr: string, folder: string, doCrossMount: boolean, originalManifest: Manifest, originalRepository: string) => Promise<void>;
102
103
  registryBaseUrl: string;
103
104
  };
105
+ export type ManifestDescriptor = Descriptor;
104
106
  export {};
package/lib/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "3.2.1";
1
+ export declare const VERSION = "3.3.2";
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 = "3.2.1";
4
+ exports.VERSION = "3.3.2";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "containerify",
3
- "version": "3.2.1",
3
+ "version": "3.3.2",
4
4
  "description": "Build node.js docker images without docker",
5
5
  "main": "./lib/cli.js",
6
6
  "scripts": {
@@ -37,8 +37,8 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "commander": "^13.1.0",
40
- "fs-extra": "^11.3.0",
41
- "tar": "^6.2.1"
40
+ "fs-extra": "^11.3.2",
41
+ "tar": "^7.5.7"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@eslint/eslintrc": "^3.3.1",