@cyclonedx/cdxgen 9.9.4 → 9.9.6

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
@@ -86,10 +86,10 @@ For go, `go mod why` command is used to identify required packages. For php, com
86
86
  ## Installing
87
87
 
88
88
  ```shell
89
- sudo npm install -g @cyclonedx/cdxgen
89
+ npm install -g @cyclonedx/cdxgen
90
90
 
91
91
  # For CycloneDX 1.4 compatibility use version 8.6.0 or pass the argument `--spec-version 1.4`
92
- sudo npm install -g @cyclonedx/cdxgen@8.6.0
92
+ npm install -g @cyclonedx/cdxgen@8.6.0
93
93
  ```
94
94
 
95
95
  If you are a [Homebrew](https://brew.sh/) user, you can also install [cdxgen](https://formulae.brew.sh/formula/cdxgen) via:
@@ -327,7 +327,7 @@ This would create a bom.json.map file with the jar - class name mapping. Refer t
327
327
 
328
328
  ## Resolving licenses
329
329
 
330
- cdxgen can automatically query public registries such as maven, npm, or nuget to resolve the package licenses. This is a time-consuming operation and is disabled by default. To enable, set the environment variable `FETCH_LICENSE` to `true`, as shown.
330
+ cdxgen can automatically query public registries such as maven, npm, or nuget to resolve the package licenses. This is a time-consuming operation and is disabled by default. To enable, set the environment variable `FETCH_LICENSE` to `true`, as shown. Ensure that `GITHUB_TOKEN` is set or provided by [built-in GITHUB_TOKEN in GitHub Actions](https://docs.github.com/en/rest/overview/rate-limits-for-the-rest-api#primary-rate-limit-for-github_token-in-github-actions), otherwise rate limiting might prevent license resolving.
331
331
 
332
332
  ```bash
333
333
  export FETCH_LICENSE=true
@@ -445,7 +445,7 @@ This feature is powered by osquery, which is [installed](https://github.com/cycl
445
445
 
446
446
  See [evinse mode](./ADVANCED.md) in the advanced documentation.
447
447
 
448
- ## BoM signing
448
+ ## BOM signing
449
449
 
450
450
  cdxgen can sign the generated BOM json file to increase authenticity and non-repudiation capabilities. To enable this, set the following environment variables.
451
451
 
package/analyzer.js CHANGED
@@ -108,7 +108,7 @@ const setFileRef = (allImports, src, file, pathnode, specifiers = []) => {
108
108
  }
109
109
  const fileRelativeLoc = relative(src, file);
110
110
  // remove unexpected extension imports
111
- if (/\.(svg|png|jpg|d\.ts)/.test(pathway)) {
111
+ if (/\.(svg|png|jpg|json|d\.ts)/.test(pathway)) {
112
112
  return;
113
113
  }
114
114
  const importedModules = specifiers
@@ -141,6 +141,25 @@
141
141
  "pkg:cargo/nickel",
142
142
  "pkg:cargo/yew",
143
143
  "pkg:cargo/azul",
144
- "pkg:cargo/conrod"
144
+ "pkg:cargo/conrod",
145
+ "pkg:generic/Aws",
146
+ "pkg:generic/Azure",
147
+ "pkg:generic/google",
148
+ "pkg:generic/CivetServer",
149
+ "pkg:generic/civetweb",
150
+ "pkg:generic/cpprest",
151
+ "pkg:generic/QCoreApplication",
152
+ "pkg:generic/drogon",
153
+ "pkg:generic/wfrest",
154
+ "pkg:generic/http",
155
+ "pkg:generic/fio",
156
+ "pkg:generic/onion",
157
+ "pkg:generic/lwan",
158
+ "pkg:generic/oatpp",
159
+ "pkg:generic/QDjango",
160
+ "pkg:generic/userver",
161
+ "pkg:generic/Wt/",
162
+ "pkg:generic/klone",
163
+ "pkg:generic/kcgi"
145
164
  ]
146
165
  }
@@ -1,31 +1,82 @@
1
1
  [
2
- { "license": "Apache-2.0", "group": "cloud.google.com", "name": "go" },
3
- { "license": "Apache-2.0", "group": "cloud.google.com/go", "name": "*" },
4
- { "license": "Apache-2.0", "group": "cuelang.org", "name": "go" },
5
- { "license": "MIT", "group": "pack.ag", "name": "amqp" },
6
- { "license": "Apache-2.0", "group": "google.golang.org", "name": "*" },
7
- { "license": "BSD-3-Clause", "group": "golang.org/x", "name": "*" },
8
2
  {
9
- "license": "BSD-3-Clause",
10
- "group": "dmitri.shuralyov.com/gpu",
11
- "name": "*"
3
+ "packageNamespace": "*",
4
+ "knownLicenses": [{ "license": "MIT", "urlIncludes": "mit-license" }]
12
5
  },
13
- { "license": "Apache-2.0", "group": "contrib.go.opencensus.io", "name": "*" },
14
- { "license": "Apache-2.0", "group": "git.apache.org", "name": "*" },
15
- { "license": "Apache-2.0", "group": ".", "name": "go.opencensus.io" },
16
- { "license": "MIT", "group": "sigs.k8s.io", "name": "*" },
17
- { "license": "BSD-3-Clause", "group": "rsc.io", "name": "*" },
18
- { "license": "Apache-2.0", "group": "openpitrix.io", "name": "*" },
19
- { "license": "BSD-3-Clause", "group": "modernc.org", "name": "*" },
20
- { "license": "Apache-2.0", "group": "kubesphere.io", "name": "*" },
21
- { "license": "Apache-2.0", "group": "k8s.io", "name": "*" },
22
- { "license": "Apache-2.0", "group": "istio.io", "name": "*" },
23
- { "license": "MIT", "group": "honnef.co/go", "name": "*" },
24
- { "license": "Apache-2.0", "group": ".", "name": "gotest.tools" },
25
- { "license": "Apache-2.0", "group": "gopkg.in", "name": "*" },
26
- { "license": "Apache-2.0", "group": "code.cloudfoundry.org", "name": "*" },
27
- { "license": "BSD-3-Clause", "group": "gonum.org/v1", "name": "*" },
28
- { "license": "Apache-2.0", "group": "gomodules.xyz/jsonpatch", "name": "*" },
29
- { "license": "MIT", "group": "go.uber.org", "name": "*" },
30
- { "license": "MIT", "group": "go.etcd.io", "name": "*" }
6
+ {
7
+ "packageNamespace": "pkg:golang/",
8
+ "knownLicenses": [
9
+ { "license": "Apache-2.0", "group": "cloud.google.com", "name": "go" },
10
+ { "license": "Apache-2.0", "group": "cloud.google.com/go", "name": "*" },
11
+ { "license": "Apache-2.0", "group": "cuelang.org", "name": "go" },
12
+ { "license": "MIT", "group": "pack.ag", "name": "amqp" },
13
+ { "license": "Apache-2.0", "group": "google.golang.org", "name": "*" },
14
+ { "license": "BSD-3-Clause", "group": "golang.org/x", "name": "*" },
15
+ {
16
+ "license": "BSD-3-Clause",
17
+ "group": "dmitri.shuralyov.com/gpu",
18
+ "name": "*"
19
+ },
20
+ {
21
+ "license": "Apache-2.0",
22
+ "group": "contrib.go.opencensus.io",
23
+ "name": "*"
24
+ },
25
+ { "license": "Apache-2.0", "group": "git.apache.org", "name": "*" },
26
+ { "license": "Apache-2.0", "group": ".", "name": "go.opencensus.io" },
27
+ { "license": "MIT", "group": "sigs.k8s.io", "name": "*" },
28
+ { "license": "BSD-3-Clause", "group": "rsc.io", "name": "*" },
29
+ { "license": "Apache-2.0", "group": "openpitrix.io", "name": "*" },
30
+ { "license": "BSD-3-Clause", "group": "modernc.org", "name": "*" },
31
+ { "license": "Apache-2.0", "group": "kubesphere.io", "name": "*" },
32
+ { "license": "Apache-2.0", "group": "k8s.io", "name": "*" },
33
+ { "license": "Apache-2.0", "group": "istio.io", "name": "*" },
34
+ { "license": "MIT", "group": "honnef.co/go", "name": "*" },
35
+ { "license": "Apache-2.0", "group": ".", "name": "gotest.tools" },
36
+ { "license": "Apache-2.0", "group": "gopkg.in", "name": "*" },
37
+ {
38
+ "license": "Apache-2.0",
39
+ "group": "code.cloudfoundry.org",
40
+ "name": "*"
41
+ },
42
+ { "license": "BSD-3-Clause", "group": "gonum.org/v1", "name": "*" },
43
+ {
44
+ "license": "Apache-2.0",
45
+ "group": "gomodules.xyz/jsonpatch",
46
+ "name": "*"
47
+ },
48
+ { "license": "MIT", "group": "go.uber.org", "name": "*" },
49
+ { "license": "MIT", "group": "go.etcd.io", "name": "*" }
50
+ ]
51
+ },
52
+ {
53
+ "packageNamespace": "pkg:nuget/",
54
+ "knownLicenses": [
55
+ {
56
+ "license": "MIT",
57
+ "urlIncludes": "//github.com/dotnet/standard/",
58
+ "licenseEvidence": "https://github.com/dotnet/standard/blob/release/3.0/LICENSE.TXT"
59
+ },
60
+ {
61
+ "license": "MIT",
62
+ "urlIncludes": "//github.com/dotnet/corefx/",
63
+ "licenseEvidence": "https://github.com/dotnet/corefx/blob/release/2.0.0/LICENSE.TXT"
64
+ },
65
+ {
66
+ "license": "MIT",
67
+ "urlIncludes": "//github.com/dotnet/core-setup/",
68
+ "licenseEvidence": "https://github.com/dotnet/core-setup/blob/release/2.0.0/LICENSE.TXT"
69
+ },
70
+ {
71
+ "licenseName": ".NET Library License",
72
+ "urlEndswith": "?LinkId=329770",
73
+ "licenseEvidence": "https://go.microsoft.com/fwlink/?LinkId=329770"
74
+ },
75
+ {
76
+ "licenseName": ".NET Library License",
77
+ "urlEndswith": "dotnet_library_license.htm",
78
+ "licenseEvidence": "https://dotnet.microsoft.com/en-us/dotnet_library_license.htm"
79
+ }
80
+ ]
81
+ }
31
82
  ]
package/docker.js CHANGED
@@ -30,9 +30,25 @@ let dockerConn = undefined;
30
30
  let isPodman = false;
31
31
  let isPodmanRootless = true;
32
32
  let isDockerRootless = false;
33
+ // https://github.com/containerd/containerd
34
+ let isContainerd = !!process.env.CONTAINERD_ADDRESS;
33
35
  const WIN_LOCAL_TLS = "http://localhost:2375";
34
36
  let isWinLocalTLS = false;
35
37
 
38
+ if (
39
+ !process.env.DOCKER_HOST &&
40
+ (process.env.CONTAINERD_ADDRESS ||
41
+ (process.env.XDG_RUNTIME_DIR &&
42
+ existsSync(
43
+ join(process.env.XDG_RUNTIME_DIR, "containerd-rootless", "api.sock")
44
+ )))
45
+ ) {
46
+ isContainerd = true;
47
+ }
48
+
49
+ // Cache the registry auth keys
50
+ const registry_auth_keys = {};
51
+
36
52
  /**
37
53
  * Method to get all dirs matching a name
38
54
  *
@@ -94,7 +110,17 @@ export const getOnlyDirs = (srcpath, dirName) => {
94
110
  ].filter((d) => d.endsWith(dirName));
95
111
  };
96
112
 
97
- const getDefaultOptions = () => {
113
+ const getDefaultOptions = (forRegistry) => {
114
+ let authTokenSet = false;
115
+ if (!forRegistry && process.env.DOCKER_SERVER_ADDRESS) {
116
+ forRegistry = process.env.DOCKER_SERVER_ADDRESS;
117
+ }
118
+ if (forRegistry) {
119
+ forRegistry = forRegistry.replace("http://", "").replace("https://", "");
120
+ if (forRegistry.includes("/")) {
121
+ forRegistry = forRegistry.split("/")[0];
122
+ }
123
+ }
98
124
  const opts = {
99
125
  enableUnixSockets: true,
100
126
  throwHttpErrors: true,
@@ -102,6 +128,97 @@ const getDefaultOptions = () => {
102
128
  hooks: { beforeError: [] },
103
129
  mutableDefaults: true
104
130
  };
131
+ const DOCKER_CONFIG = process.env.DOCKER_CONFIG || join(homedir(), ".docker");
132
+ // Support for private registry
133
+ if (process.env.DOCKER_AUTH_CONFIG) {
134
+ opts.headers = {
135
+ "X-Registry-Auth": process.env.DOCKER_AUTH_CONFIG
136
+ };
137
+ authTokenSet = true;
138
+ }
139
+ if (
140
+ !authTokenSet &&
141
+ process.env.DOCKER_USER &&
142
+ process.env.DOCKER_PASSWORD &&
143
+ process.env.DOCKER_EMAIL &&
144
+ forRegistry
145
+ ) {
146
+ const authPayload = {
147
+ username: process.env.DOCKER_USER,
148
+ email: process.env.DOCKER_EMAIL,
149
+ serveraddress: forRegistry
150
+ };
151
+ if (process.env.DOCKER_USER === "<token>") {
152
+ authPayload.IdentityToken = process.env.DOCKER_PASSWORD;
153
+ } else {
154
+ authPayload.password = process.env.DOCKER_PASSWORD;
155
+ }
156
+ opts.headers = {
157
+ "X-Registry-Auth": Buffer.from(JSON.stringify(authPayload)).toString(
158
+ "base64"
159
+ )
160
+ };
161
+ }
162
+ if (!authTokenSet && existsSync(join(DOCKER_CONFIG, "config.json"))) {
163
+ const configData = readFileSync(
164
+ join(DOCKER_CONFIG, "config.json"),
165
+ "utf-8"
166
+ );
167
+ if (configData) {
168
+ try {
169
+ const configJson = JSON.parse(configData);
170
+ if (configJson.auths) {
171
+ // Check if there are hardcoded tokens
172
+ for (const serverAddress of Object.keys(configJson.auths)) {
173
+ if (forRegistry && !serverAddress.includes(forRegistry)) {
174
+ continue;
175
+ }
176
+ if (configJson.auths[serverAddress].auth) {
177
+ opts.headers = {
178
+ "X-Registry-Auth": configJson.auths[serverAddress].auth
179
+ };
180
+ authTokenSet = true;
181
+ break;
182
+ } else if (configJson.credsStore) {
183
+ const helperAuthToken = getCredsFromHelper(
184
+ configJson.credsStore,
185
+ serverAddress
186
+ );
187
+ if (helperAuthToken) {
188
+ opts.headers = {
189
+ "X-Registry-Auth": helperAuthToken
190
+ };
191
+ authTokenSet = true;
192
+ break;
193
+ }
194
+ }
195
+ }
196
+ } else if (configJson.credHelpers) {
197
+ // Support for credential helpers
198
+ for (const serverAddress of Object.keys(configJson.credHelpers)) {
199
+ if (forRegistry && !serverAddress.includes(forRegistry)) {
200
+ continue;
201
+ }
202
+ if (configJson.credHelpers[serverAddress]) {
203
+ const helperAuthToken = getCredsFromHelper(
204
+ configJson.credHelpers[serverAddress],
205
+ serverAddress
206
+ );
207
+ if (helperAuthToken) {
208
+ opts.headers = {
209
+ "X-Registry-Auth": helperAuthToken
210
+ };
211
+ authTokenSet = true;
212
+ break;
213
+ }
214
+ }
215
+ }
216
+ }
217
+ } catch (err) {
218
+ // pass
219
+ }
220
+ }
221
+ }
105
222
  const userInfo = _userInfo();
106
223
  opts.podmanPrefixUrl = isWin ? "" : `http://unix:/run/podman/podman.sock:`;
107
224
  opts.podmanRootlessPrefixUrl = isWin
@@ -148,22 +265,34 @@ const getDefaultOptions = () => {
148
265
  ),
149
266
  key: readFileSync(join(process.env.DOCKER_CERT_PATH, "key.pem"), "utf8")
150
267
  };
268
+ // Disable tls on empty values
269
+ // From the docker docs: Setting the DOCKER_TLS_VERIFY environment variable to any value other than the empty string is equivalent to setting the --tlsverify flag
270
+ if (
271
+ process.env.DOCKER_TLS_VERIFY &&
272
+ process.env.DOCKER_TLS_VERIFY === ""
273
+ ) {
274
+ opts.https.rejectUnauthorized = false;
275
+ console.log("TLS Verification disabled for", hostStr);
276
+ }
151
277
  }
