Skip to content

Commit 0886129

Browse files
committed
fix: use central parser in dependency tree
1 parent b7aac22 commit 0886129

File tree

3 files changed

+221
-11
lines changed

3 files changed

+221
-11
lines changed

lib/dependency-tree/index.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
import { AnalyzedPackageWithVersion, OSRelease } from "../analyzer/types";
2+
import { parseImageReference } from "../image-reference";
23
import { DepTree, DepTreeDep } from "../types";
34

4-
/** @deprecated Should implement a new function to build a dependency graph instead. */
5-
export function buildTree(
6-
targetImage: string,
7-
packageFormat: string,
8-
depInfosList: AnalyzedPackageWithVersion[],
9-
targetOS: OSRelease,
10-
): DepTree {
5+
export function nameAndVersionFromTargetImage(targetImage: string): {
6+
name: string;
7+
version: string;
8+
} {
9+
let imageName: string;
10+
let imageVersion: string;
11+
12+
if (!targetImage.endsWith(".tar")) {
13+
try {
14+
const parsed = parseImageReference(targetImage);
15+
return {
16+
name: parsed.fullName,
17+
version: parsed.tag ? parsed.tag : parsed.digest ? "" : "latest",
18+
};
19+
} catch {
20+
// Fallback to manual parsing
21+
}
22+
}
23+
1124
// A tag can only occur in the last section of a docker image name, so
1225
// check any colon separator after the final '/'. If there are no '/',
1326
// which is common when using Docker's official images such as
@@ -18,8 +31,8 @@ export function buildTree(
1831
targetImage.includes(":");
1932

2033
// Defaults for simple images from dockerhub, like "node" or "centos"
21-
let imageName = targetImage;
22-
let imageVersion = "latest";
34+
imageName = targetImage;
35+
imageVersion = "latest";
2336

2437
// If we have a version, split on the last ':' to avoid the optional
2538
// port on a hostname (i.e. localhost:5000)
@@ -40,6 +53,19 @@ export function buildTree(
4053
imageVersion = "";
4154
}
4255

56+
return { name: imageName, version: imageVersion };
57+
}
58+
59+
/** @deprecated Should implement a new function to build a dependency graph instead. */
60+
export function buildTree(
61+
targetImage: string,
62+
packageFormat: string,
63+
depInfosList: AnalyzedPackageWithVersion[],
64+
targetOS: OSRelease,
65+
): DepTree {
66+
const { name: imageName, version: imageVersion } =
67+
nameAndVersionFromTargetImage(targetImage);
68+
4369
const root: DepTree = {
4470
// don't use the real image name to avoid scanning it as an issue
4571
name: "docker-image|" + imageName,

lib/image-reference.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,26 @@ export class ParsedImageReference {
5454
return ref;
5555
}
5656

57+
/**
58+
* The qualified repository name.
59+
* This is the registry and repository combined.
60+
*/
61+
get fullName(): string {
62+
return this.registry
63+
? this.registry + "/" + this.repository
64+
: this.repository;
65+
}
66+
5767
/**
5868
* Whether the image is from Docker Hub.
5969
* This is true if the registry is "registry-1.docker.io" or "docker.io" or undefined.
6070
*/
6171
get isDockerHub(): boolean {
62-
return this.registry === "registry-1.docker.io" || this.registry === "docker.io" || this.registry === undefined;
72+
return (
73+
this.registry === "registry-1.docker.io" ||
74+
this.registry === "docker.io" ||
75+
this.registry === undefined
76+
);
6377
}
6478

6579
/**
@@ -151,3 +165,12 @@ export function isValidImageReference(reference: string): boolean {
151165
return false;
152166
}
153167
}
168+
169+
// Regular Expression Source: OCI Image Spec V1
170+
// https://github.com/opencontainers/image-spec/blob/d60099175f88c47cd379c4738d158884749ed235/descriptor.md?plain=1#L143
171+
const digestIsValid = (digest: string) => /^sha256:[a-f0-9]{64}$/.test(digest);
172+
173+
// Regular Expression Source: OCI Image Spec V1
174+
// https://github.com/opencontainers/distribution-spec/blob/3940529fe6c0a068290b27fb3cd797cf0528bed6/spec.md?plain=1#L160
175+
const tagIsValid = (tag: string) =>
176+
/^[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}$/.test(tag);

test/lib/dependency-tree/index.spec.ts

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { AnalyzedPackageWithVersion } from "../../../lib/analyzer/types";
2-
import { buildTree } from "../../../lib/dependency-tree";
2+
import {
3+
buildTree,
4+
nameAndVersionFromTargetImage,
5+
} from "../../../lib/dependency-tree";
36

47
const targetImage = "snyk/deptree-test@1.0.0";
58
const targetOS = {
@@ -76,3 +79,161 @@ describe("dependency-tree", () => {
7679
});
7780
});
7881
});
82+
83+
const validSha256 =
84+
"sha256:56ea7092e72db3e9f84d58d583370d59b842de02ea9e1f836c3f3afc7ce408c1";
85+
86+
describe("nameAndVersionFromTargetImage", () => {
87+
describe("handles valid image references", () => {
88+
it("with repository only", () => {
89+
expect(nameAndVersionFromTargetImage("nginx")).toEqual({
90+
name: "nginx",
91+
version: "latest",
92+
});
93+
});
94+
95+
it("with tag", () => {
96+
expect(nameAndVersionFromTargetImage("nginx:1.23")).toEqual({
97+
name: "nginx",
98+
version: "1.23",
99+
});
100+
});
101+
102+
it("with digest", () => {
103+
expect(nameAndVersionFromTargetImage(`nginx@${validSha256}`)).toEqual({
104+
name: "nginx",
105+
version: "",
106+
});
107+
});
108+
109+
it("with tag and digest", () => {
110+
expect(
111+
nameAndVersionFromTargetImage(`nginx:1.23@${validSha256}`),
112+
).toEqual({
113+
name: "nginx",
114+
version: "1.23",
115+
});
116+
});
117+
118+
it("with registry", () => {
119+
expect(nameAndVersionFromTargetImage("gcr.io/project/nginx")).toEqual({
120+
name: "gcr.io/project/nginx",
121+
version: "latest",
122+
});
123+
});
124+
125+
it("with registry and port", () => {
126+
expect(nameAndVersionFromTargetImage("localhost:5000/foo/bar")).toEqual({
127+
name: "localhost:5000/foo/bar",
128+
version: "latest",
129+
});
130+
});
131+
132+
it("with registry and port and tag", () => {
133+
expect(
134+
nameAndVersionFromTargetImage("localhost:5000/foo/bar:tag"),
135+
).toEqual({
136+
name: "localhost:5000/foo/bar",
137+
version: "tag",
138+
});
139+
});
140+
141+
it("with registry and port and digest", () => {
142+
expect(
143+
nameAndVersionFromTargetImage(`localhost:5000/foo/bar@${validSha256}`),
144+
).toEqual({
145+
name: "localhost:5000/foo/bar",
146+
version: "",
147+
});
148+
});
149+
150+
it("with registry, port, digest and tag", () => {
151+
expect(
152+
nameAndVersionFromTargetImage(
153+
`localhost:5000/foo/bar:tag@${validSha256}`,
154+
),
155+
).toEqual({
156+
name: "localhost:5000/foo/bar",
157+
version: "tag",
158+
});
159+
});
160+
161+
it("with library/ namespace", () => {
162+
expect(nameAndVersionFromTargetImage("library/nginx:latest")).toEqual({
163+
name: "library/nginx",
164+
version: "latest",
165+
});
166+
});
167+
168+
it("with dots and dashes in the tag", () => {
169+
expect(nameAndVersionFromTargetImage("nginx:1.23.0-alpha")).toEqual({
170+
name: "nginx",
171+
version: "1.23.0-alpha",
172+
});
173+
});
174+
});
175+
176+
// These tests are to verify that the previous logic is still working as expected for
177+
// references that cannot be parsed by the new parseImageReference function.
178+
// They are not necessarily asserting that this is the correct parsing logic.
179+
describe("handles file-based reference strings", () => {
180+
it("with a simple image name", () => {
181+
expect(nameAndVersionFromTargetImage("image.tar")).toEqual({
182+
name: "image.tar",
183+
version: "",
184+
});
185+
});
186+
187+
it("with a longer path", () => {
188+
expect(nameAndVersionFromTargetImage("path/to/archive.tar")).toEqual({
189+
name: "path/to/archive.tar",
190+
version: "",
191+
});
192+
});
193+
194+
it("with a tag", () => {
195+
expect(nameAndVersionFromTargetImage("path/to/archive.tar:tag")).toEqual({
196+
name: "path/to/archive.tar",
197+
version: "tag",
198+
});
199+
});
200+
201+
it("with a digest", () => {
202+
expect(
203+
nameAndVersionFromTargetImage(`archive.tar@${validSha256}`),
204+
).toEqual({
205+
name: "archive.tar",
206+
version: "",
207+
});
208+
});
209+
210+
it("with a tag and digest", () => {
211+
expect(
212+
nameAndVersionFromTargetImage(`path/to/archive.tar:tag@${validSha256}`),
213+
).toEqual({
214+
name: "path/to/archive.tar",
215+
version: "tag",
216+
});
217+
});
218+
219+
it("with a tag and specific image name", () => {
220+
expect(
221+
nameAndVersionFromTargetImage("path/to/archive.tar:image:tag"),
222+
).toEqual({
223+
name: "path/to/archive.tar:image",
224+
version: "tag",
225+
});
226+
});
227+
228+
it("with a tag and specific image name and digest", () => {
229+
expect(
230+
nameAndVersionFromTargetImage(
231+
`path/to/archive.tar:image:tag@${validSha256}`,
232+
),
233+
).toEqual({
234+
name: "path/to/archive.tar:image:tag",
235+
version: "",
236+
});
237+
});
238+
});
239+
});

0 commit comments

Comments
 (0)