Skip to content

Commit

Permalink
fix: decouple os release analyzer
Browse files Browse the repository at this point in the history
Allow the logic about processing OS release files to be reused in the future for static analysis.
  • Loading branch information
ivanstanev committed Sep 27, 2019
1 parent 780919a commit 8d89eae
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 156 deletions.
2 changes: 1 addition & 1 deletion lib/analyzer/docker-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DockerOptions } from "../docker";
import * as dockerFile from "../docker-file";
import * as binariesAnalyzer from "./binaries-analyzer";
import * as imageInspector from "./image-inspector";
import * as osReleaseDetector from "./os-release-detector";
import * as osReleaseDetector from "./os-release";

import apkInputDocker = require("../inputs/apk/docker");
import aptInputDocker = require("../inputs/apt/docker");
Expand Down
154 changes: 0 additions & 154 deletions lib/analyzer/os-release-detector.ts

This file was deleted.

81 changes: 81 additions & 0 deletions lib/analyzer/os-release/docker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Docker, DockerOptions } from "../../docker";
import { DockerFileAnalysis } from "../../docker-file";
import {
getAlpineRelease,
getDebianVersion,
getLsbRelease,
getOracleRelease,
getOsRelease,
getRedHatRelease,
} from "../../inputs/os-release/docker";
import { OSRelease } from "../types";
import {
tryAlpineRelease,
tryDebianVersion,
tryLsbRelease,
tryOracleRelease,
tryOSRelease,
tryRedHatRelease,
} from "./release-analyzer";

export async function detect(
targetImage: string,
dockerfileAnalysis?: DockerFileAnalysis,
options?: DockerOptions,
): Promise<OSRelease> {
const docker = new Docker(targetImage, options);

let osRelease = await getOsRelease(docker).then((release) =>
tryOSRelease(release),
);

// First generic fallback
if (!osRelease) {
osRelease = await getLsbRelease(docker).then((release) =>
tryLsbRelease(release),
);
}

// Fallbacks for specific older distributions
if (!osRelease) {
osRelease = await getDebianVersion(docker).then((release) =>
tryDebianVersion(release),
);
}

if (!osRelease) {
osRelease = await getAlpineRelease(docker).then((release) =>
tryAlpineRelease(release),
);
}

if (!osRelease) {
osRelease = await getOracleRelease(docker).then((release) =>
tryOracleRelease(release),
);
}

if (!osRelease) {
osRelease = await getRedHatRelease(docker).then((release) =>
tryRedHatRelease(release),
);
}

if (!osRelease) {
if (dockerfileAnalysis && dockerfileAnalysis.baseImage === "scratch") {
// If the docker file was build from a scratch image
// then we don't have a known OS

osRelease = { name: "scratch", version: "0.0" };
} else {
throw new Error("Failed to detect OS release");
}
}

// Oracle Linux identifies itself as "ol"
if (osRelease.name.trim() === "ol") {
osRelease.name = "oracle";
}

return osRelease;
}
3 changes: 3 additions & 0 deletions lib/analyzer/os-release/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { detect } from "./docker";

export { detect };
93 changes: 93 additions & 0 deletions lib/analyzer/os-release/release-analyzer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { OSRelease } from "../types";

export async function tryOSRelease(text: string): Promise<OSRelease | null> {
if (!text) {
return null;
}
const idRes = text.match(/^ID=(.+)$/m);
if (!idRes) {
throw new Error("Failed to parse /etc/os-release");
}
const name = idRes[1].replace(/"/g, "");
const versionRes = text.match(/^VERSION_ID=(.+)$/m);
let version = versionRes ? versionRes[1].replace(/"/g, "") : "unstable";

if (name === "ol") {
version = version.split(".")[0];
}

return { name, version };
}

export async function tryLsbRelease(text: string): Promise<OSRelease | null> {
if (!text) {
return null;
}
const idRes = text.match(/^DISTRIB_ID=(.+)$/m);
const versionRes = text.match(/^DISTRIB_RELEASE=(.+)$/m);
if (!idRes || !versionRes) {
throw new Error("Failed to parse /etc/lsb-release");
}
const name = idRes[1].replace(/"/g, "").toLowerCase();
const version = versionRes[1].replace(/"/g, "");
return { name, version };
}

export async function tryDebianVersion(
text: string,
): Promise<OSRelease | null> {
if (!text) {
return null;
}
text = text.trim();
if (text.length < 2) {
throw new Error("Failed to parse /etc/debian_version");
}
return { name: "debian", version: text.split(".")[0] };
}

export async function tryAlpineRelease(
text: string,
): Promise<OSRelease | null> {
if (!text) {
return null;
}
text = text.trim();
if (text.length < 2) {
throw new Error("Failed to parse /etc/alpine-release");
}
return { name: "alpine", version: text };
}

export async function tryRedHatRelease(
text: string,
): Promise<OSRelease | null> {
if (!text) {
return null;
}
const idRes = text.match(/^(\S+)/m);
const versionRes = text.match(/(\d+)\./m);
if (!idRes || !versionRes) {
throw new Error("Failed to parse /etc/redhat-release");
}
const name = idRes[1].replace(/"/g, "").toLowerCase();
const version = versionRes[1].replace(/"/g, "");
return { name, version };
}

export async function tryOracleRelease(
text: string,
): Promise<OSRelease | null> {
if (!text) {
return null;
}
const idRes = text.match(/^(\S+)/m);
const versionRes = text.match(/(\d+\.\d+)/m);
if (!idRes || !versionRes) {
throw new Error("Failed to parse /etc/oracle-release");
}
const name = idRes[1].replace(/"/g, "").toLowerCase();
const version = versionRes[1].replace(/"/g, "").split(".")[0];

return { name, version };
}
36 changes: 36 additions & 0 deletions lib/inputs/os-release/docker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Docker } from "../../docker";

export function getOsRelease(docker: Docker): Promise<string> {
return getFileContent(docker, "/etc/os-release");
}

export function getLsbRelease(docker: Docker): Promise<string> {
return getFileContent(docker, "/etc/lsb-release");
}

export function getDebianVersion(docker: Docker): Promise<string> {
return getFileContent(docker, "/etc/debian_version");
}

export function getAlpineRelease(docker: Docker): Promise<string> {
return getFileContent(docker, "/etc/alpine-release");
}

export function getRedHatRelease(docker: Docker): Promise<string> {
return getFileContent(docker, "/etc/redhat-release");
}

export function getOracleRelease(docker: Docker): Promise<string> {
return getFileContent(docker, "/etc/oracle-release");
}

async function getFileContent(
docker: Docker,
release: string,
): Promise<string> {
try {
return (await docker.catSafe(release)).stdout;
} catch (error) {
throw new Error(error.stderr);
}
}
2 changes: 1 addition & 1 deletion test/lib/analyzer/os-release-detector.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as path from "path";
import * as sinon from "sinon";
import { test } from "tap";

import * as osReleaseDetector from "../../../lib/analyzer/os-release-detector";
import * as osReleaseDetector from "../../../lib/analyzer/os-release";
import * as subProcess from "../../../lib/sub-process";

const readOsFixtureFile = (...from) =>
Expand Down

0 comments on commit 8d89eae

Please sign in to comment.