Skip to content

Commit bdbbc88

Browse files
committed
feat: convert parsedimagereference to class
1 parent 6e1e9b9 commit bdbbc88

File tree

2 files changed

+127
-37
lines changed

2 files changed

+127
-37
lines changed

lib/image-reference.ts

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,64 @@ const imageReferenceRegex = new RegExp(imageReferencePattern);
1313
const imageRegistryPattern = String.raw`^((?:(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+|\[(?:[a-fA-F0-9:]+)\]|localhost)(?::[0-9]+)?)(?:/|@)`;
1414
const imageRegistryRegex = new RegExp(imageRegistryPattern);
1515

16-
export interface ParsedImageReference {
16+
export class ParsedImageReference {
1717
/** Repository path (e.g. nginx, library/nginx) */
18-
repository: string;
18+
readonly repository: string;
1919
/** Registry hostname (e.g. gcr.io, registry-1.docker.io); undefined if none */
20-
registry?: string;
20+
readonly registry?: string;
2121
/** Tag (e.g. latest, 1.23.0); undefined if only digest or neither */
22-
tag?: string;
22+
readonly tag?: string;
2323
/** Inline digest (e.g. sha256:abc...); undefined if not present */
24-
digest?: string;
24+
readonly digest?: string;
25+
26+
constructor(params: {
27+
repository: string;
28+
registry?: string;
29+
tag?: string;
30+
digest?: string;
31+
}) {
32+
this.repository = params.repository;
33+
this.registry = params.registry;
34+
this.tag = params.tag;
35+
this.digest = params.digest;
36+
}
37+
38+
/**
39+
* Rebuilds the image reference string from repository, registry, tag, and digest.
40+
* Format: [registry/]repository[:tag][@digest]
41+
*/
42+
toString(): string {
43+
let ref = "";
44+
if (this.registry) {
45+
ref += this.registry + "/";
46+
}
47+
ref += this.repository;
48+
if (this.tag) {
49+
ref += ":" + this.tag;
50+
}
51+
if (this.digest) {
52+
ref += "@" + this.digest;
53+
}
54+
return ref;
55+
}
56+
57+
/**
58+
* The registry to use for pulling the image.
59+
* If the registry is not set, use Docker Hub.
60+
*/
61+
get registryForPull(): string {
62+
return this.registry ?? "registry-1.docker.io";
63+
}
64+
65+
/**
66+
* The tail reference to use for pulling the image.
67+
* If the digest is set, use the digest.
68+
* If the tag is set, use the tag.
69+
* If neither are set, use "latest".
70+
*/
71+
get tailReferenceForPull(): string {
72+
return this.digest ?? this.tag ?? "latest";
73+
}
2574
}
2675

2776
/**
@@ -58,12 +107,12 @@ export function parseImageReference(reference: string): ParsedImageReference {
58107
repository = repository.slice(registry.length + 1);
59108
}
60109

61-
return {
110+
return new ParsedImageReference({
62111
repository,
63112
registry,
64113
tag: tag ?? undefined,
65114
digest: digest ?? undefined,
66-
};
115+
});
67116
}
68117

69118
/**

test/lib/image-reference.spec.ts

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
parseImageReference,
33
isValidImageReference,
4+
ParsedImageReference,
45
} from "../../lib/image-reference";
56

67
const validSha256 =
@@ -10,7 +11,7 @@ describe("image-reference", () => {
1011
describe("parseImageReference", () => {
1112
describe("valid image references", () => {
1213
it("parses image with tag only", () => {
13-
expect(parseImageReference("nginx:latest")).toEqual({
14+
expect(parseImageReference("nginx:latest")).toMatchObject({
1415
repository: "nginx",
1516
registry: undefined,
1617
tag: "latest",
@@ -19,7 +20,7 @@ describe("image-reference", () => {
1920
});
2021

2122
it("parses image with semantic tag", () => {
22-
expect(parseImageReference("nginx:1.23.0")).toEqual({
23+
expect(parseImageReference("nginx:1.23.0")).toMatchObject({
2324
repository: "nginx",
2425
registry: undefined,
2526
tag: "1.23.0",
@@ -28,7 +29,7 @@ describe("image-reference", () => {
2829
});
2930

3031
it("parses image without tag or digest (repository only)", () => {
31-
expect(parseImageReference("nginx")).toEqual({
32+
expect(parseImageReference("nginx")).toMatchObject({
3233
repository: "nginx",
3334
registry: undefined,
3435
tag: undefined,
@@ -37,7 +38,7 @@ describe("image-reference", () => {
3738
});
3839

3940
it("parses image with digest only", () => {
40-
expect(parseImageReference(`nginx@${validSha256}`)).toEqual({
41+
expect(parseImageReference(`nginx@${validSha256}`)).toMatchObject({
4142
repository: "nginx",
4243
registry: undefined,
4344
tag: undefined,
@@ -48,7 +49,7 @@ describe("image-reference", () => {
4849
it("parses image with tag and digest (name:tag@digest)", () => {
4950
expect(
5051
parseImageReference(`nginx:1.23.0@${validSha256}`),
51-
).toEqual({
52+
).toMatchObject({
5253
repository: "nginx",
5354
registry: undefined,
5455
tag: "1.23.0",
@@ -57,7 +58,9 @@ describe("image-reference", () => {
5758
});
5859

5960
it("parses image with registry (gcr.io)", () => {
60-
expect(parseImageReference("gcr.io/project/nginx:latest")).toEqual({
61+
expect(
62+
parseImageReference("gcr.io/project/nginx:latest"),
63+
).toMatchObject({
6164
repository: "project/nginx",
6265
registry: "gcr.io",
6366
tag: "latest",
@@ -68,7 +71,7 @@ describe("image-reference", () => {
6871
it("parses image with registry and digest", () => {
6972
expect(
7073
parseImageReference(`gcr.io/project/nginx:1.23.0@${validSha256}`),
71-
).toEqual({
74+
).toMatchObject({
7275
repository: "project/nginx",
7376
registry: "gcr.io",
7477
tag: "1.23.0",
@@ -79,7 +82,7 @@ describe("image-reference", () => {
7982
it("parses localhost registry with port", () => {
8083
expect(
8184
parseImageReference("localhost:5000/foo/bar:tag"),
82-
).toEqual({
85+
).toMatchObject({
8386
repository: "foo/bar",
8487
registry: "localhost:5000",
8588
tag: "tag",
@@ -90,7 +93,7 @@ describe("image-reference", () => {
9093
it("parses docker.io style registry", () => {
9194
expect(
9295
parseImageReference("docker.io/calico/cni:release-v3.14"),
93-
).toEqual({
96+
).toMatchObject({
9497
repository: "calico/cni",
9598
registry: "docker.io",
9699
tag: "release-v3.14",
@@ -99,7 +102,7 @@ describe("image-reference", () => {
99102
});
100103

101104
it("parses library/ prefix (Docker Hub official images)", () => {
102-
expect(parseImageReference("library/nginx:latest")).toEqual({
105+
expect(parseImageReference("library/nginx:latest")).toMatchObject({
103106
repository: "library/nginx",
104107
registry: undefined,
105108
tag: "latest",
@@ -108,16 +111,20 @@ describe("image-reference", () => {
108111
});
109112

110113
it("parses docker.io/library/ prefix (Docker Hub official images)", () => {
111-
expect(parseImageReference("docker.io/library/nginx:latest")).toEqual({
114+
expect(
115+
parseImageReference("registry-1.docker.io/library/nginx:latest"),
116+
).toMatchObject({
112117
repository: "library/nginx",
113-
registry: "docker.io",
118+
registry: "registry-1.docker.io",
114119
tag: "latest",
115120
digest: undefined,
116121
});
117122
});
118123

119124
it("parses repository with dots and dashes", () => {
120-
expect(parseImageReference("my.repo/image-name:tag")).toEqual({
125+
expect(
126+
parseImageReference("my.repo/image-name:tag"),
127+
).toMatchObject({
121128
repository: "image-name",
122129
registry: "my.repo",
123130
tag: "tag",
@@ -128,7 +135,7 @@ describe("image-reference", () => {
128135
it("parses IPv6 registry", () => {
129136
expect(
130137
parseImageReference("[::1]:5000/foo/bar:latest"),
131-
).toEqual({
138+
).toMatchObject({
132139
repository: "foo/bar",
133140
registry: "[::1]:5000",
134141
tag: "latest",
@@ -137,13 +144,21 @@ describe("image-reference", () => {
137144
});
138145

139146
it("parses tag with dots and dashes", () => {
140-
expect(parseImageReference("nginx:1.23.0-alpha")).toEqual({
147+
expect(
148+
parseImageReference("nginx:1.23.0-alpha"),
149+
).toMatchObject({
141150
repository: "nginx",
142151
registry: undefined,
143152
tag: "1.23.0-alpha",
144153
digest: undefined,
145154
});
146155
});
156+
157+
it("returns ParsedImageReference instance", () => {
158+
expect(parseImageReference("nginx:latest")).toBeInstanceOf(
159+
ParsedImageReference,
160+
);
161+
});
147162
});
148163

149164
describe("invalid image references", () => {
@@ -189,27 +204,53 @@ describe("image-reference", () => {
189204
});
190205
});
191206

192-
describe("isValidImageReference", () => {
193-
it("returns true for valid references", () => {
194-
expect(isValidImageReference("nginx:latest")).toBe(true);
195-
expect(isValidImageReference("nginx")).toBe(true);
196-
expect(
197-
isValidImageReference(`nginx:1.23.0@${validSha256}`),
198-
).toBe(true);
199-
expect(isValidImageReference("gcr.io/project/nginx:latest")).toBe(true);
207+
describe("ParsedImageReference#toString", () => {
208+
it("rebuilds reference with repository only", () => {
209+
const parsed = parseImageReference("nginx");
210+
expect(parsed.toString()).toBe("nginx");
211+
});
212+
213+
it("rebuilds reference with repository and tag", () => {
214+
const parsed = parseImageReference("nginx:latest");
215+
expect(parsed.toString()).toBe("nginx:latest");
216+
});
217+
218+
it("rebuilds reference with repository and digest", () => {
219+
const parsed = parseImageReference(`nginx@${validSha256}`);
220+
expect(parsed.toString()).toBe(`nginx@${validSha256}`);
221+
});
222+
223+
it("rebuilds reference with repository, tag and digest", () => {
224+
const ref = `nginx:1.23.0@${validSha256}`;
225+
const parsed = parseImageReference(ref);
226+
expect(parsed.toString()).toBe(ref);
200227
});
201228

202-
it("returns false for empty string", () => {
203-
expect(isValidImageReference("")).toBe(false);
229+
it("rebuilds reference with registry, repository and tag", () => {
230+
const parsed = parseImageReference("gcr.io/project/nginx:latest");
231+
expect(parsed.toString()).toBe("gcr.io/project/nginx:latest");
204232
});
205233

206-
it("returns false for invalid format", () => {
207-
expect(isValidImageReference(":tag")).toBe(false);
208-
expect(isValidImageReference("/invalid")).toBe(false);
234+
it("rebuilds reference with registry, repository, tag and digest", () => {
235+
const ref = `gcr.io/project/nginx:1.23.0@${validSha256}`;
236+
const parsed = parseImageReference(ref);
237+
expect(parsed.toString()).toBe(ref);
209238
});
210239

211-
it("returns false for uppercase in repository", () => {
212-
expect(isValidImageReference("UPPERCASE")).toBe(false);
240+
it("round-trips for various valid references", () => {
241+
const refs = [
242+
"nginx",
243+
"nginx:latest",
244+
"nginx:1.23.0",
245+
"library/nginx:latest",
246+
"localhost:5000/foo/bar:tag",
247+
"[::1]:5000/foo/bar:latest",
248+
`nginx@${validSha256}`,
249+
`nginx:1.23.0@${validSha256}`,
250+
];
251+
for (const ref of refs) {
252+
expect(parseImageReference(ref).toString()).toBe(ref);
253+
}
213254
});
214255
});
215256
});

0 commit comments

Comments
 (0)