Skip to content

Commit 9bb356c

Browse files
authored
fix: strip ports and digests from service aliases (#1820)
1 parent ce66039 commit 9bb356c

5 files changed

Lines changed: 142 additions & 21 deletions

File tree

src/job.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ interface Cache {
4646
when: "on_success" | "on_failure" | "always";
4747
}
4848

49-
interface Service {
49+
export interface Service {
5050
name: string;
5151
entrypoint: string[] | null;
5252
command: string[] | null;
@@ -1583,15 +1583,8 @@ If you know what you're doing and would like to suppress this warning, use one o
15831583
}
15841584
}
15851585

1586-
const serviceAlias = service.alias;
15871586
const serviceName = service.name;
1588-
const serviceNameWithoutVersion = serviceName.replace(/(.*)(:.*)/, "$1");
1589-
const aliases = new Set<string>();
1590-
aliases.add(serviceNameWithoutVersion.replaceAll("/", "-"));
1591-
aliases.add(serviceNameWithoutVersion.replaceAll("/", "__"));
1592-
if (serviceAlias) {
1593-
aliases.add(serviceAlias);
1594-
}
1587+
const aliases = Utils.getAllServiceAliases(service);
15951588

15961589
for (const [key, val] of Object.entries(expanded)) {
15971590
// Replacing `'` with `'\''` to correctly handle single quotes(if `val` contains `'`) in shell commands
@@ -1642,23 +1635,14 @@ If you know what you're doing and would like to suppress this warning, use one o
16421635
}
16431636

16441637
private async serviceHealthCheck (writeStreams: WriteStreams, service: Service, serviceIndex: number, serviceContainerLogFile: string) {
1645-
const serviceAlias = service.alias;
1638+
const serviceAlias = Utils.getServiceAlias(service);
16461639
const serviceName = service.name;
16471640
const waitImageName = this.argv.waitImage;
16481641
const waitForServicesTimeout = this.argv.waitForServicesTimeout;
16491642

16501643
const {stdout} = await Utils.spawn([this.argv.containerExecutable, "image", "inspect", serviceName]);
16511644
const imageInspect = JSON.parse(stdout);
16521645

1653-
// Copied from the startService block. Important thing is that the aliases match
1654-
const serviceNameWithoutVersion = serviceName.replace(/(.*)(:.*)/, "$1");
1655-
const aliases = [serviceNameWithoutVersion.replaceAll("/", "-"), serviceNameWithoutVersion.replaceAll("/", "__")];
1656-
if (serviceAlias) {
1657-
aliases.push(serviceAlias);
1658-
}
1659-
1660-
const uniqueAlias = aliases[aliases.length - 1];
1661-
16621646
if ((imageInspect[0]?.Config?.ExposedPorts ?? null) === null) {
16631647
return writeStreams.stderr(chalk`${this.formattedJobName} {yellow Could not find exposed tcp ports ${serviceName}}\n`);
16641648
}
@@ -1670,7 +1654,7 @@ If you know what you're doing and would like to suppress this warning, use one o
16701654
if (!port.endsWith("/tcp")) return;
16711655
const portNum = parseInt(port.replace("/tcp", ""));
16721656
const containerName = `gcl-wait-for-it-${this.jobId}-${serviceIndex}-${portNum}`;
1673-
const spawnCmd = [this.argv.containerExecutable, "run", "--rm", `--name=${containerName}`, "--network", `${this._serviceNetworkId}`, `${waitImageName}`, `${uniqueAlias}:${portNum}`, "-t", `${waitForServicesTimeout}`];
1657+
const spawnCmd = [this.argv.containerExecutable, "run", "--rm", `--name=${containerName}`, "--network", `${this._serviceNetworkId}`, `${waitImageName}`, `${serviceAlias}:${portNum}`, "-t", `${waitForServicesTimeout}`];
16741658
this._containersToClean.push(containerName);
16751659
return Utils.spawn(spawnCmd);
16761660
}));

src/utils.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import "./global.js";
22
import {RE2JS} from "re2js";
33
import chalk from "chalk-template";
4-
import {Job, JobRule, Need} from "./job.js";
4+
import {Job, JobRule, Need, Service} from "./job.js";
55
import {needsComplex} from "./data-expander.js";
66
import fs from "fs-extra";
77
import checksum from "checksum";
@@ -529,4 +529,26 @@ export class Utils {
529529
return String(variable);
530530
}
531531
}
532+
533+
static getAllServiceAliases (service: Service): Set<string> {
534+
const aliases = new Set<string>();
535+
536+
if (service.alias) {
537+
aliases.add(service.alias);
538+
}
539+
540+
// Strip any port (:443), tag (:1.2.3), or digest (@sha256:...) suffix from each path segment
541+
const serviceNameWithoutVersionAndPort = service.name.replaceAll(/[:@][^/]*/g, "");
542+
aliases.add(serviceNameWithoutVersionAndPort.replaceAll("/", "-"));
543+
aliases.add(serviceNameWithoutVersionAndPort.replaceAll("/", "__"));
544+
545+
return aliases;
546+
}
547+
548+
static getServiceAlias (service: Service): string {
549+
const aliases = Utils.getAllServiceAliases(service);
550+
551+
// Return the first alias in the set
552+
return aliases.values().next().value!;
553+
}
532554
}

