@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 +4 -4
- package/analyzer.js +1 -1
- package/data/frameworks-list.json +20 -1
- package/data/known-licenses.json +78 -27
- package/docker.js +271 -30
- package/docker.test.js +38 -6
- package/evinser.js +35 -31
- package/index.js +78 -28
- package/package.json +7 -7
- package/server.js +3 -2
- package/utils.js +459 -145
- package/utils.test.js +36 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
}
|
package/data/known-licenses.json
CHANGED
|
@@ -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
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"name": "*"
|
|
3
|
+
"packageNamespace": "*",
|
|
4
|
+
"knownLicenses": [{ "license": "MIT", "urlIncludes": "mit-license" }]
|
|
12
5
|
},
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 (
|
|
159
|
-
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
347
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
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
|
+
};
|