Skip to content

Commit e56ec2b

Browse files
Fix parsing registry from env variable to work with trailing slash (#748)
1 parent 1337e9c commit e56ec2b

File tree

2 files changed

+153
-16
lines changed

2 files changed

+153
-16
lines changed

packages/testcontainers/src/container-runtime/image-name.test.ts

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,26 @@ describe("ContainerImage", () => {
5454
expect(imageName.string).toBe("aa285b773a2c042056883845aea893a743d358a5d40f61734fa228fde93dae6f:1");
5555
});
5656

57-
it("should substitute no registry with the one provided via TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX", () => {
58-
const oldEnvValue = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX;
59-
try {
60-
process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = "custom.com/registry";
61-
const imageName = new ImageName(undefined, "image", "tag");
62-
expect(imageName.string).toBe("custom.com/registry/image:tag");
63-
} finally {
64-
if (oldEnvValue === undefined) {
65-
delete process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX;
66-
} else {
67-
process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = oldEnvValue;
57+
it.each(["custom.com/registry", "custom.com/registry/"])(
58+
"should substitute no registry with the one provided via TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX when provided registry is %s",
59+
(customRegistry: string) => {
60+
const oldEnvValue = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX;
61+
try {
62+
process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = customRegistry;
63+
const imageName = new ImageName(undefined, "image", "tag");
64+
expect(imageName.registry).toBe("custom.com");
65+
expect(imageName.image).toBe("registry/image");
66+
expect(imageName.tag).toBe("tag");
67+
expect(imageName.string).toBe("custom.com/registry/image:tag");
68+
} finally {
69+
if (oldEnvValue === undefined) {
70+
delete process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX;
71+
} else {
72+
process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = oldEnvValue;
73+
}
6874
}
6975
}
70-
});
76+
);
7177
});
7278

7379
describe("fromString", () => {
@@ -156,4 +162,123 @@ describe("ContainerImage", () => {
156162
expect(imageName.tag).toBe("1");
157163
});
158164
});
165+
166+
describe.each([
167+
{ customRegistry: "custom.com/registry", expectedRegistry: "custom.com", expectedImagePrefix: "registry/" },
168+
{ customRegistry: "custom.com/registry/", expectedRegistry: "custom.com", expectedImagePrefix: "registry/" },
169+
{ customRegistry: "custom.com", expectedRegistry: "custom.com", expectedImagePrefix: "" },
170+
{
171+
customRegistry: "custom.com/registry/with/slashes",
172+
expectedRegistry: "custom.com",
173+
expectedImagePrefix: "registry/with/slashes/",
174+
},
175+
])(
176+
"fromString with TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX set to $customRegistry",
177+
({ customRegistry, expectedRegistry, expectedImagePrefix }) => {
178+
let oldEnvValue: string | undefined;
179+
beforeEach(() => {
180+
oldEnvValue = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX;
181+
process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = customRegistry;
182+
});
183+
184+
afterEach(() => {
185+
if (oldEnvValue === undefined) {
186+
delete process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX;
187+
} else {
188+
process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX = oldEnvValue;
189+
}
190+
});
191+
192+
it("should work", () => {
193+
const imageName = ImageName.fromString("image:latest");
194+
195+
expect(imageName.registry).toEqual(expectedRegistry);
196+
expect(imageName.image).toEqual(`${expectedImagePrefix}image`);
197+
expect(imageName.tag).toBe("latest");
198+
});
199+
200+
it("should work without tag", () => {
201+
const imageName = ImageName.fromString("image");
202+
203+
expect(imageName.registry).toEqual(expectedRegistry);
204+
expect(imageName.image).toEqual(`${expectedImagePrefix}image`);
205+
expect(imageName.tag).toBe("latest");
206+
});
207+
208+
it("should work with registry", () => {
209+
const imageName = ImageName.fromString("domain.com/image:latest");
210+
211+
expect(imageName.registry).toBe("domain.com");
212+
expect(imageName.image).toBe("image");
213+
expect(imageName.tag).toBe("latest");
214+
});
215+
216+
it("should work with registry with port", () => {
217+
const imageName = ImageName.fromString("domain.com:5000/image:latest");
218+
219+
expect(imageName.registry).toBe("domain.com:5000");
220+
expect(imageName.image).toBe("image");
221+
expect(imageName.tag).toBe("latest");
222+
});
223+
224+
it("should work with registry without tag", () => {
225+
const imageName = ImageName.fromString("domain.com/image");
226+
227+
expect(imageName.registry).toBe("domain.com");
228+
expect(imageName.image).toBe("image");
229+
expect(imageName.tag).toBe("latest");
230+
});
231+
232+
it("should work with nested image", () => {
233+
const imageName = ImageName.fromString("parent/child:latest");
234+
235+
expect(imageName.registry).toEqual(expectedRegistry);
236+
expect(imageName.image).toEqual(`${expectedImagePrefix}parent/child`);
237+
expect(imageName.tag).toBe("latest");
238+
});
239+
240+
it("should work with registry and nested image", () => {
241+
const imageName = ImageName.fromString("domain.com/parent/child:latest");
242+
243+
expect(imageName.registry).toBe("domain.com");
244+
expect(imageName.image).toBe("parent/child");
245+
expect(imageName.tag).toBe("latest");
246+
});
247+
248+
it("should work with tag being a hash", () => {
249+
const imageName = ImageName.fromString("image@sha256:1234abcd1234abcd1234abcd1234abcd");
250+
251+
expect(imageName.registry).toEqual(expectedRegistry);
252+
expect(imageName.image).toEqual(`${expectedImagePrefix}image`);
253+
expect(imageName.tag).toBe("sha256:1234abcd1234abcd1234abcd1234abcd");
254+
});
255+
256+
it("should work with image being an image ID", () => {
257+
const imageName = ImageName.fromString("aa285b773a2c042056883845aea893a743d358a5d40f61734fa228fde93dae6f");
258+
259+
expect(imageName.registry).toEqual(expectedRegistry);
260+
expect(imageName.image).toEqual(
261+
`${expectedImagePrefix}aa285b773a2c042056883845aea893a743d358a5d40f61734fa228fde93dae6f`
262+
);
263+
expect(imageName.tag).toBe("latest");
264+
});
265+
266+
it("should work with image being an image ID and an explicit tag", () => {
267+
// Note: Such an ID will not be accepted by docker:
268+
//
269+
// > "invalid repository name [...], cannot specify 64-byte hexadecimal strings"
270+
//
271+
// However, parsing it this way is probably least surprising.
272+
const imageName = ImageName.fromString("aa285b773a2c042056883845aea893a743d358a5d40f61734fa228fde93dae6f:1");
273+
274+
expect(imageName.registry).toEqual(expectedRegistry);
275+
expect(imageName.image).toEqual(
276+
`${expectedImagePrefix}aa285b773a2c042056883845aea893a743d358a5d40f61734fa228fde93dae6f`
277+
);
278+
expect(imageName.tag).toBe("1");
279+
});
280+
281+
// Add more tests here for different scenarios with prefix
282+
}
283+
);
159284
});

