@cyclonedx/cdxgen 9.8.10 → 9.9.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 CHANGED
@@ -12,45 +12,45 @@ CycloneDX 1.5 specification is new and unsupported by many downstream tools. Use
12
12
 
13
13
  ## Why cdxgen?
14
14
 
15
- A typical application might have several repos, components, and libraries. Traditional techniques to generate a single SBOM per language or package manifest do not work in enterprise environments. So we built cdxgen - the universal polyglot SBOM generator!
15
+ Most SBOM tools are like barcode scanners. They can scan a few package manifest and create a list of components only based on these files without any deep inspection. Further, a typical application might have several repos, components, and libraries. Traditional techniques to generate a SBOM per language or package manifest either do not work in enterprise environments or doesn't provide the confidence required for both compliance and automated analysis. So we built cdxgen - the universal polyglot SBOM generator that is both precise and comprehensive!
16
16
 
17
17
  <img src="./docs/why-cdxgen.jpg" alt="why cdxgen" width="256">
18
18
 
19
19
  ## Supported languages and package format
20
20
 
21
- | Language/Platform | Package format | Transitive dependencies |
22
- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
23
- | node.js | npm-shrinkwrap.json, package-lock.json, pnpm-lock.yaml, yarn.lock, rush.js, bower.json, .min.js | Yes except .min.js |
24
- | java | maven (pom.xml [1]), gradle (build.gradle, .kts), scala (sbt), bazel | Yes unless pom.xml is manually parsed due to unavailability of maven or errors |
25
- | php | composer.lock | Yes |
26
- | python | pyproject.toml, setup.py, requirements.txt [2], Pipfile.lock, poetry.lock, pdm.lock, bdist_wheel, .whl, .egg-info | Yes using the automatic pip install/freeze. When disabled, only with Pipfile.lock and poetry.lock |
27
- | go | binary, go.mod, go.sum, Gopkg.lock | Yes except binary |
28
- | ruby | Gemfile.lock, gemspec | Only for Gemfile.lock |
29
- | rust | binary, Cargo.toml, Cargo.lock | Only for Cargo.lock |
30
- | .Net | .csproj, packages.config, project.assets.json [3], packages.lock.json, .nupkg, paket.lock | Only for project.assets.json, packages.lock.json, paket.lock |
31
- | dart | pubspec.lock, pubspec.yaml | Only for pubspec.lock |
32
- | haskell | cabal.project.freeze | Yes |
33
- | elixir | mix.lock | Yes |
34
- | c/c++/Objective C/c++11 | conan.lock, conanfile.txt, \*.cmake, CMakeLists.txt, meson.build, codebase without package managers! | Yes only for conan.lock. Best effort basis for cmake without version numbers. |
35
- | clojure | Clojure CLI (deps.edn), Leiningen (project.clj) | Yes unless the files are parsed manually due to lack of clojure cli or leiningen command |
36
- | swift | Package.resolved, Package.swift (swiftpm) | Yes |
37
- | docker / oci image | All supported languages. Linux OS packages with plugins [4] | Best effort based on lock files |
38
- | GitHub Actions | .github/workflows/\*.yml | N/A |
39
- | Linux | All supported languages. Linux OS packages with plugins [5] | Best effort based on lock files |
40
- | Windows | All supported languages. OS packages with best effort [5] | Best effort based on lock files |
41
- | Jenkins Plugins | .hpi files | |
42
- | Helm Charts | .yaml | N/A |
43
- | Skaffold | .yaml | N/A |
44
- | kustomization | .yaml | N/A |
45
- | Tekton tasks | .yaml | N/A |
46
- | Kubernetes | .yaml | N/A |
47
- | Maven Cache | $HOME/.m2/repository/\*\*/\*.jar | N/A |
48
- | SBT Cache | $HOME/.ivy2/cache/\*\*/\*.jar | N/A |
49
- | Gradle Cache | $HOME/caches/modules-2/files-2.1/\*\*/\*.jar | N/A |
50
- | Helm Index | $HOME/.cache/helm/repository/\*\*/\*.yaml | N/A |
51
- | Docker compose | docker-compose\*.yml. Images would also be scanned. | N/A |
52
- | Google CloudBuild configuration | cloudbuild.yaml | N/A |
53
- | OpenAPI | openapi\*.json, openapi\*.yaml | N/A |
21
+ | Language/Platform | Package format | Transitive dependencies | Evidence |
22
+ | ------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------- |
23
+ | node.js | npm-shrinkwrap.json, package-lock.json, pnpm-lock.yaml, yarn.lock, rush.js, bower.json, .min.js | Yes except .min.js | Yes |
24
+ | java | maven (pom.xml [1]), gradle (build.gradle, .kts), scala (sbt), bazel | Yes unless pom.xml is manually parsed due to unavailability of maven or errors | Yes |
25
+ | php | composer.lock | Yes | |
26
+ | python | pyproject.toml, setup.py, requirements.txt [2], Pipfile.lock, poetry.lock, pdm.lock, bdist_wheel, .whl, .egg-info | Yes using the automatic pip install/freeze. When disabled, only with Pipfile.lock and poetry.lock | Yes |
27
+ | go | binary, go.mod, go.sum, Gopkg.lock | Yes except binary | Yes |
28
+ | ruby | Gemfile.lock, gemspec | Only for Gemfile.lock | |
29
+ | rust | binary, Cargo.toml, Cargo.lock | Only for Cargo.lock | |
30
+ | .Net | .csproj, packages.config, project.assets.json [3], packages.lock.json, .nupkg, paket.lock | Only for project.assets.json, packages.lock.json, paket.lock | |
31
+ | dart | pubspec.lock, pubspec.yaml | Only for pubspec.lock | |
32
+ | haskell | cabal.project.freeze | Yes | |
33
+ | elixir | mix.lock | Yes | |
34
+ | c/c++/Objective C/c++11 | conan.lock, conanfile.txt, \*.cmake, CMakeLists.txt, meson.build, codebase without package managers! | Yes only for conan.lock. Best effort basis for cmake without version numbers. | Yes |
35
+ | clojure | Clojure CLI (deps.edn), Leiningen (project.clj) | Yes unless the files are parsed manually due to lack of clojure cli or leiningen command | |
36
+ | swift | Package.resolved, Package.swift (swiftpm) | Yes | |
37
+ | docker / oci image | All supported languages. Linux OS packages with plugins [4] | Best effort based on lock files | Yes |
38
+ | GitHub Actions | .github/workflows/\*.yml | N/A | Yes |
39
+ | Linux | All supported languages. Linux OS packages with plugins [5] | Best effort based on lock files | Yes |
40
+ | Windows | All supported languages. OS packages with best effort [5] | Best effort based on lock files | Yes |
41
+ | Jenkins Plugins | .hpi files | | Yes |
42
+ | Helm Charts | .yaml | N/A | |
43
+ | Skaffold | .yaml | N/A | |
44
+ | kustomization | .yaml | N/A | |
45
+ | Tekton tasks | .yaml | N/A | |
46
+ | Kubernetes | .yaml | N/A | |
47
+ | Maven Cache | $HOME/.m2/repository/\*\*/\*.jar | N/A | |
48
+ | SBT Cache | $HOME/.ivy2/cache/\*\*/\*.jar | N/A | |
49
+ | Gradle Cache | $HOME/caches/modules-2/files-2.1/\*\*/\*.jar | N/A | |
50
+ | Helm Index | $HOME/.cache/helm/repository/\*\*/\*.yaml | N/A | |
51
+ | Docker compose | docker-compose\*.yml. Images would also be scanned. | N/A | |
52
+ | Google CloudBuild configuration | cloudbuild.yaml | N/A | |
53
+ | OpenAPI | openapi\*.json, openapi\*.yaml | N/A | |
54
54
 