152
278
  }
153
279
 
154
280
  return opts;
155
281
  };
156
282
 
157
- export const getConnection = async (options) => {
158
- if (!dockerConn) {
159
- const defaultOptions = getDefaultOptions();
283
+ export const getConnection = async (options, forRegistry) => {
284
+ if (isContainerd) {
285
+ return undefined;
286
+ } else if (!dockerConn) {
287
+ const defaultOptions = getDefaultOptions(forRegistry);
160
288
  const opts = Object.assign(
161
289
  {},
162
290
  {
163
291
  enableUnixSockets: defaultOptions.enableUnixSockets,
164
292
  throwHttpErrors: defaultOptions.throwHttpErrors,
165
293
  method: defaultOptions.method,
166
- prefixUrl: defaultOptions.prefixUrl
294
+ prefixUrl: defaultOptions.prefixUrl,
295
+ headers: defaultOptions.headers
167
296
  },
168
297
  options
169
298
  );
@@ -247,8 +376,8 @@ export const getConnection = async (options) => {
247
376
  return dockerConn;
248
377
  };
249
378
 
250
- export const makeRequest = async (path, method = "GET") => {
251
- const client = await getConnection();
379
+ export const makeRequest = async (path, method = "GET", forRegistry) => {
380
+ const client = await getConnection({}, forRegistry);
252
381
  if (!client) {
253
382
  return undefined;
254
383
  }
@@ -258,14 +387,15 @@ export const makeRequest = async (path, method = "GET") => {
258
387
  enableUnixSockets: true,
259
388
  method
260
389
  };
261
- const defaultOptions = getDefaultOptions();
390
+ const defaultOptions = getDefaultOptions(forRegistry);
262
391
  const opts = Object.assign(
263
392
  {},
264
393
  {
265
394
  enableUnixSockets: defaultOptions.enableUnixSockets,
266
395
  throwHttpErrors: defaultOptions.throwHttpErrors,
267
396
  method: defaultOptions.method,
268
- prefixUrl: defaultOptions.prefixUrl
397
+ prefixUrl: defaultOptions.prefixUrl,
398
+ headers: defaultOptions.headers
269
399
  },
270
400
  extraOptions
271
401
  );
@@ -286,11 +416,16 @@ export const parseImageName = (fullImageName) => {
286
416
  repo: "",
287
417
  tag: "",
288
418
  digest: "",
289
- platform: ""
419
+ platform: "",
420
+ group: "",
421
+ name: ""
290
422
  };
291
423
  if (!fullImageName) {
292
424
  return nameObj;
293
425
  }
426
+ // ensure it's lowercased
427
+ fullImageName = fullImageName.toLowerCase();
428
+
294
429
  // Extract registry name
295
430
  if (
296
431
  fullImageName.includes("/") &&
@@ -307,6 +442,7 @@ export const parseImageName = (fullImageName) => {
307
442
  fullImageName = fullImageName.replace(tmpA[0] + "/", "");
308
443
  }
309
444
  }
445
+
310
446
  // Extract digest name
311
447
  if (fullImageName.includes("@sha256:")) {
312
448
  const tmpA = fullImageName.split("@sha256:");
@@ -315,6 +451,7 @@ export const parseImageName = (fullImageName) => {
315
451
  fullImageName = fullImageName.replace("@sha256:" + nameObj.digest, "");
316
452
  }
317
453
  }
454
+
318
455
  // Extract tag name
319
456
  if (fullImageName.includes(":")) {
320
457
  const tmpA = fullImageName.split(":");
@@ -323,28 +460,61 @@ export const parseImageName = (fullImageName) => {
323
460
  fullImageName = fullImageName.replace(":" + nameObj.tag, "");
324
461
  }
325
462
  }
326
- if (fullImageName && fullImageName.startsWith("library/")) {
327
- fullImageName = fullImageName.replace("library/", "");
328
- }
463
+
329
464
  // The left over string is the repo name
330
465
  nameObj.repo = fullImageName;
466
+ nameObj.name = fullImageName;
467
+
468
+ // extract group name
469
+ if (fullImageName.includes("/")) {
470
+ const tmpA = fullImageName.split("/");
471
+ if (tmpA.length > 1) {
472
+ nameObj.name = tmpA[tmpA.length - 1];
473
+ nameObj.group = fullImageName.replace("/" + tmpA[tmpA.length - 1], "");
474
+ }
475
+ }
476
+
331
477
  return nameObj;
332
478
  };
333
479
 
480
+ /**
481
+ * Prefer cli on windows or when using tcp/ssh based host.
482
+ *
483
+ * @returns boolean true if we should use the cli. false otherwise
484
+ */
485
+ const needsCliFallback = () => {
486
+ return (
487
+ isWin ||
488
+ (process.env.DOCKER_HOST &&
489
+ (process.env.DOCKER_HOST.startsWith("tcp://") ||
490
+ process.env.DOCKER_HOST.startsWith("ssh://")))
491
+ );
492
+ };
493
+
334
494
  /**
335
495
  * Method to get image to the local registry by pulling from the remote if required
336
496
  */
337
497
  export const getImage = async (fullImageName) => {
338
498
  let localData = undefined;
339
499
  let pullData = undefined;
340
- const { repo, tag, digest } = parseImageName(fullImageName);
341
- let repoWithTag = `${repo}:${tag !== "" ? tag : ":latest"}`;
500
+ const { registry, repo, tag, digest } = parseImageName(fullImageName);
501
+ let repoWithTag =
502
+ registry && registry !== "docker.io"
503
+ ? fullImageName
504
+ : `${repo}:${tag !== "" ? tag : ":latest"}`;
342
505
  // Fetch only the latest tag if none is specified
343
506
  if (tag === "" && digest === "") {
344
507
  fullImageName = fullImageName + ":latest";
345
508
  }
346
- if (isWin) {
347
- let result = spawnSync("docker", ["pull", fullImageName], {
509
+ if (isContainerd) {
510
+ console.log(
511
+ "containerd/nerdctl is currently unsupported. Export the image manually and run cdxgen against the tar image."
512
+ );
513
+ return undefined;
514
+ }
515
+ if (needsCliFallback()) {
516
+ const dockerCmd = process.env.DOCKER_CMD || "docker";
517
+ let result = spawnSync(dockerCmd, ["pull", fullImageName], {
348
518
  encoding: "utf-8"
349
519
  });
350
520
  if (result.status !== 0 || result.error) {
@@ -355,12 +525,16 @@ export const getImage = async (fullImageName) => {
355
525
  console.log(
356
526
  "Ensure Docker for Desktop is running as an administrator with 'Exposing daemon on TCP without TLS' setting turned on."
357
527
  );
528
+ } else if (result.stderr && result.stderr.includes("not found")) {
529
+ console.log(
530
+ "Set the environment variable DOCKER_CMD to use an alternative command such as nerdctl or podman."
531
+ );
358
532
  } else {
359
533
  console.log(result.stderr);
360
534
  }
361
535
  return localData;
362
536
  } else {
363
- result = spawnSync("docker", ["inspect", fullImageName], {
537
+ result = spawnSync(dockerCmd, ["inspect", fullImageName], {
364
538
  encoding: "utf-8"
365
539
  });
366
540
  if (result.status !== 0 || result.error) {
@@ -385,7 +559,11 @@ export const getImage = async (fullImageName) => {
385
559
  }
386
560
  }
387
561
  try {
388
- localData = await makeRequest(`images/${repoWithTag}/json`);
562
+ localData = await makeRequest(
563
+ `images/${repoWithTag}/json`,
564
+ "GET",
565
+ registry
566
+ );
389
567
  if (localData) {
390
568
  return localData;
391
569
  }
@@ -393,10 +571,14 @@ export const getImage = async (fullImageName) => {
393
571
  // ignore
394
572
  }
395
573
  try {
396
- localData = await makeRequest(`images/${repo}/json`);
574
+ localData = await makeRequest(`images/${repo}/json`, "GET", registry);
397
575
  } catch (err) {
398
576
  try {
399
- localData = await makeRequest(`images/${fullImageName}/json`);
577
+ localData = await makeRequest(
578
+ `images/${fullImageName}/json`,
579
+ "GET",
580
+ registry
581
+ );
400
582
  if (localData) {
401
583
  return localData;
402
584
  }
@@ -412,7 +594,8 @@ export const getImage = async (fullImageName) => {
412
594
  try {
413
595
  pullData = await makeRequest(
414
596
  `images/create?fromImage=${fullImageName}`,
415
- "POST"
597
+ "POST",
598
+ registry
416
599
  );
417
600
  if (
418
601
  pullData &&
@@ -434,7 +617,8 @@ export const getImage = async (fullImageName) => {
434
617
  }
435
618
  pullData = await makeRequest(
436
619
  `images/create?fromImage=${repoWithTag}`,
437
- "POST"
620
+ "POST",
621
+ registry
438
622
  );
439
623
  } catch (err) {
440
624
  // continue regardless of error
@@ -444,7 +628,11 @@ export const getImage = async (fullImageName) => {
444
628
  if (DEBUG_MODE) {
445
629
  console.log(`Trying with ${repoWithTag}`);
446
630
  }
447
- localData = await makeRequest(`images/${repoWithTag}/json`);
631
+ localData = await makeRequest(
632
+ `images/${repoWithTag}/json`,
633
+ "GET",
634
+ registry
635
+ );
448
636
  if (localData) {
449
637
  return localData;
450
638
  }
@@ -453,7 +641,7 @@ export const getImage = async (fullImageName) => {
453
641
  if (DEBUG_MODE) {
454
642
  console.log(`Trying with ${repo}`);
455
643
  }
456
- localData = await makeRequest(`images/${repo}/json`);
644
+ localData = await makeRequest(`images/${repo}/json`, "GET", registry);
457
645
  if (localData) {
458
646
  return localData;
459
647
  }
@@ -464,7 +652,11 @@ export const getImage = async (fullImageName) => {
464
652
  if (DEBUG_MODE) {
465
653
  console.log(`Trying with ${fullImageName}`);
466
654
  }
467
- localData = await makeRequest(`images/${fullImageName}/json`);
655
+ localData = await makeRequest(
656
+ `images/${fullImageName}/json`,
657
+ "GET",
658
+ registry
659
+ );
468
660
  } catch (err) {
469
661
  // continue regardless of error
470
662
  }
@@ -684,7 +876,7 @@ export const exportImage = async (fullImageName) => {
684
876
  if (!localData) {
685
877
  return undefined;
686
878
  }
687
- const { tag, digest } = parseImageName(fullImageName);
879
+ const { registry, tag, digest } = parseImageName(fullImageName);
688
880
  // Fetch only the latest tag if none is specified
689
881
  if (tag === "" && digest === "") {
690
882
  fullImageName = fullImageName + ":latest";
@@ -695,7 +887,7 @@ export const exportImage = async (fullImageName) => {
695
887
  // Windows containers use index.json
696
888
  const manifestIndexFile = join(tempDir, "index.json");
697
889
  // On Windows, fallback to invoking cli
698
- if (isWin) {
890
+ if (needsCliFallback()) {
699
891
  const imageTarFile = join(tempDir, "image.tar");
700
892
  console.log(
701
893
  `About to export image ${fullImageName} to ${imageTarFile} using docker cli`
@@ -722,10 +914,16 @@ export const exportImage = async (fullImageName) => {
722
914
  }
723
915
  }
724
916
  } else {
725
- const client = await getConnection();
917
+ const client = await getConnection({}, registry);
726
918
  try {
727
919
  if (DEBUG_MODE) {
728
- console.log(`About to export image ${fullImageName} to ${tempDir}`);
920
+ if (registry && registry.trim().length) {
921
+ console.log(
922
+ `About to export image ${fullImageName} from ${registry} to ${tempDir}`
923
+ );
924
+ } else {
925
+ console.log(`About to export image ${fullImageName} to ${tempDir}`);
926
+ }
729
927
  }
730
928
  await stream.pipeline(
731
929
  client.stream(`images/${fullImageName}/get`),
@@ -896,3 +1094,46 @@ export const removeImage = async (fullImageName, force = false) => {
896
1094
  );
897
1095
  return removeData;
898
1096
  };
1097
+
1098
+ export const getCredsFromHelper = (exeSuffix, serverAddress) => {
1099
+ if (registry_auth_keys[serverAddress]) {
1100
+ return registry_auth_keys[serverAddress];
1101
+ }
1102
+ let credHelperExe = `docker-credential-${exeSuffix}`;
1103
+ if (isWin) {
1104
+ credHelperExe = credHelperExe + ".exe";
1105
+ }
1106
+ const result = spawnSync(credHelperExe, ["get"], {
1107
+ input: serverAddress,
1108
+ encoding: "utf-8"
1109
+ });
1110
+ if (result.status !== 0 || result.error) {
1111
+ console.log(result.stdout, result.stderr);
1112
+ } else if (result.stdout) {
1113
+ const cmdOutput = Buffer.from(result.stdout).toString();
1114
+ try {
1115
+ const authPayload = JSON.parse(cmdOutput);
1116
+ const fixedAuthPayload = {
1117
+ username:
1118
+ authPayload.username ||
1119
+ authPayload.Username ||
1120
+ process.env.DOCKER_USER,
1121
+ password:
1122
+ authPayload.password ||
1123
+ authPayload.Secret ||
1124
+ process.env.DOCKER_PASSWORD,
1125
+ email:
1126
+ authPayload.email || authPayload.username || process.env.DOCKER_USER,
1127
+ serveraddress: serverAddress
1128
+ };
1129
+ const authKey = Buffer.from(JSON.stringify(fixedAuthPayload)).toString(
1130
+ "base64"
1131
+ );
1132
+ registry_auth_keys[serverAddress] = authKey;
1133
+ return authKey;
1134
+ } catch (err) {
1135
+ return undefined;
1136
+ }
1137
+ }
1138
+ return undefined;
1139
+ };