Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: decouple os release analyzer #109

Merged
merged 1 commit into from
Oct 3, 2019
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
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