55
55
  NOTE:
56
56
 
@@ -181,6 +181,9 @@ Options:
181
181
  --only Include components only containining this word in
182
182
  purl. Useful to generate BOM with first party co
183
183
  mponents alone. Multiple values allowed. [array]
184
+ --author The person(s) who created the BOM. Set this value
185
+ if you're intending the modify the BOM and claim
186
+ authorship.[array] [default: "OWASP Foundation"]
184
187
  --auto-compositions Automatically set compositions when the BOM was f
185
188
  iltered. Defaults to true
186
189
  [boolean] [default: true]
@@ -338,7 +341,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
338
341
  - Gradle
339
342
  - Scala SBT
340
343
  - Python (requirements.txt, setup.py, pyproject.toml, poetry.lock)
341
- - csharp (projects.assets.json)
344
+ - .NET (project.assets.json, paket.lock)
342
345
  - Go (go.mod)
343
346
 
344
347
  ## Environment variables
@@ -367,6 +370,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
367
370
  | USE_GOSUM | Set to `true` or `1` to generate BOMs for golang projects using go.sum as the dependency source of truth, instead of go.mod |
368
371
  | CDXGEN_TIMEOUT_MS | Default timeout for known execution involving maven, gradle or sbt |
369
372
  | CDXGEN_SERVER_TIMEOUT_MS | Default timeout in server mode |
