@magda/docker-utils 2.3.3 → 3.0.0-alpha.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/package.json CHANGED
@@ -1,24 +1,32 @@
1
1
  {
2
2
  "name": "@magda/docker-utils",
3
3
  "description": "MAGDA Docker Utilities",
4
- "version": "2.3.3",
4
+ "version": "3.0.0-alpha.1",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./dist/docker-util.js",
8
+ "./dist/*.js": "./dist/*.js",
9
+ "./package.json": "./package.json"
10
+ },
11
+ "engines": {
12
+ "node": ">=16.0.0"
13
+ },
5
14
  "bin": {
6
- "create-docker-context-for-node-component": "./bin/create-docker-context-for-node-component.js",
7
- "retag-and-push": "./bin/retag-and-push.js"
15
+ "create-docker-context-for-node-component": "./dist/create-docker-context-for-node-component.js",
16
+ "retag-and-push": "./dist/retag-and-push.js"
8
17
  },
9
18
  "scripts": {
10
- "prebuild": "rimraf bin",
11
- "build": "node build.js",
19
+ "prebuild": "rimraf dist",
20
+ "build": "node esbuild.js",
12
21
  "release": "npm publish || echo \"Skip releasing npm package @magda/docker-utils.\""
13
22
  },
14
23
  "author": "",
15
24
  "license": "Apache-2.0",
16
- "main": "bin/create-secrets.js",
17
25
  "devDependencies": {
18
- "@magda/scripts": "^2.3.3"
26
+ "@magda/scripts": "^3.0.0-alpha.1"
19
27
  },
20
28
  "dependencies": {
21
- "fs-extra": "^2.1.2",
29
+ "fs-extra": "^11.2.0",
22
30
  "is-subdir": "^1.0.2",
23
31
  "lodash": "^4.17.5",
24
32
  "yargs": "^12.0.5"
@@ -37,6 +45,6 @@
37
45
  "docker-utils"
38
46
  ],
39
47
  "files": [
40
- "bin"
48
+ "dist"
41
49
  ]
42
50
  }