tests/test-cases/services/.gitlab-ci.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,15 @@ service-user:
108108
image: docker.io/library/alpine
109109
script:
110110
- echo "bye"
111+
112+
registry-port:
113+
services:
114+
- registry-1.docker.io:443/library/nginx:1.29.7
115+
image: docker.io/curlimages/curl:8.18.0
116+
script: curl -sS http://registry-1.docker.io-library-nginx/
117+
118+
image-digest:
119+
services:
120+
- docker.io/library/nginx@sha256:e7257f1ef28ba17cf7c248cb8ccf6f0c6e0228ab9c315c152f9c203cd34cf6d1
121+
image: docker.io/curlimages/curl:8.18.0
122+
script: curl -sS http://docker.io-library-nginx/

tests/test-cases/services/integration.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,33 @@ test.concurrent("services <unnamed services should be ignored>", async () => {
206206
stateDir: ".gitlab-ci-local-unnamed",
207207
}, writeStreams);
208208
});
209+
210+
test.concurrent("services <registry-port>", async () => {
211+
const writeStreams = new WriteStreamsMock();
212+
await handler({
213+
cwd: "tests/test-cases/services",
214+
job: ["registry-port"],
215+
stateDir: ".gitlab-ci-local-registry-port",
216+
}, writeStreams);
217+
218+
const expected = [
219+
chalk`{blueBright registry-port} {greenBright >} <title>Welcome to nginx!</title>`,
220+
chalk`{black.bgGreenBright PASS } {blueBright registry-port}`,
221+
];
222+
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
223+
});
224+
225+
test.concurrent("services <image-digest>", async () => {
226+
const writeStreams = new WriteStreamsMock();
227+
await handler({
228+
cwd: "tests/test-cases/services",
229+
job: ["image-digest"],
230+
stateDir: ".gitlab-ci-local-image-digest",
231+
}, writeStreams);
232+
233+
const expected = [
234+
chalk`{blueBright image-digest} {greenBright >} <title>Welcome to nginx!</title>`,
235+
chalk`{black.bgGreenBright PASS } {blueBright image-digest}`,
236+
];
237+
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
238+
});

tests/utils.test.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,76 @@ describe("isSubPath where process.cwd() have been mocked to return /home/user/gi
193193
});
194194
});
195195
});
196+
197+
describe("getAllServiceAliases", () => {
198+
const tests = [
199+
{
200+
input: "nginx",
201+
expected: ["nginx"],
202+
},
203+
{
204+
input: "library/nginx",
205+
expected: ["library-nginx", "library__nginx"],
206+
},
207+
{
208+
input: "docker.io/library/nginx",
209+
expected: ["docker.io-library-nginx", "docker.io__library__nginx"],
210+
},
211+
{
212+
input: "registry-1.docker.io/library/nginx",
213+
expected: ["registry-1.docker.io-library-nginx", "registry-1.docker.io__library__nginx"],
214+
},
215+
{
216+
input: "registry-1.docker.io:443/library/nginx",
217+
expected: ["registry-1.docker.io-library-nginx", "registry-1.docker.io__library__nginx"],
218+
},
219+
];
220+
221+
const suffixes = [
222+
"",
223+
":1.29.7",
224+
":1.29.7@sha256:e7257f1ef28ba17cf7c248cb8ccf6f0c6e0228ab9c315c152f9c203cd34cf6d1",
225+
"@sha256:e7257f1ef28ba17cf7c248cb8ccf6f0c6e0228ab9c315c152f9c203cd34cf6d1",
226+
];
227+
228+
tests.forEach(({input, expected}) => {
229+
suffixes.forEach((suffix) => {
230+
const serviceName = `${input}${suffix}`;
231+
test.concurrent(`${serviceName}`, () => {
232+
const service = {
233+
name: serviceName,
234+
entrypoint: null,
235+
command: null,
236+
alias: null,
237+
variables: {},
238+
};
239+
const aliases = Utils.getAllServiceAliases(service);
240+
expect([...aliases]).toEqual(expected);
241+
});
242+
});
243+
});
244+
245+
test.concurrent("should include custom alias when provided", () => {
246+
const service = {
247+
name: "docker.io/library/nginx:1.29.7",
248+
entrypoint: null,
249+
command: null,
250+
alias: "my-nginx",
251+
variables: {},
252+
};
253+
const aliases = Utils.getAllServiceAliases(service);
254+
expect([...aliases]).toEqual(["my-nginx", "docker.io-library-nginx", "docker.io__library__nginx"]);
255+
});
256+
});
257+
258+
describe("getServiceAlias", () => {
259+
const base = {entrypoint: null, command: null, variables: {}};
260+
261+
test.concurrent("returns - variant when no custom alias", () => {
262+
expect(Utils.getServiceAlias({...base, name: "library/nginx", alias: null})).toBe("library-nginx");
263+
});
264+
265+
test.concurrent("returns custom alias when provided", () => {
266+
expect(Utils.getServiceAlias({...base, name: "library/nginx", alias: "my-nginx"})).toBe("my-nginx");
267+
});
268+
});

0 commit comments

Comments
 (0)