-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathoci_dockerfile.js
More file actions
118 lines (104 loc) · 3.88 KB
/
Copy pathoci_dockerfile.js
File metadata and controls
118 lines (104 loc) · 3.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import fs from 'node:fs'
import { generateImageSBOM, parseImageRef } from '../oci_image/utils.js'
import { getFromQuery, getParser } from './containerfile_parser.js'
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'oci' } }
/** @typedef {import('../provider').Provider} */
/** @typedef {import('../provider').Provided} Provided */
/**
* @type {string} ecosystem identifier for OCI image packages
* @private
*/
const ecosystem = 'oci'
/**
* Check if the given manifest name is a Dockerfile or Containerfile.
* Supports dot-suffixed variants such as Dockerfile.dev or Containerfile.prod.
* @param {string} manifestName the manifest file name to check
* @returns {boolean} true if the manifest is a Dockerfile or Containerfile
*/
function isSupported(manifestName) {
return /^(Dockerfile|Containerfile)(\..+)?$/.test(manifestName)
}
/**
* Dockerfiles have no lock file, so validation always passes.
* @returns {boolean} always true
*/
function validateLockFile() { return true; }
/**
* Check whether a syntax node contains any expansion (variable substitution) children.
* @param {import('web-tree-sitter').SyntaxNode} node
* @returns {boolean}
* @private
*/
function containsExpansion(node) {
if (node.type === 'expansion') {
return true
}
for (let i = 0; i < node.childCount; i++) {
if (containsExpansion(node.child(i))) {
return true
}
}
return false
}
/**
* Parse the last FROM instruction from a Dockerfile using tree-sitter to extract the base image reference.
* In multi-stage builds, the last FROM represents the final stage.
* @param {string} manifestContent the content of the Dockerfile
* @returns {Promise<string>} the image reference from the last FROM instruction
* @throws {Error} when no FROM instruction is found or when ARG substitution is used
*/
export async function parseFromImage(manifestContent) {
const [parser, fromQuery] = await Promise.all([getParser(), getFromQuery()])
const tree = parser.parse(manifestContent)
const matches = fromQuery.matches(tree.rootNode)
if (matches.length === 0) {
throw new Error('No FROM line found in Dockerfile')
}
const lastMatch = matches[matches.length - 1]
const imageSpec = lastMatch.captures.find(c => c.name === 'image').node
if (containsExpansion(imageSpec)) {
throw new Error('Dockerfile uses ARG substitution in FROM line — cannot resolve variable references')
}
return imageSpec.text
}
/**
* Generate an image SBOM from a Dockerfile manifest using syft.
* @param {string} manifest path to the Dockerfile
* @param {{}} [opts={}] optional various options to pass along the application
* @returns {Promise<{ecosystem: string, content: string, contentType: string}>}
* @private
*/
async function getImageSBOM(manifest, opts = {}) {
const manifestContent = fs.readFileSync(manifest, 'utf-8')
const image = await parseFromImage(manifestContent)
const imageRef = parseImageRef(image, opts)
const sbom = generateImageSBOM(imageRef, opts)
return {
ecosystem,
content: JSON.stringify(sbom),
contentType: 'application/vnd.cyclonedx+json'
}
}
/**
* Provide content and content type for Dockerfile component analysis.
* @param {string} manifest path to the Dockerfile
* @param {{}} [opts={}] optional various options to pass along the application
* @returns {Promise<Provided>}
*/
async function provideComponent(manifest, opts = {}) {
return getImageSBOM(manifest, opts)
}
/**
* Provide content and content type for Dockerfile stack analysis.
* @param {string} manifest path to the Dockerfile
* @param {{}} [opts={}] optional various options to pass along the application
* @returns {Promise<Provided>}
*/
async function provideStack(manifest, opts = {}) {
return getImageSBOM(manifest, opts)
}
/**
* Dockerfiles contain no license information.
* @returns {null} always null
*/
function readLicenseFromManifest() { return null; }