373
+ | CDXGEN_MAX_BUFFER | Max buffer for stdout and stderr. Defaults to 100MB |
370
374
  | CLJ_CMD | Set to override the clojure cli command |
371
375
  | LEIN_CMD | Set to override the leiningen command |
372
376
  | SBOM_SIGN_ALGORITHM | Signature algorithm. Some valid values are RS256, RS384, RS512, PS256, PS384, PS512, ES256 etc |
@@ -377,6 +381,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
377
381
  | CDX_MAVEN_INCLUDE_TEST_SCOPE | Whether test scoped dependencies should be included from Maven projects, Default: true |
378
382
  | ASTGEN_IGNORE_DIRS | Comma separated list of directories to ignore while analyzing using babel. The environment variable is also used by atom and astgen. |
379
383
  | ASTGEN_IGNORE_FILE_PATTERN | Ignore regex to use |
384
+ | PYPI_URL | Override pypi url. Default: https://pypi.org/pypi/ |
380
385
 
381
386
  ## Plugins
382
387
 
package/analyzer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { parse } from "@babel/parser";
2
2
  import traverse from "@babel/traverse";
3
3
  import { join } from "node:path";
4
- import { readdirSync, statSync, readFileSync } from "node:fs";
4
+ import { readdirSync, lstatSync, readFileSync } from "node:fs";
5
5
  import { basename, resolve, isAbsolute, relative } from "node:path";
6
6
 
7
7
  const IGNORE_DIRS = process.env.ASTGEN_IGNORE_DIRS
@@ -46,7 +46,11 @@ const getAllFiles = (dir, extn, files, result, regex) => {
46
46
  continue;
47
47
  }
48
48
  const file = join(dir, files[i]);
