containerify 2.5.2 → 2.6.1
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 +13 -0
- package/lib/appLayerCreator.js +5 -2
- package/lib/cli.js +10 -8
- package/lib/registry.d.ts +1 -1
- package/lib/registry.js +23 -5
- package/lib/types.d.ts +2 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,18 @@ This will pull the `node:13-slim` image from Docker hub, build the image by addi
|
|
|
18
18
|
containerify --fromImage node:13-slim --folder src/ --toImage myapp:latest --toRegistry https://registry.example.com/v2/ --setTimeStamp=$(git show -s --format="%aI" HEAD)
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
### customContent - Adding compiled code to non-node container
|
|
22
|
+
|
|
23
|
+
If you want to build a non-node container (e.g. add compiled frontend code to an nginx container), you can use `--customContent`. When doing this
|
|
24
|
+
the normal `node_modules` etc layers will not be added. By default it does _NOT_ modify then entrypoint, user or workdir, so the base image settings are still used when running. You can still override with `--entrypoint` etc. if needed.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
npm run build # or some other build command
|
|
28
|
+
containerify --fromImage nginx:alpine --folder . --toImage frontend:latest --customContent dist:/usr/share/nginx/html --toRegistry https://registry.example.com/v2/
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This will take the `nginx:alpine` image, and copy the files from `./dist/` into `/usr/share/nginx/html`.
|
|
32
|
+
|
|
21
33
|
### Command line options
|
|
22
34
|
|
|
23
35
|
```
|
|
@@ -31,6 +43,7 @@ Options:
|
|
|
31
43
|
--fromRegistry <registry url> Optional: URL of registry to pull base image from - Default: https://registry-1.docker.io/v2/
|
|
32
44
|
--fromToken <token> Optional: Authentication token for from registry
|
|
33
45
|
--toRegistry <registry url> Optional: URL of registry to push base image to - Default: https://registry-1.docker.io/v2/
|
|
46
|
+
--optimisticToRegistryCheck Optional: Treat redirects as layer existing in remote registry. Potentially unsafe, but could save bandwidth.
|
|
34
47
|
--toToken <token> Optional: Authentication token for target registry
|
|
35
48
|
--toTar <path> Optional: Export to tar file
|
|
36
49
|
--registry <path> Optional: Convenience argument for setting both from and to registry
|
package/lib/appLayerCreator.js
CHANGED
|
@@ -166,7 +166,10 @@ 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.length > 0) {
|
|
169
|
+
if (Object.entries(options.customContent).length > 0) {
|
|
170
|
+
// We only add these layers if they have been explicitely set for customContent. This allows customContent
|
|
171
|
+
// to be used to add compiled frontend code to an nginx container without also modifying the entrypoint, user,
|
|
172
|
+
// and workdir.
|
|
170
173
|
if (options.nonDefaults.workdir)
|
|
171
174
|
yield addWorkdirLayer(options, config, options.nonDefaults.workdir);
|
|
172
175
|
if (options.nonDefaults.entrypoint)
|
|
@@ -175,7 +178,7 @@ function addAppLayers(options, config, todir, manifest, tmpdir) {
|
|
|
175
178
|
yield addUserLayer(options, config, options.nonDefaults.user);
|
|
176
179
|
yield addEnvsLayer(options, config);
|
|
177
180
|
yield addLabelsLayer(options, config);
|
|
178
|
-
yield addDataLayer(tmpdir, todir, options, config, manifest, options.customContent, "custom");
|
|
181
|
+
yield addDataLayer(tmpdir, todir, options, config, manifest, Object.entries(options.customContent), "custom");
|
|
179
182
|
}
|
|
180
183
|
else {
|
|
181
184
|
yield addWorkdirLayer(options, config, options.workdir);
|
package/lib/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ const possibleArgs = {
|
|
|
32
32
|
"--fromRegistry <registry url>": "Optional: URL of registry to pull base image from - Default: https://registry-1.docker.io/v2/",
|
|
33
33
|
"--fromToken <token>": "Optional: Authentication token for from registry",
|
|
34
34
|
"--toRegistry <registry url>": "Optional: URL of registry to push base image to - Default: https://registry-1.docker.io/v2/",
|
|
35
|
+
"--optimisticToRegistryCheck": "Treat redirects as layer existing in remote registry. Potentially unsafe, but can save bandwidth.",
|
|
35
36
|
"--toToken <token>": "Optional: Authentication token for target registry",
|
|
36
37
|
"--toTar <path>": "Optional: Export to tar file",
|
|
37
38
|
"--toDocker": "Optional: Export to local docker registry",
|
|
@@ -48,16 +49,17 @@ const possibleArgs = {
|
|
|
48
49
|
"--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",
|
|
49
50
|
"--verbose": "Verbose logging",
|
|
50
51
|
"--allowInsecureRegistries": "Allow insecure registries (with self-signed/untrusted cert)",
|
|
51
|
-
"--customContent <dirs/files>": "Optional: Skip normal node_modules and applayer and include specified root folder files/directories instead",
|
|
52
|
+
"--customContent <dirs/files>": "Optional: Skip normal node_modules and applayer and include specified root folder files/directories instead. You can specify as local-path:absolute-container-path if you want to place it in a specific location",
|
|
52
53
|
"--extraContent <dirs/files>": "Optional: Add specific content. Specify as local-path:absolute-container-path,local-path2:absolute-container-path2 etc",
|
|
53
54
|
"--layerOwner <gid:uid>": "Optional: Set specific gid and uid on files in the added layers",
|
|
54
55
|
"--buildFolder <path>": "Optional: Use a specific build folder when creating the image",
|
|
55
56
|
"--layerCacheFolder <path>": "Optional: Folder to cache base layers between builds",
|
|
56
57
|
"--version": "Get containerify version",
|
|
57
58
|
};
|
|
58
|
-
function setKeyValue(target, keyValue, separator = "=") {
|
|
59
|
+
function setKeyValue(target, keyValue, separator = "=", defaultValue) {
|
|
60
|
+
var _a;
|
|
59
61
|
const [k, v] = keyValue.split(separator, 2);
|
|
60
|
-
target[k.trim()] = v.trim();
|
|
62
|
+
target[k.trim()] = (_a = v === null || v === void 0 ? void 0 : v.trim()) !== null && _a !== void 0 ? _a : defaultValue;
|
|
61
63
|
}
|
|
62
64
|
const cliLabels = {};
|
|
63
65
|
commander_1.program.on("option:label", (ops) => {
|
|
@@ -114,9 +116,9 @@ Object.keys(envOpt)
|
|
|
114
116
|
exitWithErrorIf(true, `Env ${l} specified both with --envs and --env`);
|
|
115
117
|
});
|
|
116
118
|
const envs = Object.assign(Object.assign(Object.assign({}, configFromFile.envs), envOpt), cliEnv); //Let cli arguments override file
|
|
117
|
-
const customContent =
|
|
118
|
-
(_e = configFromFile.customContent) === null || _e === void 0 ? void 0 : _e.forEach((c) => customContent
|
|
119
|
-
(_f = cliOptions.customContent) === null || _f === void 0 ? void 0 : _f.split(",").forEach((c) => customContent
|
|
119
|
+
const customContent = {};
|
|
120
|
+
(_e = configFromFile.customContent) === null || _e === void 0 ? void 0 : _e.forEach((c) => setKeyValue(customContent, c, ":", c));
|
|
121
|
+
(_f = cliOptions.customContent) === null || _f === void 0 ? void 0 : _f.split(",").forEach((c) => setKeyValue(customContent, c, ":", c));
|
|
120
122
|
const cliExtraContent = {};
|
|
121
123
|
(_g = cliOptions.extraContent) === null || _g === void 0 ? void 0 : _g.split(",").forEach((x) => setKeyValue(cliExtraContent, x, ":"));
|
|
122
124
|
const extraContent = Object.assign(Object.assign({}, configFromFile.extraContent), cliExtraContent);
|
|
@@ -184,7 +186,7 @@ if (options.fromRegistry && !options.fromRegistry.endsWith("/"))
|
|
|
184
186
|
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("/"))) {
|
|
185
187
|
options.fromImage = "library/" + options.fromImage;
|
|
186
188
|
}
|
|
187
|
-
options.customContent.forEach((p) => {
|
|
189
|
+
Object.keys(options.customContent).forEach((p) => {
|
|
188
190
|
exitWithErrorIf(!fs.existsSync(p), "Could not find " + p + " in the base folder " + options.folder);
|
|
189
191
|
});
|
|
190
192
|
if (options.layerCacheFolder) {
|
|
@@ -230,7 +232,7 @@ function run(options) {
|
|
|
230
232
|
yield tarExporter_1.default.saveToTar(todir, tmpdir, options.toTar, [options.toImage], options);
|
|
231
233
|
}
|
|
232
234
|
if (options.toRegistry) {
|
|
233
|
-
const toRegistry = (0, registry_1.createRegistry)(options.toRegistry, (_b = options.toToken) !== null && _b !== void 0 ? _b : "");
|
|
235
|
+
const toRegistry = (0, registry_1.createRegistry)(options.toRegistry, (_b = options.toToken) !== null && _b !== void 0 ? _b : "", options.optimisticToRegistryCheck);
|
|
234
236
|
yield toRegistry.upload(options.toImage, todir);
|
|
235
237
|
}
|
|
236
238
|
logger_1.default.debug("Deleting " + tmpdir + " ...");
|
package/lib/registry.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Platform } from "./types";
|
|
2
|
-
export declare function createRegistry(registryBaseUrl: string, token: string): {
|
|
2
|
+
export declare function createRegistry(registryBaseUrl: string, token: string, optimisticToRegistryCheck?: boolean): {
|
|
3
3
|
download: (imageStr: string, folder: string, preferredPlatform: Platform, cacheFolder?: string) => Promise<void>;
|
|
4
4
|
upload: (imageStr: string, folder: string) => Promise<void>;
|
|
5
5
|
};
|
package/lib/registry.js
CHANGED
|
@@ -13,15 +13,15 @@ exports.createDockerRegistry = exports.createRegistry = void 0;
|
|
|
13
13
|
const https = require("https");
|
|
14
14
|
const http = require("http");
|
|
15
15
|
const URL = require("url");
|
|
16
|
+
const fss = require("fs");
|
|
16
17
|
const fs_1 = require("fs");
|
|
17
18
|
const path = require("path");
|
|
18
19
|
const fse = require("fs-extra");
|
|
19
|
-
const fss = require("fs");
|
|
20
20
|
const fileutil = require("./fileutil");
|
|
21
21
|
const logger_1 = require("./logger");
|
|
22
22
|
const MIMETypes_1 = require("./MIMETypes");
|
|
23
23
|
const utils_1 = require("./utils");
|
|
24
|
-
const redirectCodes = [307, 303, 302];
|
|
24
|
+
const redirectCodes = [308, 307, 303, 302, 301];
|
|
25
25
|
function request(options, callback) {
|
|
26
26
|
return (options.protocol == "https:" ? https : http).request(options, (res) => {
|
|
27
27
|
callback(res);
|
|
@@ -128,18 +128,36 @@ function buildHeaders(accept, auth) {
|
|
|
128
128
|
headers.authorization = auth;
|
|
129
129
|
return headers;
|
|
130
130
|
}
|
|
131
|
-
function headOk(url, headers) {
|
|
131
|
+
function headOk(url, headers, optimisticCheck = false, depth = 0) {
|
|
132
|
+
if (depth >= 5) {
|
|
133
|
+
logger_1.default.info("Followed five redirects, assuming layer does not exist");
|
|
134
|
+
return new Promise((resolve) => resolve(false));
|
|
135
|
+
}
|
|
132
136
|
return new Promise((resolve, reject) => {
|
|
133
137
|
logger_1.default.debug(`HEAD ${url}`);
|
|
134
138
|
const options = URL.parse(url);
|
|
135
139
|
options.headers = headers;
|
|
136
140
|
options.method = "HEAD";
|
|
137
141
|
request(options, (res) => {
|
|
142
|
+
var _a;
|
|
138
143
|
logger_1.default.debug(`HEAD ${url}`, res.statusCode);
|
|
144
|
+
// Not found
|
|
139
145
|
if (res.statusCode == 404)
|
|
140
146
|
return resolve(false);
|
|
147
|
+
// OK
|
|
141
148
|
if (res.statusCode == 200)
|
|
142
149
|
return resolve(true);
|
|
150
|
+
// Redirected
|
|
151
|
+
if (redirectCodes.includes((_a = res.statusCode) !== null && _a !== void 0 ? _a : 0) && res.headers.location) {
|
|
152
|
+
if (optimisticCheck)
|
|
153
|
+
return resolve(true);
|
|
154
|
+
return resolve(headOk(res.headers.location, headers, optimisticCheck, ++depth));
|
|
155
|
+
}
|
|
156
|
+
// Unauthorized
|
|
157
|
+
// Possibly related to https://gitlab.com/gitlab-org/gitlab/-/issues/23132
|
|
158
|
+
if (res.statusCode == 401) {
|
|
159
|
+
return resolve(false);
|
|
160
|
+
}
|
|
143
161
|
reject(toError(res));
|
|
144
162
|
}).end();
|
|
145
163
|
});
|
|
@@ -175,12 +193,12 @@ function uploadContent(uploadUrl, file, fileConfig, auth) {
|
|
|
175
193
|
fss.createReadStream(file).pipe(req);
|
|
176
194
|
});
|
|
177
195
|
}
|
|
178
|
-
function createRegistry(registryBaseUrl, token) {
|
|
196
|
+
function createRegistry(registryBaseUrl, token, optimisticToRegistryCheck = false) {
|
|
179
197
|
const auth = token.startsWith("Basic ") ? token : "Bearer " + token;
|
|
180
198
|
function exists(image, layer) {
|
|
181
199
|
return __awaiter(this, void 0, void 0, function* () {
|
|
182
200
|
const url = `${registryBaseUrl}${image.path}/blobs/${layer.digest}`;
|
|
183
|
-
return yield headOk(url, buildHeaders(layer.mediaType, auth));
|
|
201
|
+
return yield headOk(url, buildHeaders(layer.mediaType, auth), optimisticToRegistryCheck, 0);
|
|
184
202
|
});
|
|
185
203
|
}
|
|
186
204
|
function uploadLayerContent(uploadUrl, layer, dir) {
|
package/lib/types.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export type Options = {
|
|
|
61
61
|
fromRegistry?: string;
|
|
62
62
|
fromToken?: string;
|
|
63
63
|
toRegistry?: string;
|
|
64
|
+
optimisticToRegistryCheck?: boolean;
|
|
64
65
|
toToken?: string;
|
|
65
66
|
toTar?: string;
|
|
66
67
|
toDocker?: boolean;
|
|
@@ -75,7 +76,7 @@ export type Options = {
|
|
|
75
76
|
setTimeStamp?: string;
|
|
76
77
|
verbose?: boolean;
|
|
77
78
|
allowInsecureRegistries?: boolean;
|
|
78
|
-
customContent: string
|
|
79
|
+
customContent: Record<string, string>;
|
|
79
80
|
extraContent: Record<string, string>;
|
|
80
81
|
layerOwner?: string;
|
|
81
82
|
buildFolder?: string;
|
package/lib/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "2.
|
|
1
|
+
export declare const VERSION = "2.6.1";
|
package/lib/version.js
CHANGED