Skip to content

Commit f56564f

Browse files
committed
Merge branch 'docker-registry' into master-test
2 parents dd464ef + 55765a4 commit f56564f

7 files changed

Lines changed: 178 additions & 0 deletions

File tree

src/argv.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,4 +337,8 @@ export class Argv {
337337
get childPipelineDepth (): number {
338338
return this.map.get("childPipelineDepth");
339339
}
340+
341+
get registry (): boolean {
342+
return this.map.get("registry") ?? false;
343+
}
340344
}

src/handler.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ export async function handler (args: any, writeStreams: WriteStreams, jobs: Job[
6464
Commander.runCsv(parser, writeStreams, argv.listCsvAll);
6565
} else if (argv.job.length > 0) {
6666
assert(argv.stage === null, "You cannot use --stage when starting individual jobs");
67+
if (argv.registry) {
68+
await Utils.startDockerRegistry(argv);
69+
}
6770
generateGitIgnore(cwd, stateDir);
6871
const time = process.hrtime();
6972
if (argv.needs || argv.onlyNeeds) {
@@ -77,6 +80,9 @@ export async function handler (args: any, writeStreams: WriteStreams, jobs: Job[
7780
writeStreams.stderr(chalk`{grey pipeline finished} in {grey ${prettyHrtime(process.hrtime(time))}}\n`);
7881
}
7982
} else if (argv.stage) {
83+
if (argv.registry) {
84+
await Utils.startDockerRegistry(argv);
85+
}
8086
generateGitIgnore(cwd, stateDir);
8187
const time = process.hrtime();
8288
const pipelineIid = await state.getPipelineIid(cwd, stateDir);
@@ -85,6 +91,9 @@ export async function handler (args: any, writeStreams: WriteStreams, jobs: Job[
8591
await Commander.runJobsInStage(argv, parser, writeStreams);
8692
writeStreams.stderr(chalk`{grey pipeline finished} in {grey ${prettyHrtime(process.hrtime(time))}}\n`);
8793
} else {
94+
if (argv.registry) {
95+
await Utils.startDockerRegistry(argv);
96+
}
8897
generateGitIgnore(cwd, stateDir);
8998
const time = process.hrtime();
9099
await state.incrementPipelineIid(cwd, stateDir);
@@ -96,5 +105,8 @@ export async function handler (args: any, writeStreams: WriteStreams, jobs: Job[
96105
}
97106
writeStreams.flush();
98107

108+
if (argv.registry) {
109+
await Utils.stopDockerRegistry(argv.containerExecutable);
110+
}
99111
return cleanupJobResources(jobs);
100112
}

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,11 @@ process.on("SIGUSR2", async () => await cleanupJobResources(jobs));
308308
default: true,
309309
description: "Enables color",
310310
})
311+
.option("registry", {
312+
type: "boolean",
313+
requiresArg: false,
314+
description: "Start a local docker registry and configure gitlab-ci-local containers to use that by default",
315+
})
311316
.completion("completion", false, (current: string, yargsArgv: any, completionFilter: any, done: (completions: string[]) => any) => {
312317
try {
313318
if (current.startsWith("-")) {

src/job.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,15 @@ export class Job {
893893
dockerCmd += `--network ${this._serviceNetworkId} --network-alias build `;
894894
}
895895

896+
if (this.argv.registry) {
897+
dockerCmd += `--network ${Utils.gclRegistryPrefix}.net `;
898+
dockerCmd += `--volume ${Utils.gclRegistryPrefix}.certs:/etc/containers/certs.d:ro `;
899+
dockerCmd += `--volume ${Utils.gclRegistryPrefix}.certs:/etc/docker/certs.d:ro `;
900+
expanded["CI_REGISTRY"] = Utils.gclRegistryPrefix;
901+
expanded["CI_REGISTRY_USER"] = expanded["CI_REGISTRY_USER"] ?? `${Utils.gclRegistryPrefix}.user`;
902+
expanded["CI_REGISTRY_PASSWORD"] = expanded["CI_REGISTRY_PASSWORD"] ?? `${Utils.gclRegistryPrefix}.password`;
903+
}
904+
896905
dockerCmd += `--volume ${buildVolumeName}:${this.ciProjectDir} `;
897906
dockerCmd += `--volume ${tmpVolumeName}:${this.fileVariablesDir} `;
898907
dockerCmd += `--workdir ${this.ciProjectDir} `;

src/utils.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,4 +442,81 @@ export class Utils {
442442
}
443443
return lsFilesRes.stdout.split("\n");
444444
}
445+
446+
static async dockerVolumeFileExists (containerExecutable: string, path: string, volume: string): Promise<boolean> {
447+
try {
448+
await Utils.spawn([containerExecutable, "run", "--rm", "-v", `${volume}:/mnt/vol`, "alpine", "ls", `/mnt/vol/${path}`]);
449+
return true;
450+
} catch {
451+
return false;
452+
}
453+
}
454+
455+
static gclRegistryPrefix: string = "registry.gcl.local";
456+
static async startDockerRegistry (argv: Argv): Promise<void> {
457+
const gclRegistryCertVol = `${this.gclRegistryPrefix}.certs`;
458+
const gclRegistryDataVol = `${this.gclRegistryPrefix}.data`;
459+
const gclRegistryNet = `${this.gclRegistryPrefix}.net`;
460+
461+
// create cert volume
462+
try {
463+
await Utils.spawn(`${argv.containerExecutable} volume create ${gclRegistryCertVol}`.split(" "));
464+
} catch (err) {
465+
if (err instanceof Error && !err.message.endsWith("already exists"))
466+
throw err;
467+
}
468+
469+
// create self-signed cert/key files for https support
470+
if (!await this.dockerVolumeFileExists(argv.containerExecutable, `${this.gclRegistryPrefix}.crt`, gclRegistryCertVol)) {
471+
const opensslArgs = [
472+
"req", "-newkey", "rsa:4096", "-nodes", "-sha256",
473+
"-keyout", `/certs/${this.gclRegistryPrefix}.key`,
474+
"-x509", "-days", "365",
475+
"-out", `/certs/${this.gclRegistryPrefix}.crt`,
476+
"-subj", `/CN=${this.gclRegistryPrefix}`,
477+
"-addext", `subjectAltName=DNS:${this.gclRegistryPrefix}`,
478+
];
479+
const generateCertsInPlace = [
480+
argv.containerExecutable, "run", "--rm", "-v", `${gclRegistryCertVol}:/certs`, "--entrypoint", "sh", "alpine/openssl", "-c",
481+
[
482+
"openssl", ...opensslArgs,
483+
"&&", "mkdir", "-p", `/certs/${this.gclRegistryPrefix}`,
484+
"&&", "cp", `/certs/${this.gclRegistryPrefix}.crt`, `/certs/${this.gclRegistryPrefix}/ca.crt`,
485+
].join(" "),
486+
];
487+
await Utils.spawn(generateCertsInPlace);
488+
}
489+
490+
// create data volume
491+
try {
492+
await Utils.spawn([argv.containerExecutable, "volume", "create", gclRegistryDataVol]);
493+
} catch (err) {
494+
if (err instanceof Error && !err.message.endsWith("already exists"))
495+
throw err;
496+
}
497+
498+
// create network
499+
try {
500+
await Utils.spawn([argv.containerExecutable, "network", "create", gclRegistryNet]);
501+
} catch (err) {
502+
if (err instanceof Error && !err.message.includes("already exists"))
503+
throw err;
504+
}
505+
506+
await Utils.spawn([argv.containerExecutable, "rm", "-f", this.gclRegistryPrefix]);
507+
await Utils.spawn([
508+
argv.containerExecutable, "run", "-d", "--name", this.gclRegistryPrefix,
509+
"--network", gclRegistryNet,
510+
"--volume", `${gclRegistryDataVol}:/var/lib/registry`,
511+
"--volume", `${gclRegistryCertVol}:/certs:ro`,
512+
"-e", "REGISTRY_HTTP_ADDR=0.0.0.0:443",
513+
"-e", `REGISTRY_HTTP_TLS_CERTIFICATE=/certs/${this.gclRegistryPrefix}.crt`,
514+
"-e", `REGISTRY_HTTP_TLS_KEY=/certs/${this.gclRegistryPrefix}.key`,
515+
"registry",
516+
]);
517+
}
518+
519+
static async stopDockerRegistry (containerExecutable: string): Promise<void> {
520+
await Utils.spawn([containerExecutable, "rm", "-f", this.gclRegistryPrefix]);
521+
}
445522
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
registry-variables:
3+
image: alpine:latest
4+
script:
5+
- echo "CI_REGISTRY=$CI_REGISTRY"
6+
- echo "CI_REGISTRY_USER=$CI_REGISTRY_USER"
7+
- echo "CI_REGISTRY_PASSWORD=$CI_REGISTRY_PASSWORD"
8+
9+
registry-login-docker:
10+
image: docker:dind
11+
script:
12+
- echo "$CI_REGISTRY_PASSWORD" | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
13+
14+
registry-login-oci:
15+
image: quay.io/podman/stable
16+
script:
17+
- echo "$CI_REGISTRY_PASSWORD" | podman login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import {WriteStreamsMock} from "../../../src/write-streams.js";
2+
import {handler} from "../../../src/handler.js";
3+
import {Utils} from "../../../src/utils.js";
4+
import chalk from "chalk";
5+
6+
test("local-registry ci variables", async () => {
7+
const writeStreams = new WriteStreamsMock;
8+
await handler({
9+
cwd: "tests/test-cases/local-registry",
10+
job: ["registry-variables"],
11+
registry: true,
12+
}, writeStreams);
13+
14+
const expected = [
15+
chalk`{blueBright registry-variables} {greenBright >} CI_REGISTRY=${Utils.gclRegistryPrefix}`,
16+
chalk`{blueBright registry-variables} {greenBright >} CI_REGISTRY_USER=${Utils.gclRegistryPrefix}.user`,
17+
chalk`{blueBright registry-variables} {greenBright >} CI_REGISTRY_PASSWORD=${Utils.gclRegistryPrefix}.password`,
18+
];
19+
20+
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
21+
});
22+
23+
test("local-registry login <docker>", async () => {
24+
const writeStreams = new WriteStreamsMock();
25+
await handler({
26+
cwd: "tests/test-cases/local-registry",
27+
job: ["registry-login-docker"],
28+
registry: true,
29+
}, writeStreams);
30+
31+
32+
const expected = [
33+
chalk`{blueBright registry-login-docker} {greenBright >} Login Succeeded`,
34+
];
35+
36+
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
37+
});
38+
39+
test("local-registry login <oci>", async () => {
40+
const writeStreams = new WriteStreamsMock();
41+
await handler({
42+
cwd: "tests/test-cases/local-registry",
43+
job: ["registry-login-oci"],
44+
registry: true,
45+
privileged: true,
46+
}, writeStreams);
47+
48+
49+
const expected = [
50+
chalk`{blueBright registry-login-oci} {greenBright >} Login Succeeded!`,
51+
];
52+
53+
expect(writeStreams.stdoutLines).toEqual(expect.arrayContaining(expected));
54+
});

0 commit comments

Comments
 (0)