Skip to content

Commit 79a0114

Browse files
authored
feat: JVM release file parsing (#723)
* feat: add java runtime version detection from release file * fix: add fact to schema * fix: rename files
1 parent f9e49ac commit 79a0114

File tree

10 files changed

+721
-0
lines changed

10 files changed

+721
-0
lines changed

components/common.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ schemas:
1717
enum:
1818
- autoDetectedUserInstructions
1919
- binaries
20+
- baseRuntimes
2021
- depGraph
2122
- dockerfileAnalysis
2223
- dockerLayers
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ExtractedLayers } from "../../extractor/types";
2+
import { BaseRuntime } from "../../facts";
3+
import { getJavaRuntimeReleaseContent } from "../../inputs/base-runtimes/static";
4+
import { parseJavaRuntimeRelease } from "./parser";
5+
6+
export function detectJavaRuntime(
7+
extractedLayers: ExtractedLayers,
8+
): BaseRuntime | null {
9+
const releaseContent = getJavaRuntimeReleaseContent(extractedLayers);
10+
return releaseContent ? parseJavaRuntimeRelease(releaseContent) : null;
11+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { BaseRuntime } from "../../facts";
2+
3+
const VALID_VERSION_PATTERN =
4+
/^(?!.*\.\.)[0-9]+(?:[._+a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
5+
6+
const regex = /^\s*JAVA_VERSION\s*=\s*(?:(["'])(.*?)\1|([^#\r\n]+))/gm;
7+
8+
function isValidJavaVersion(version: string): boolean {
9+
if (!version || version.length === 0) {
10+
return false;
11+
}
12+
return VALID_VERSION_PATTERN.test(version);
13+
}
14+
15+
export function parseJavaRuntimeRelease(content: string): BaseRuntime | null {
16+
if (!content || content.trim().length === 0) {
17+
return null;
18+
}
19+
try {
20+
const matches = [...content.matchAll(regex)];
21+
22+
if (matches.length !== 1) {
23+
return null;
24+
}
25+
const version = (matches[0][2] || matches[0][3] || "").trim();
26+
27+
if (!isValidJavaVersion(version)) {
28+
return null;
29+
}
30+
return { type: "java", version };
31+
} catch (error) {
32+
return null;
33+
}
34+
}

lib/analyzer/static-analyzer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getDpkgFileContentAction,
1616
getExtFileContentAction,
1717
} from "../inputs/apt/static";
18+
import { getJavaRuntimeReleaseAction } from "../inputs/base-runtimes/static";
1819
import {
1920
getBinariesHashes,
2021
getNodeBinariesFileContentAction,
@@ -67,6 +68,7 @@ import { jarFilesToScannedResults } from "./applications/java";
6768
import { pipFilesToScannedProjects } from "./applications/python";
6869
import { getApplicationFiles } from "./applications/runtime-common";
6970
import { AppDepsScanResultWithoutTarget } from "./applications/types";
71+
import { detectJavaRuntime } from "./base-runtimes";
7072
import * as osReleaseDetector from "./os-release";
7173
import { analyze as apkAnalyze } from "./package-managers/apk";
7274
import {
@@ -105,6 +107,7 @@ export async function analyze(
105107
...getOsReleaseActions,
106108
getNodeBinariesFileContentAction,
107109
getOpenJDKBinariesFileContentAction,
110+
getJavaRuntimeReleaseAction,
108111
getDpkgPackageFileContentAction,
109112
getRedHatRepositoriesContentAction,
110113
];
@@ -233,6 +236,8 @@ export async function analyze(
233236
}
234237

235238
const binaries = getBinariesHashes(extractedLayers);
239+
const javaRuntime = detectJavaRuntime(extractedLayers);
240+
const baseRuntimes = javaRuntime ? [javaRuntime] : undefined;
236241

237242
const applicationDependenciesScanResults: AppDepsScanResultWithoutTarget[] =
238243
[];
@@ -309,6 +314,7 @@ export async function analyze(
309314
platform,
310315
results,
311316
binaries,
317+
baseRuntimes,
312318
imageLayers: manifestLayers,
313319
rootFsLayers,
314320
applicationDependenciesScanResults,

lib/analyzer/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ImageName } from "../extractor/image";
2+
import { BaseRuntime } from "../facts";
23
import { AutoDetectedUserInstructions, ManifestFile } from "../types";
34
import {
45
AppDepsScanResultWithoutTarget,
@@ -75,6 +76,7 @@ export interface StaticAnalysis {
7576
osRelease: OSRelease;
7677
results: ImageAnalysis[];
7778
binaries: string[];
79+
baseRuntimes?: BaseRuntime[];
7880
imageLayers: string[];
7981
rootFsLayers?: string[];
8082
autoDetectedUserInstructions?: AutoDetectedUserInstructions;

lib/facts.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,3 +152,13 @@ export interface PluginWarningsFact {
152152
parameterChecks?: string[];
153153
};
154154
}
155+
156+
export interface BaseRuntime {
157+
type: string;
158+
version: string;
159+
}
160+
161+
export interface BaseRuntimesFact {
162+
type: "baseRuntimes";
163+
data: BaseRuntime[];
164+
}

lib/inputs/base-runtimes/static.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { normalize as normalizePath } from "path";
2+
import { getContentAsString } from "../../extractor";
3+
import { ExtractAction, ExtractedLayers } from "../../extractor/types";
4+
import { streamToString } from "../../stream-utils";
5+
6+
export const getJavaRuntimeReleaseAction: ExtractAction = {
7+
actionName: "java-runtime-release",
8+
filePathMatches: (filePath) =>
9+
filePath === normalizePath("/opt/java/openjdk/release"),
10+
callback: streamToString,
11+
};
12+
13+
export function getJavaRuntimeReleaseContent(
14+
extractedLayers: ExtractedLayers,
15+
): string {
16+
const content = getContentAsString(
17+
extractedLayers,
18+
getJavaRuntimeReleaseAction,
19+
);
20+
return content || "";
21+
}

lib/response-builder.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ async function buildResponse(
5151
};
5252
additionalFacts.push(keyBinariesHashesFact);
5353
}
54+
if (depsAnalysis.baseRuntimes && depsAnalysis.baseRuntimes.length > 0) {
55+
const baseRuntimesFact: facts.BaseRuntimesFact = {
56+
type: "baseRuntimes",
57+
data: depsAnalysis.baseRuntimes,
58+
};
59+
additionalFacts.push(baseRuntimesFact);
60+
}
5461

5562
if (dockerfileAnalysis !== undefined) {
5663
const dockerfileAnalysisFact: facts.DockerfileAnalysisFact = {

lib/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ export type FactType =
7070
| "jarFingerprints"
7171
// Hashes of executables not installed by a package manager (e.g. if they were copied straight onto the image).
7272
| "keyBinariesHashes"
73+
// Base runtime metadata (e.g., Java ) extracted from release files
74+
| "baseRuntimes"
7375
| "loadedPackages"
7476
| "ociDistributionMetadata"
7577
| "containerConfig"

0 commit comments

Comments
 (0)