49
- if (statSync(file).isDirectory()) {
49
+ const fileStat = lstatSync(file);
50
+ if (fileStat.isSymbolicLink()) {
51
+ continue;
52
+ }
53
+ if (fileStat.isDirectory()) {
50
54
  // Ignore directories
51
55
  const dirName = basename(file);
52
56
  if (
package/bin/cdxgen.js CHANGED
@@ -143,6 +143,7 @@ const args = yargs(hideBin(process.argv))
143
143
  "Validate the generated SBOM using json schema. Defaults to true. Pass --no-validate to disable."
144
144
  })
145
145
  .option("evidence", {
146
+ hidden: true,
146
147
  type: "boolean",
147
148
  default: false,
148
149
  description: "Generate SBOM with evidence for supported languages. WIP"
@@ -165,8 +166,15 @@ const args = yargs(hideBin(process.argv))
165
166
  description:
166
167
  "Include components only containining this word in purl. Useful to generate BOM with first party components alone. Multiple values allowed."
167
168
  })
169
+ .option("author", {
170
+ description:
171
+ "The person(s) who created the BOM. Set this value if you're intending the modify the BOM and claim authorship.",
172
+ default: "OWASP Foundation"
173
+ })
174
+ .completion("completion", "Generate bash/zsh completion")
168
175
  .array("filter")
169
176
  .array("only")
177
+ .array("author")
170
178
  .option("auto-compositions", {
171
179
  type: "boolean",
172
180
  default: true,
@@ -213,11 +221,6 @@ if (!args.projectName) {
213
221
  }
214
222
  }
215
223
 
216
- // To help dependency track users, we downgrade the spec version to 1.4 automatically
217
- if (args.serverUrl || args.apiKey) {
218
- args.specVersion = 1.4;
219
- }
220
-
221
224
  // Support for obom aliases
222
225
  if (process.argv[1].includes("obom") && !args.type) {
223
226
  args.type = "os";
package/bin/evinse.js CHANGED
@@ -9,7 +9,12 @@ import { homedir, platform as _platform } from "node:os";
9
9
  import process from "node:process";
10
10
  import { analyzeProject, createEvinseFile, prepareDB } from "../evinser.js";
11
11
  import { validateBom } from "../validator.js";
12
- import { printCallStack, printOccurrences, printServices } from "../display.js";
12
+ import {
13
+ printCallStack,
14
+ printOccurrences,
15
+ printServices,
16
+ printReachables
17
+ } from "../display.js";
13
18
  import { findUpSync } from "find-up";
14
19
  import { load as _load } from "js-yaml";
15
20
 
@@ -65,7 +70,18 @@ const args = yargs(hideBin(process.argv))
65
70
  alias: "l",
66
71
  description: "Application language",
67
72
  default: "java",
68
- choices: ["java", "jar", "javascript", "python", "android", "cpp"]
73
+ choices: [
74
+ "java",
75
+ "jar",
76
+ "js",
77
+ "ts",
78
+ "javascript",
79
+ "py",
80
+ "python",
81
+ "android",
82
+ "c",
83
+ "cpp"
84
+ ]
69
85
  })
70
86
  .option("db-path", {
71
87
  description: `Atom slices DB path. Default ${ATOM_DB}`,
@@ -98,6 +114,12 @@ const args = yargs(hideBin(process.argv))
98
114
  default: false,
99
115
  type: "boolean"
100
116
  })
117
+ .option("with-reachables", {
118
+ description:
119
+ "Enable auto-tagged reachable slicing. Requires SBOM generated with --deep mode.",
120
+ default: false,
121
+ type: "boolean"
122
+ })
101
123
  .option("usages-slices-file", {
102
124
  description: "Use an existing usages slices file.",
103
125
  default: "usages.slices.json"
@@ -106,11 +128,27 @@ const args = yargs(hideBin(process.argv))
106
128
  description: "Use an existing data-flow slices file.",
107
129
  default: "data-flow.slices.json"
108
130
  })
131
+ .option("reachables-slices-file", {
132
+ description: "Use an existing reachables slices file.",
133
+ default: "reachables.slices.json"
134
+ })
109
135
  .option("print", {
110
136
  alias: "p",
111
137
  type: "boolean",
112
138
  description: "Print the evidences as table"
113
139
  })
140
+ .example([
141
+ [
142
+ "$0 -i bom.json -o bom.evinse.json -l java .",
143
+ "Generate a Java SBOM with evidence for the current directory"
144
+ ],
145
+ [
146
+ "$0 -i bom.json -o bom.evinse.json -l java --with-reachables .",
147
+ "Generate a Java SBOM with occurrence and reachable evidence for the current directory"
148
+ ]
149
+ ])
150
+ .completion("completion", "Generate bash/zsh completion")
151
+ .epilogue("for documentation, visit https://cyclonedx.github.io/cdxgen")
114
152
  .config(config)
115
153
  .scriptName("evinse")
116
154
  .version()
@@ -119,8 +157,8 @@ const args = yargs(hideBin(process.argv))
119
157
  const evinseArt = `
120
158
  ███████╗██╗ ██╗██╗███╗ ██╗███████╗███████╗
121
159
  ██╔════╝██║ ██║██║████╗ ██║██╔════╝██╔════╝
122
- █████╗ ██║ ██║██║██╔██╗ ██║███████╗█████╗
123
- ██╔══╝ ╚██╗ ██╔╝██║██║╚██╗██║╚════██║██╔══╝
160
+ █████╗ ██║ ██║██║██╔██╗ ██║███████╗█████╗
161
+ ██╔══╝ ╚██╗ ██╔╝██║██║╚██╗██║╚════██║██╔══╝
124
162
  ███████╗ ╚████╔╝ ██║██║ ╚████║███████║███████╗
125
163
  ╚══════╝ ╚═══╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝
126
164
  `;
@@ -141,6 +179,7 @@ console.log(evinseArt);
141
179
  if (args.print) {
142
180
  printOccurrences(bomJson);
143
181
  printCallStack(bomJson);
182
+ printReachables(sliceArtefacts);
144
183
  printServices(bomJson);
145
184
  }
146
185
  }