@@ -1,446 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const childProcess = require("child_process");
4
- const fse = require("fs-extra");
5
- const path = require("path");
6
- const process = require("process");
7
- const yargs = require("yargs");
8
- const _ = require("lodash");
9
- const isSubDir = require("is-subdir");
10
- const {
11
- getVersions,
12
- getTags,
13
- getName,
14
- getRepository
15
- } = require("./docker-util");
16
-
17
- // --- cache dependencies data from package.json
18
- const packageDependencyDataCache = {};
19
- const argv = yargs
20
- .options({
21
- build: {
22
- description: "Pipe the Docker context straight to Docker.",
23
- type: "boolean",
24
- default: false
25
- },
26
- tag: {
27
- description:
28
- 'The tag to pass to "docker build". This parameter is only used if --build is specified. If the value of this parameter is `auto`, a tag name is automatically created from NPM configuration.',
29
- type: "string",
30
- default: "auto"
31
- },
32
- repository: {
33
- description:
34
- "The repository to use in auto tag generation. Will default to '', i.e. dockerhub unless --local is set. Requires --tag=auto",
35
- type: "string",
36
- default: process.env.MAGDA_DOCKER_REPOSITORY
37
- },
38
- name: {
39
- description:
40
- "The package name to use in auto tag generation. Will default to ''. Used to override the docker nanme config in package.json during the auto tagging. Requires --tag=auto",
41
- type: "string",
42
- default: process.env.MAGDA_DOCKER_NAME
43
- },
44
- version: {
45
- description:
46
- "The version(s) to use in auto tag generation. Will default to the current version in package.json. Requires --tag=auto",
47
- type: "string",
48
- array: true,
49
- default: process.env.MAGDA_DOCKER_VERSION
50
- },
51
- output: {
52
- description:
53
- "The output path and filename for the Docker context .tar file.",
54
- type: "string"
55
- },
56
- local: {
57
- description:
58
- "Build for a local Kubernetes container registry. This parameter is only used if --build is specified.",
59
- type: "boolean",
60
- default: false
61
- },
62
- push: {
63
- description:
64
- "Push the build image to the docker registry. This parameter is only used if --build is specified.",
65
- type: "boolean",
66
- default: false
67
- },
68
- platform: {
69
- description:
70
- "A list of platform that the docker image build should target. Specify this value will enable multi-arch image build.",
71
- type: "string"
72
- },
73
- noCache: {
74
- description: "Disable the cache during the docker image build.",
75
- type: "boolean",
76
- default: false
77
- },
78
- cacheFromVersion: {
79
- description:
80
- "Version to cache from when building, using the --cache-from field in docker. Will use the same repository and name. Using this options causes the image to be pulled before build.",
81
- type: "string"
82
- }
83
- })
84
- // Because 'version is a default yargs thing we need to specifically override its normal parsing.
85
- .version(false)
86
- .array("version")
87
- .help().argv;
88
-
89
- if (!argv.build && !argv.output) {
90
- console.log("Either --build or --output <filename> must be specified.");
91
- process.exit(1);
92
- }
93
-
94
- if (argv.platform && !argv.push) {
95
- console.log(
96
- "When --platform is specified, --push must be specified as well as multi-arch image can only be pushed to remote registry."
97
- );
98
- process.exit(1);
99
- }
100
-
101
- if (argv.noCache && argv.cacheFromVersion) {
102
- console.log("When --noCache=true, --cacheFromVersion can't be specified.");
103
- process.exit(1);
104
- }
105
-
106
- const componentSrcDir = path.resolve(process.cwd());
107
- const dockerContextDir = fse.mkdtempSync(
108
- path.resolve(__dirname, "..", "docker-context-")
109
- );
110
- const componentDestDir = path.resolve(dockerContextDir, "component");
111
-
112
- fse.emptyDirSync(dockerContextDir);
113
- fse.ensureDirSync(componentDestDir);
114
-
115
- preparePackage(componentSrcDir, componentDestDir);
116
-
117
- const tar = process.platform === "darwin" ? "gtar" : "tar";
118
-
119
- // Docker and ConEmu (an otherwise excellent console for Windows) don't get along.
120
- // See: https://github.com/Maximus5/ConEmu/issues/958 and https://github.com/moby/moby/issues/28814
121
- // So if we're running under ConEmu, we need to add an extra -cur_console:i parameter to disable
122
- // ConEmu's hooks and also set ConEmuANSI to OFF so Docker doesn't do anything drastic.
123
- const env = Object.assign({}, process.env);
124
- const extraParameters = [];
125
- if (env.ConEmuANSI === "ON") {
126
- env.ConEmuANSI = "OFF";
127
- extraParameters.push("-cur_console:i");
128
- }
129
-
130
- updateDockerFile(componentSrcDir, componentDestDir);
131
-
132
- if (argv.build) {
133
- const cacheFromImage =
134
- argv.cacheFromVersion &&
135
- getRepository(argv.local, argv.repository) +
136
- getName(argv.name) +
137
- ":" +
138
- argv.cacheFromVersion;
139
-
140
- if (cacheFromImage) {
141
- // Pull this image into the docker daemon - if it fails we don't care, we'll just go from scratch.
142
- const dockerPullProcess = childProcess.spawnSync(
143
- "docker",
144
- [...extraParameters, "pull", cacheFromImage],
145
- {
146
- stdio: "inherit",
147
- env: env
148
- }
149
- );
150
- wrapConsoleOutput(dockerPullProcess);
151
- }
152
-
153
- const tarProcess = childProcess.spawn(
154
- tar,
155
- [...extraParameters, "--dereference", "-czf", "-", "*"],
156
- {
157
- cwd: dockerContextDir,
158
- stdio: ["inherit", "pipe", "inherit"],
159
- env: env,
160
- shell: true
161
- }
162
- );
163
- const tags = getTags(
164
- argv.tag,
165
- argv.local,
166
- argv.repository,
167
- argv.version,
168
- argv.name
169
- );
170
- const tagArgs = tags
171
- .map((tag) => ["-t", tag])
172
- .reduce((soFar, tagArgs) => soFar.concat(tagArgs), []);
173
-
174
- const cacheFromArgs = cacheFromImage
175
- ? ["--cache-from", cacheFromImage]
176
- : [];
177
-
178
- const dockerProcess = childProcess.spawn(
179
- "docker",
180
- [
181
- ...extraParameters,
182
- ...(argv.platform ? ["buildx"] : []),
183
- "build",
184
- ...tagArgs,
185
- ...cacheFromArgs,
186
- ...(argv.noCache ? ["--no-cache"] : []),
187
- ...(argv.platform ? ["--platform", argv.platform, "--push"] : []),
188
- "-f",
189
- `./component/Dockerfile`,
190
- "-"
191
- ],
192
- {
193
- stdio: ["pipe", "inherit", "inherit"],
194
- env: env
195
- }
196
- );
197
-
198
- wrapConsoleOutput(dockerProcess);
199
-
200
- dockerProcess.on("close", (code) => {
201
- fse.removeSync(dockerContextDir);
202
-
203
- if (code === 0 && argv.push && !argv.platform) {
204
- if (tags.length === 0) {
205
- console.error("Can not push an image without a tag.");
206
- process.exit(1);
207
- }
208
-
209
- // Stop if there's a code !== 0
210
- tags.every((tag) => {
211
- const process = childProcess.spawnSync(
212
- "docker",
213
- ["push", tag],
214
- {
215
- stdio: "inherit"
216
- }
217
- );
218
-
219
- code = process.status;
220
-
221
- return code === 0;
222
- });
223
- }
224
- process.exit(code);
225
- });
226
-
227
- tarProcess.on("close", (code) => {
228
- dockerProcess.stdin.end();
229
- });
230
-
231
- tarProcess.stdout.on("data", (data) => {
232
- dockerProcess.stdin.write(data);
233
- });
234
- } else if (argv.output) {
235
- const outputPath = path.resolve(process.cwd(), argv.output);
236
-
237
- const outputTar = fse.openSync(outputPath, "w", 0o644);
238
-
239
- const tarProcess = childProcess.spawn(
240
- tar,
241
- ["--dereference", "-czf", "-", "*"],
242
- {
243
- cwd: dockerContextDir,
244
- stdio: ["inherit", outputTar, "inherit"],
245
- env: env,
246
- shell: true
247
- }
248
- );
249
-
250
- tarProcess.on("close", (code) => {
251
- fse.closeSync(outputTar);
252
- console.log(tarProcess.status);
253
- fse.removeSync(dockerContextDir);
254
- });
255
- }
256
-
257
- function updateDockerFile(sourceDir, destDir) {
258
- const tags = getVersions(argv.local, argv.version);
259
- const repository = getRepository(argv.local, argv.repository);
260
- const dockerFileContents = fse.readFileSync(
261
- path.resolve(sourceDir, "Dockerfile"),
262
- "utf-8"
263
- );
264
- const replacedDockerFileContents = dockerFileContents
265
- // Add a repository if this is a magda image
266
- .replace(
267
- /FROM .*(magda-[^:\s\/]+)(:[^\s]+)/,
268
- "FROM " + repository + "$1" + (tags[0] ? ":" + tags[0] : "$2")
269
- );
270
-
271
- fse.writeFileSync(
272
- path.resolve(destDir, "Dockerfile"),
273
- replacedDockerFileContents,
274
- "utf-8"
275
- );
276
- }
277
-
278
- function preparePackage(packageDir, destDir) {
279
- const packageJson = require(path.join(packageDir, "package.json"));
280
- const dockerIncludesFromPackageJson =
281
- packageJson.config &&
282
- packageJson.config.docker &&
283
- packageJson.config.docker.include;
284
-
285
- let dockerIncludes;
286
- if (!dockerIncludesFromPackageJson) {
287
- console.log(
288
- `WARNING: Package ${packageDir} does not have a config.docker.include key in package.json, so all of its files will be included in the docker image.`
289
- );
290
- dockerIncludes = fse.readdirSync(packageDir);
291
- } else if (dockerIncludesFromPackageJson.trim() === "*") {
292
- dockerIncludes = fse.readdirSync(packageDir);
293
- } else {
294
- if (dockerIncludesFromPackageJson.indexOf("*") >= 0) {
295
- throw new Error(
296
- "Sorry, wildcards are not currently supported in config.docker.include."
297
- );
298
- }
299
- dockerIncludes = dockerIncludesFromPackageJson
300
- .split(" ")
301
- .filter((include) => include.length > 0);
302
- }
303
-
304
- dockerIncludes
305
- .filter((include) => include !== "Dockerfile") // Filter out the dockerfile because we'll manually copy over a modified version.
306
- .forEach(function (include) {
307
- const src = path.resolve(packageDir, include);
308
- const dest = path.resolve(destDir, include);
309
-
310
- if (include === "node_modules") {
311
- fse.ensureDirSync(dest);
312
-
313
- const env = Object.create(process.env);
314
- env.NODE_ENV = "production";
315
-
316
- const productionPackages = _.uniqBy(
317
- getPackageList(packageDir, path.resolve(packageDir, "..")),
318
- (package) => package.path
319
- );
320
-
321
- prepareNodeModules(src, dest, productionPackages);
322
-
323
- return;
324
- }
325
-
326
- try {
327
- // On Windows we can't create symlinks to files without special permissions.
328
- // So just copy the file instead. Usually creating directory junctions is
329
- // fine without special permissions, but fall back on copying in the unlikely
330
- // event that fails, too.
331
- const type = fse.statSync(src).isFile() ? "file" : "junction";
332
- fse.ensureSymlinkSync(src, dest, type);
333
- } catch (e) {
334
- fse.copySync(src, dest);
335
- }
336
- });
337
- }
338
-
339
- function prepareNodeModules(packageDir, destDir, productionPackages) {
340
- productionPackages.forEach((src) => {
341
- const relativePath = path.relative(packageDir, src.path);
342
- const dest = path.resolve(destDir, relativePath);
343
- const srcPath = path.resolve(packageDir, relativePath);
344
-
345
- // console.log("src " + srcPath + " to " + dest);
346
-
347
- try {
348
- const stat = fse.lstatSync(srcPath);
349
- const type = stat.isFile() ? "file" : "junction";
350
- fse.ensureSymlinkSync(srcPath, dest, type);
351
- } catch (e) {
352
- fse.copySync(srcPath, dest);
353
- }
354
- });
355
- }
356
-
357
- function getPackageList(packagePath, packageSearchRoot, resolvedSoFar = {}) {
358
- const dependencies = getPackageDependencies(packagePath);
359
- const result = [];
360
-
361
- if (!dependencies || !dependencies.length) {
362
- return result;
363
- }
364
-
365
- dependencies.forEach(function (dependencyName) {
366
- const dependencyNamePath = dependencyName.replace(/\//g, path.sep);
367
-
368
- let currentBaseDir = packagePath;
369
- let dependencyDir;
370
-
371
- do {
372
- dependencyDir = path.resolve(
373
- currentBaseDir,
374
- "node_modules",
375
- dependencyNamePath
376
- );
377
-
378
- if (
379
- currentBaseDir === packageSearchRoot ||
380
- isSubDir(currentBaseDir, packageSearchRoot)
381
- ) {
382
- // --- will not look for packages outside project root directory
383
- break;
384
- }
385
-
386
- // Does this directory exist? If not, imitate node's module resolution by walking
387
- // up the directory tree.
388
- currentBaseDir = path.resolve(currentBaseDir, "..");
389
- } while (!fse.existsSync(dependencyDir));
390
-
391
- if (!fse.existsSync(dependencyDir)) {
392
- throw new Error(
393
- "Could not find path for " +
394
- dependencyName +
395
- " @ " +
396
- packagePath
397
- );
398
- }
399
-
400
- // If we haven't already seen this
401
- if (!resolvedSoFar[dependencyDir]) {
402
- result.push({ name: dependencyName, path: dependencyDir });
403
-
404
- // Now that we've added this package to the list to resolve, add all its children.
405
-
406
- const childPackageResult = getPackageList(
407
- dependencyDir,
408
- packageSearchRoot,
409
- { ...resolvedSoFar, [dependencyDir]: true }
410
- );
411
-
412
- Array.prototype.push.apply(result, childPackageResult);
413
- }
414
- });
415
-
416
- return result;
417
- }
418
-
419
- function getPackageDependencies(packagePath) {
420
- const packageJsonPath = path.resolve(packagePath, "package.json");
421
-
422
- if (packageDependencyDataCache[packageJsonPath]) {
423
- return packageDependencyDataCache[packageJsonPath];
424
- }
425
- const pkgData = fse.readJSONSync(packageJsonPath);
426
- const depData = pkgData["dependencies"];
427
- if (!depData) {
428
- packageDependencyDataCache[packageJsonPath] = [];
429
- } else {
430
- packageDependencyDataCache[packageJsonPath] = Object.keys(depData);
431
- }
432
- return packageDependencyDataCache[packageJsonPath];
433
- }
434
-
435
- function wrapConsoleOutput(process) {
436
- if (process.stdout) {
437
- process.stdout.on("data", (data) => {
438
- console.log(data.toString());
439
- });
440
- }
441
- if (process.stderr) {
442
- process.stderr.on("data", (data) => {
443
- console.error(data.toString());
444
- });
445
- }
446
- }
@@ -1,37 +0,0 @@
1
- exports.getVersions = function getVersions(local, version) {
2
- return (
3
- version || [
4
- !local && process.env.npm_package_version
5
- ? process.env.npm_package_version
6
- : "latest"
7
- ]
8
- );
9
- };
10
-
11
- exports.getName = function getName(name) {
12
- if (name && typeof name === "string") {
13
- return name;
14
- }
15
- return process.env.npm_package_config_docker_name
16
- ? process.env.npm_package_config_docker_name
17
- : process.env.npm_package_name
18
- ? "data61/magda-" + process.env.npm_package_name.split("/")[1]
19
- : "UnnamedImage";
20
- };
21
-
22
- exports.getTags = function getTags(tag, local, repository, version, name) {
23
- if (tag === "auto") {
24
- return exports.getVersions(local, version).map((version) => {
25
- const tagPrefix = exports.getRepository(local, repository);
26
- const imageName = exports.getName(name);
27
-
28
- return tagPrefix + imageName + ":" + version;
29
- });
30
- } else {
31
- return tag ? [tag] : [];
32
- }
33
- };
34
-
35
- exports.getRepository = function getRepository(local, repository) {
36
- return (repository && repository + "/") || (local ? "localhost:5000/" : "");
37
- };
@@ -1,122 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const yargs = require("yargs");
4
- const childProcess = require("child_process");
5
- const {
6
- getVersions,
7
- getTags,
8
- getName,
9
- getRepository
10
- } = require("./docker-util");
11
-
12
- // Docker and ConEmu (an otherwise excellent console for Windows) don't get along.
13
- // See: https://github.com/Maximus5/ConEmu/issues/958 and https://github.com/moby/moby/issues/28814
14
- // So if we're running under ConEmu, we need to add an extra -cur_console:i parameter to disable
15
- // ConEmu's hooks and also set ConEmuANSI to OFF so Docker doesn't do anything drastic.
16
- const env = Object.assign({}, process.env);
17
- const extraParameters = [];
18
- if (env.ConEmuANSI === "ON") {
19
- env.ConEmuANSI = "OFF";
20
- extraParameters.push("-cur_console:i");
21
- }
22
-
23
- const argv = yargs
24
- .config()
25
- .help()
26
- .option("fromPrefix", {
27
- describe: "The prefix of the image that will be given a new tag",
28
- type: "string",
29
- example: "registry.gitlab.com/magda-data/",
30
- default: ""
31
- })
32
- .option("fromName", {
33
- describe:
34
- "The package name that used to generate the fromTag. Used to optionally override the docker nanme config in package.json during the auto tagging.",
35
- type: "string",
36
- example: "data61/magda-ckan-connector",
37
- default: ""
38
- })
39
- .option("fromVersion", {
40
- describe:
41
- "The version of the existing image that will be given a new tag",
42
- type: "string",
43
- example: "0.0.38-RC1",
44
- default: getVersions()[0]
45
- })
46
- .option("toPrefix", {
47
- describe: "The prefix for the tag to push to",
48
- type: "string",
49
- example: "registry.gitlab.com/magda-data/",
50
- default: ""
51
- })
52
- .option("toName", {
53
- describe:
54
- "The package name that used to generate the toTag. Used to optionally override the docker nanme config in package.json during the auto tagging.",
55
- type: "string",
56
- example: "data61/magda-ckan-connector",
57
- default: ""
58
- })
59
- .option("copyFromRegistry", {
60
- describe: `When \`copyFromRegistry\`=true, [regctl](https://github.com/regclient/regclient) will be used.
61
- The image will be copied directly from remote registry to the destination registry rather than from a local image.
62
- This allows copying multi-arch image from one registry to another registry.`,
63
- type: "boolean",
64
- default: false
65
- })
66
- .option("toVersion", {
67
- describe: "The version for the tag to push to",
68
- type: "string",
69
- example: "0.0.39",
70
- default: getVersions()[0]
71
- }).argv;
72
-
73
- const fromTag =
74
- argv.fromPrefix + getName(argv.fromName) + ":" + argv.fromVersion;
75
-
76
- const toTag = argv.toPrefix + getName(argv.toName) + ":" + argv.toVersion;
77
-
78
- if (argv.copyFromRegistry) {
79
- console.log(`Copying from \`${fromTag}\` to \`${toTag}\`...`);
80
- const copyProcess = childProcess.spawn(
81
- "regctl",
82
- ["image", "copy", fromTag, toTag],
83
- {
84
- stdio: ["pipe", "pipe", "pipe"],
85
- env: env
86
- }
87
- );
88
- copyProcess.stderr.pipe(process.stderr);
89
- copyProcess.stdout.pipe(process.stdout);
90
- copyProcess.on("close", (code) => {
91
- process.exit(code);
92
- });
93
- } else {
94
- const pullProcess = childProcess.spawnSync("docker", ["pull", fromTag], {
95
- stdio: ["pipe", "inherit", "inherit"],
96
- env: env
97
- });
98
-
99
- if (pullProcess.status !== 0) {
100
- process.exit(pullProcess.status);
101
- }
102
-
103
- const tagProcess = childProcess.spawnSync(
104
- "docker",
105
- ["tag", fromTag, toTag],
106
- {
107
- stdio: ["pipe", "inherit", "inherit"],
108
- env: env
109
- }
110
- );
111
-
112
- if (tagProcess.status !== 0) {
113
- process.exit(tagProcess.status);
114
- }
115
-
116
- const pushProcess = childProcess.spawnSync("docker", ["push", toTag], {
117
- stdio: ["pipe", "inherit", "inherit"],
118
- env: env
119
- });
120
-
121
- process.exit(pushProcess.status);
122
- }