packages/testcontainers/src/container-runtime/image-name.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,23 @@ export class ImageName {
1010
public readonly image: string,
1111
public readonly tag: string
1212
) {
13-
if (!this.registry) {
14-
this.registry = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX;
15-
if (this.registry) {
16-
log.info(`Applying changes to image ${image} with tag ${tag}: added registry ${this.registry}`);
13+
if (!this.registry && process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX) {
14+
const prefix = process.env.TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX;
15+
16+
// Parse the registry. If it's undefined - then the whole prefix is a registry.
17+
const registry = ImageName.getRegistry(prefix);
18+
this.registry = registry ?? prefix;
19+
20+
// If the registry is defined, then the imagePrefix is the rest of the prefix.
21+
const imagePrefix = registry ? prefix.substring(prefix.indexOf("/") + 1).replace(/\/?$/, "/") : "";
22+
const originalImage = this.image;
23+
this.image = `${imagePrefix}${this.image}`;
24+
25+
let message = `Applying changes to image ${originalImage} with tag ${tag}: added registry ${this.registry}`;
26+
if (this.image !== originalImage) {
27+
message += ` and changed image to ${this.image}`;
1728
}
29+
log.info(message);
1830
}
1931
if (this.registry) {
2032
if (this.tag.startsWith("sha256:")) {

0 commit comments

Comments
 (0)