Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ schemas:
enum:
- autoDetectedUserInstructions
- binaries
- baseRuntimes
- depGraph
- dockerfileAnalysis
- dockerLayers
Expand Down
11 changes: 11 additions & 0 deletions lib/analyzer/base-runtimes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ExtractedLayers } from "../../extractor/types";
import { BaseRuntime } from "../../facts";
import { getJavaRuntimeReleaseContent } from "../../inputs/base-runtimes/static";
import { parseJavaRuntimeRelease } from "./parser";

export function detectJavaRuntime(
extractedLayers: ExtractedLayers,
): BaseRuntime | null {
const releaseContent = getJavaRuntimeReleaseContent(extractedLayers);
return releaseContent ? parseJavaRuntimeRelease(releaseContent) : null;
}
34 changes: 34 additions & 0 deletions lib/analyzer/base-runtimes/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { BaseRuntime } from "../../facts";

const VALID_VERSION_PATTERN =
/^(?!.*\.\.)[0-9]+(?:[._+a-zA-Z0-9-]*[a-zA-Z0-9])?$/;

const regex = /^\s*JAVA_VERSION\s*=\s*(?:(["'])(.*?)\1|([^#\r\n]+))/gm;

function isValidJavaVersion(version: string): boolean {
if (!version || version.length === 0) {
return false;
}
return VALID_VERSION_PATTERN.test(version);
}

export function parseJavaRuntimeRelease(content: string): BaseRuntime | null {
if (!content || content.trim().length === 0) {
return null;
}
try {
const matches = [...content.matchAll(regex)];

if (matches.length !== 1) {
return null;
}
const version = (matches[0][2] || matches[0][3] || "").trim();

if (!isValidJavaVersion(version)) {
return null;
}
return { type: "java", version };
} catch (error) {
return null;
}
}
6 changes: 6 additions & 0 deletions lib/analyzer/static-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getDpkgFileContentAction,
getExtFileContentAction,
} from "../inputs/apt/static";
import { getJavaRuntimeReleaseAction } from "../inputs/base-runtimes/static";
import {
getBinariesHashes,
getNodeBinariesFileContentAction,
Expand Down Expand Up @@ -67,6 +68,7 @@ import { jarFilesToScannedResults } from "./applications/java";
import { pipFilesToScannedProjects } from "./applications/python";
import { getApplicationFiles } from "./applications/runtime-common";
import { AppDepsScanResultWithoutTarget } from "./applications/types";
import { detectJavaRuntime } from "./base-runtimes";
import * as osReleaseDetector from "./os-release";
import { analyze as apkAnalyze } from "./package-managers/apk";
import {
Expand Down Expand Up @@ -105,6 +107,7 @@ export async function analyze(
...getOsReleaseActions,
getNodeBinariesFileContentAction,
getOpenJDKBinariesFileContentAction,
getJavaRuntimeReleaseAction,
getDpkgPackageFileContentAction,
getRedHatRepositoriesContentAction,
];
Expand Down Expand Up @@ -233,6 +236,8 @@ export async function analyze(
}

const binaries = getBinariesHashes(extractedLayers);
const javaRuntime = detectJavaRuntime(extractedLayers);
const baseRuntimes = javaRuntime ? [javaRuntime] : undefined;

const applicationDependenciesScanResults: AppDepsScanResultWithoutTarget[] =
[];
Expand Down Expand Up @@ -309,6 +314,7 @@ export async function analyze(
platform,
results,
binaries,
baseRuntimes,
imageLayers: manifestLayers,
rootFsLayers,
applicationDependenciesScanResults,
Expand Down
2 changes: 2 additions & 0 deletions lib/analyzer/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ImageName } from "../extractor/image";
import { BaseRuntime } from "../facts";
import { AutoDetectedUserInstructions, ManifestFile } from "../types";
import {
AppDepsScanResultWithoutTarget,
Expand Down Expand Up @@ -75,6 +76,7 @@ export interface StaticAnalysis {
osRelease: OSRelease;
results: ImageAnalysis[];
binaries: string[];
baseRuntimes?: BaseRuntime[];
imageLayers: string[];
rootFsLayers?: string[];
autoDetectedUserInstructions?: AutoDetectedUserInstructions;
Expand Down
10 changes: 10 additions & 0 deletions lib/facts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,13 @@ export interface PluginWarningsFact {
parameterChecks?: string[];
};
}

export interface BaseRuntime {
type: string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the only current value is "java" would narrowing this to a literal type (or a discriminated union) provides compile-time safety:
For example:

export interface BaseRuntime {
    type: "java";                                                                                                                                     
    version: string;
  }

version: string;
}

export interface BaseRuntimesFact {
type: "baseRuntimes";
data: BaseRuntime[];
}
21 changes: 21 additions & 0 deletions lib/inputs/base-runtimes/static.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { normalize as normalizePath } from "path";
import { getContentAsString } from "../../extractor";
import { ExtractAction, ExtractedLayers } from "../../extractor/types";
import { streamToString } from "../../stream-utils";

export const getJavaRuntimeReleaseAction: ExtractAction = {
actionName: "java-runtime-release",
filePathMatches: (filePath) =>
filePath === normalizePath("/opt/java/openjdk/release"),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude suggests that:

/opt/java/openjdk/release is the Eclipse Adoptium/Temurin convention. Many widely-used JVM base images use different paths:

  • eclipse-temurin → /opt/java/openjdk/release ✓
  • openjdk (official Docker) → /usr/local/openjdk-/release
  • Debian/Ubuntu default-jdk → /usr/lib/jvm/java--openjdk-/release
  • Oracle → /usr/java//release

Should we at least expand to a few common other release file locations:

  filePathMatches: (filePath) =>
    filePath === normalizePath("/opt/java/openjdk/release") ||
    filePath.startsWith(normalizePath("/usr/local/openjdk-")) ...

callback: streamToString,
};

export function getJavaRuntimeReleaseContent(
extractedLayers: ExtractedLayers,
): string {
const content = getContentAsString(
extractedLayers,
getJavaRuntimeReleaseAction,
);
return content || "";
}
7 changes: 7 additions & 0 deletions lib/response-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ async function buildResponse(
};
additionalFacts.push(keyBinariesHashesFact);
}
if (depsAnalysis.baseRuntimes && depsAnalysis.baseRuntimes.length > 0) {
const baseRuntimesFact: facts.BaseRuntimesFact = {
type: "baseRuntimes",
data: depsAnalysis.baseRuntimes,
};
additionalFacts.push(baseRuntimesFact);
}

if (dockerfileAnalysis !== undefined) {
const dockerfileAnalysisFact: facts.DockerfileAnalysisFact = {
Expand Down
2 changes: 2 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export type FactType =
| "jarFingerprints"
// Hashes of executables not installed by a package manager (e.g. if they were copied straight onto the image).
| "keyBinariesHashes"
// Base runtime metadata (e.g., Java ) extracted from release files
| "baseRuntimes"
| "loadedPackages"
| "ociDistributionMetadata"
| "containerConfig"
Expand Down
Loading
Loading