package/bin/verify.js CHANGED
@@ -24,6 +24,8 @@ const args = yargs(hideBin(process.argv))
24
24
  default: "public.key",
25
25
  description: "Public key in PEM format. Default public.key"
26
26
  })
27
+ .completion("completion", "Generate bash/zsh completion")
28
+ .epilogue("for documentation, visit https://cyclonedx.github.io/cdxgen")
27
29
  .scriptName("cdx-verify")
28
30
  .version()
29
31
  .help("h").argv;
package/binary.js CHANGED
@@ -1,5 +1,11 @@
1
- import { platform as _platform, arch as _arch, tmpdir } from "node:os";
2
- import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
1
+ import { platform as _platform, arch as _arch, tmpdir, homedir } from "node:os";
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ mkdtempSync,
6
+ readFileSync,
7
+ rmSync
8
+ } from "node:fs";
3
9
  import { join, dirname, basename } from "node:path";
4
10
  import { spawnSync } from "node:child_process";
5
11
  import { PackageURL } from "packageurl-js";
@@ -284,6 +290,13 @@ export const getOSPackages = (src) => {
284
290
  const allTypes = new Set();
285
291
  if (TRIVY_BIN) {
286
292
  let imageType = "image";
293
+ const trivyCacheDir = join(homedir(), ".cache", "trivy");
294
+ try {
295
+ mkdirSync(join(trivyCacheDir, "db"), { recursive: true });
296
+ mkdirSync(join(trivyCacheDir, "java-db"), { recursive: true });
297
+ } catch (err) {
298
+ // ignore errors
299
+ }
287
300
  if (existsSync(src)) {
288
301
  imageType = "rootfs";
289
302
  }
@@ -292,12 +305,17 @@ export const getOSPackages = (src) => {
292
305
  const args = [
293
306
  imageType,
294
307
  "--skip-db-update",
308
+ "--skip-java-db-update",
295
309
  "--offline-scan",
310
+ "--skip-files",
311
+ "**/*.jar",
296
312
  "--no-progress",
297
313
  "--exit-code",
298
314
  "0",
299
315
  "--format",
300
316
  "cyclonedx",
317
+ "--cache-dir",
318
+ trivyCacheDir,
301
319
  "--output",
302
320
  bomJsonFile
303
321
  ];
package/data/README.md CHANGED
@@ -18,3 +18,4 @@ Contents of data directory and their purpose.
18
18
  | spdx.schema.json | jsonschema for validation |
19
19
  | vendor-alias.json | List to correct the group names. Used while parsing .jar files |
20
20
  | wrapdb-releases.json | Database of all available meson wraps. Generated using contrib/wrapdb.py. |
21
+ | frameworks-list.json | List of string fragments to categorize components into frameworks |
@@ -0,0 +1,128 @@
1
+ {
2
+ "all": [
3
+ "System.Web",
4
+ "System.ServiceModel",
5
+ "System.Data",
6
+ "spring",
7
+ "flask",
8
+ "django",
9
+ "beego",
10
+ "chi",
11
+ "echo",
12
+ "github.com/gin-gonic/gin",
13
+ "gorilla",
14
+ "rye",
15
+ "httprouter",
16
+ "akka",
17
+ "dropwizard",
18
+ "vertx",
19
+ "gwt",
20
+ "jax-rs",
21
+ "jax-ws",
22
+ "jsf",
23
+ "play",
24
+ "spark",
25
+ "struts",
26
+ "angular",
27
+ "react",
28
+ "next",
29
+ "ember",
30
+ "express",
31
+ "knex",
32
+ "vue",
33
+ "aiohttp",
34
+ "bottle",
35
+ "cherrypy",
36
+ "drt",
37
+ "falcon",
38
+ "hug",
39
+ "pyramid",
40
+ "sanic",
41
+ "tornado",
42
+ "vibora",
43
+ "koa",
44
+ "-sdk",
45
+ "org.apache",
46
+ "appfuse",
47
+ "drools",
48
+ "jbpm",
49
+ "activiti",
50
+ "barracuda",
51
+ "birt",
52
+ "biojava",
53
+ "bluecove",
54
+ "bouncycastle",
55
+ "cascading",
56
+ "deeplearning4j",
57
+ "eclipselink",
58
+ "geoapi",
59
+ "geotools",
60
+ "hibernate",
61
+ "hsqldb",
62
+ "ibatis",
63
+ "javassist",
64
+ "jersey",
65
+ "jetty",
66
+ "jfreechart",
67
+ "jhipster",
68
+ "jmonkeyengine",
69
+ "jsf",
70
+ "keycloak",
71
+ "liquibase",
72
+ "lwjgl",
73
+ "micronaut",
74
+ "mybatis",
75
+ "netty",
76
+ "neuroph",
77
+ "opencv",
78
+ "orientdb",
79
+ "ormlite",
80
+ "payara",
81
+ "primefaces",
82
+ "quarkus",
83
+ "quartz",
84
+ "sax",
85
+ "slf4j",
86
+ "jasper",
87
+ "spock",
88
+ "thymeleaf",
89
+ "vaadin",
90
+ "vertx",
91
+ "wildfly",
92
+ "zkoss",
93
+ "org.ow2.asm",
94
+ "backbone",
95
+ "dojo",
96
+ "ember",
97
+ "enyo",
98
+ "extjs",
99
+ "jquery",
100
+ "jqwidgets",
101
+ "knockout",
102
+ "mootools",
103
+ "prototypejs",
104
+ "qooxdoo",
105
+ "openui5",
106
+ "solidjs",
107
+ "sproutcore",
108
+ "svelte",
109
+ "wakanda",
110
+ "webix",
111
+ "github.com/aerogo/aero",
112
+ "github.com/aofei/air",
113
+ "github.com/go-the-way/anoweb",
114
+ "github.com/appist/appy",
115
+ "github.com/ungerik/go-rest",
116
+ "goa.design/goa",
117
+ "github.com/aceld/zinx",
118
+ "github.com/dolab/gogo",
119
+ "github.com/yarf-framework/yarf",
120
+ "github.com/norunners/vert",
121
+ "pkg:cargo/rocket",
122
+ "pkg:cargo/actix",
123
+ "pkg:cargo/nickel",
124
+ "pkg:cargo/yew",
125
+ "pkg:cargo/azul",
126
+ "pkg:cargo/conrod"
127
+ ]
128
+ }
package/display.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { existsSync, readFileSync } from "fs";
1
2
  import { createStream, table } from "table";
2
3
 
3
4
  // https://github.com/yangshun/tree-node-cli/blob/master/src/index.js
@@ -277,3 +278,36 @@ const recursePrint = (depMap, subtree, level, shownList, treeGraphics) => {
277
278
  }
278
279
  }
279
280
  };
281
+
282
+ export const printReachables = (sliceArtefacts) => {
283
+ const reachablesSlicesFile = sliceArtefacts.reachablesSlicesFile;
284
+ if (!existsSync(reachablesSlicesFile)) {
285
+ return;
286
+ }
287
+ const purlCounts = {};
288
+ const reachablesSlices = JSON.parse(
289
+ readFileSync(reachablesSlicesFile, "utf-8")
290
+ );
291
+ for (const areachable of reachablesSlices.reachables || []) {
292
+ const purls = areachable.purls || [];
293
+ for (const apurl of purls) {
294
+ purlCounts[apurl] = (purlCounts[apurl] || 0) + 1;
295
+ }
296
+ }
297
+ const sortedPurls = Object.fromEntries(
298
+ Object.entries(purlCounts).sort(([, a], [, b]) => b - a)
299
+ );
300
+ const data = [["Package URL", "Reachable Flows"]];
301
+ for (const apurl of Object.keys(sortedPurls)) {
302
+ data.push([apurl, "" + sortedPurls[apurl]]);
303
+ }
304
+ const config = {
305
+ header: {
306
+ alignment: "center",
307
+ content: "Reachable Components\nGenerated with \u2665 by cdxgen"
308
+ }
309
+ };
310
+ if (data.length > 1) {
311
+ console.log(table(data, config));
312
+ }
313
+ };
package/docker.js CHANGED
@@ -323,6 +323,9 @@ export const parseImageName = (fullImageName) => {
323
323
  fullImageName = fullImageName.replace(":" + nameObj.tag, "");
324
324
  }
325
325
  }
326
+ if (fullImageName && fullImageName.startsWith("library/")) {
327
+ fullImageName = fullImageName.replace("library/", "");
328
+ }
326
329
  // The left over string is the repo name
327
330
  nameObj.repo = fullImageName;
328
331
  return nameObj;
@@ -333,7 +336,9 @@ export const parseImageName = (fullImageName) => {
333
336
  */
334
337
  export const getImage = async (fullImageName) => {
335
338
  let localData = undefined;
339
+ let pullData = undefined;
336
340
  const { repo, tag, digest } = parseImageName(fullImageName);
341
+ let repoWithTag = `${repo}:${tag !== "" ? tag : ":latest"}`;
337
342
  // Fetch only the latest tag if none is specified
338
343
  if (tag === "" && digest === "") {
339
344
  fullImageName = fullImageName + ":latest";
@@ -379,6 +384,14 @@ export const getImage = async (fullImageName) => {
379
384
  }
380
385
  }
381
386
  }
387
+ try {
388
+ localData = await makeRequest(`images/${repoWithTag}/json`);
389
+ if (localData) {
390
+ return localData;
391
+ }
392
+ } catch (err) {
393
+ // ignore
394
+ }
382
395
  try {
383
396
  localData = await makeRequest(`images/${repo}/json`);
384
397
  } catch (err) {
@@ -397,7 +410,7 @@ export const getImage = async (fullImageName) => {
397
410
  }
398
411
  // If the data is not available locally
399
412
  try {
400
- const pullData = await makeRequest(
413
+ pullData = await makeRequest(
401
414
  `images/create?fromImage=${fullImageName}`,
402
415
  "POST"
403
416
  );
@@ -415,15 +428,42 @@ export const getImage = async (fullImageName) => {
415
428
  return undefined;
416
429
  }
417
430
  } catch (err) {
418
- // continue regardless of error
431
+ try {
432
+ if (DEBUG_MODE) {
433
+ console.log(`Re-trying the pull with the name ${repoWithTag}.`);
434
+ }
435
+ pullData = await makeRequest(
436
+ `images/create?fromImage=${repoWithTag}`,
437
+ "POST"
438
+ );
439
+ } catch (err) {
440
+ // continue regardless of error
441
+ }
419
442
  }
420
443
  try {
421
444
  if (DEBUG_MODE) {
422
- console.log(`Trying with ${repo}`);
445
+ console.log(`Trying with ${repoWithTag}`);
446
+ }
447
+ localData = await makeRequest(`images/${repoWithTag}/json`);
448
+ if (localData) {
449
+ return localData;
423
450
  }
424
- localData = await makeRequest(`images/${repo}/json`);
425
451
  } catch (err) {
426
452
  try {
453
+ if (DEBUG_MODE) {
454
+ console.log(`Trying with ${repo}`);
455
+ }
456
+ localData = await makeRequest(`images/${repo}/json`);
457
+ if (localData) {
458
+ return localData;
459
+ }
460
+ } catch (err) {
461
+ // continue regardless of error
462
+ }
463
+ try {
464
+ if (DEBUG_MODE) {
465
+ console.log(`Trying with ${fullImageName}`);
466
+ }
427
467
  localData = await makeRequest(`images/${fullImageName}/json`);
428
468
  } catch (err) {
429
469
  // continue regardless of error
@@ -701,7 +741,26 @@ export const exportImage = async (fullImageName) => {
701
741
  })
702
742
  );
703
743
  } catch (err) {
704
- console.error(err);
744
+ if (localData && localData.Id) {
745
+ console.log(`Retrying with ${localData.Id}`);
746
+ try {
747
+ await stream.pipeline(
748
+ client.stream(`images/${localData.Id}/get`),
749
+ x({
750
+ sync: true,
751
+ preserveOwner: false,
752
+ noMtime: true,
753
+ noChmod: true,
754
+ strict: true,
755
+ C: tempDir,
756
+ portable: true,
757
+ onwarn: () => {}
758
+ })
759
+ );
760
+ } catch (err) {
761
+ console.log(err);
762
+ }
763
+ }
705
764
  }
706
765
  }
707
766
  // Continue with extracting the layers