@foss.global/forgefixtures 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.smartconfig.json +49 -0
  2. package/changelog.md +17 -0
  3. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  4. package/dist_ts/00_commitinfo_data.js +9 -0
  5. package/dist_ts/classes.certificateauthority.d.ts +22 -0
  6. package/dist_ts/classes.certificateauthority.js +93 -0
  7. package/dist_ts/classes.containerlifecycle.d.ts +88 -0
  8. package/dist_ts/classes.containerlifecycle.js +383 -0
  9. package/dist_ts/classes.giteafixture.d.ts +41 -0
  10. package/dist_ts/classes.giteafixture.js +182 -0
  11. package/dist_ts/classes.giteaseed.d.ts +13 -0
  12. package/dist_ts/classes.giteaseed.js +432 -0
  13. package/dist_ts/classes.gitlabfixture.d.ts +49 -0
  14. package/dist_ts/classes.gitlabfixture.js +237 -0
  15. package/dist_ts/classes.gitlabseed.d.ts +13 -0
  16. package/dist_ts/classes.gitlabseed.js +466 -0
  17. package/dist_ts/classes.httpclient.d.ts +53 -0
  18. package/dist_ts/classes.httpclient.js +116 -0
  19. package/dist_ts/classes.reaper.d.ts +25 -0
  20. package/dist_ts/classes.reaper.js +102 -0
  21. package/dist_ts/classes.tlsterminator.d.ts +21 -0
  22. package/dist_ts/classes.tlsterminator.js +131 -0
  23. package/dist_ts/constants.d.ts +22 -0
  24. package/dist_ts/constants.js +30 -0
  25. package/dist_ts/giteaseed.default.d.ts +10 -0
  26. package/dist_ts/giteaseed.default.js +87 -0
  27. package/dist_ts/gitlabseed.default.d.ts +12 -0
  28. package/dist_ts/gitlabseed.default.js +84 -0
  29. package/dist_ts/index.d.ts +17 -0
  30. package/dist_ts/index.js +17 -0
  31. package/dist_ts/interfaces.d.ts +92 -0
  32. package/dist_ts/interfaces.giteaseed.d.ts +182 -0
  33. package/dist_ts/interfaces.giteaseed.js +2 -0
  34. package/dist_ts/interfaces.gitlabseed.d.ts +183 -0
  35. package/dist_ts/interfaces.gitlabseed.js +2 -0
  36. package/dist_ts/interfaces.js +2 -0
  37. package/dist_ts/ownership.d.ts +29 -0
  38. package/dist_ts/ownership.js +140 -0
  39. package/dist_ts/plugins.d.ts +13 -0
  40. package/dist_ts/plugins.js +18 -0
  41. package/dist_ts/responses.d.ts +7 -0
  42. package/dist_ts/responses.js +30 -0
  43. package/license.md +21 -0
  44. package/package.json +65 -0
  45. package/readme.md +206 -0
  46. package/ts/00_commitinfo_data.ts +8 -0
  47. package/ts/classes.certificateauthority.ts +117 -0
  48. package/ts/classes.containerlifecycle.ts +432 -0
  49. package/ts/classes.giteafixture.ts +205 -0
  50. package/ts/classes.giteaseed.ts +486 -0
  51. package/ts/classes.gitlabfixture.ts +258 -0
  52. package/ts/classes.gitlabseed.ts +502 -0
  53. package/ts/classes.httpclient.ts +156 -0
  54. package/ts/classes.reaper.ts +126 -0
  55. package/ts/classes.tlsterminator.ts +136 -0
  56. package/ts/constants.ts +35 -0
  57. package/ts/giteaseed.default.ts +88 -0
  58. package/ts/gitlabseed.default.ts +86 -0
  59. package/ts/index.ts +17 -0
  60. package/ts/interfaces.giteaseed.ts +130 -0
  61. package/ts/interfaces.gitlabseed.ts +135 -0
  62. package/ts/interfaces.ts +94 -0
  63. package/ts/ownership.ts +160 -0
  64. package/ts/plugins.ts +23 -0
  65. package/ts/responses.ts +33 -0
@@ -0,0 +1,237 @@
1
+ import { gitlabFixtureImage } from './constants.js';
2
+ import { ForgeFixtureContainerLifecycle } from './classes.containerlifecycle.js';
3
+ const httpPort = 80;
4
+ const adminUsername = 'root';
5
+ const usernamePattern = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,254}$/;
6
+ const tokenNamePattern = /^[A-Za-z0-9 _.-]{1,64}$/;
7
+ const scopePattern = /^[a-z_]{1,64}$/;
8
+ const tokenMarker = 'FORGEFIXTURES_TOKEN=';
9
+ const tokenPattern = /^[A-Za-z0-9_.-]{20,256}$/;
10
+ const railsRunnerTimeoutMs = 10 * 60 * 1000;
11
+ /**
12
+ * Written by the image's post-reconfigure hook (`GITLAB_POST_RECONFIGURE_SCRIPT`,
13
+ * evaluated by `/assets/init-container` once `gitlab-ctl reconfigure` returned).
14
+ * Before that, services are still being configured and restarted.
15
+ */
16
+ const reconfiguredMarker = '/run/forgefixtures-reconfigured';
17
+ const sleep = (msArg) => new Promise((resolveArg) => setTimeout(resolveArg, msArg));
18
+ /** Default rootless Docker socket of the current user. */
19
+ export const defaultRootlessDockerSocket = () => {
20
+ const runtimeDirectory = process.env.XDG_RUNTIME_DIR ?? `/run/user/${process.getuid?.() ?? 0}`;
21
+ return `unix://${runtimeDirectory}/docker.sock`;
22
+ };
23
+ /**
24
+ * Memory-constrained omnibus configuration from GitLab's documentation for
25
+ * small instances, behind this package's TLS-terminating proxy.
26
+ */
27
+ const omnibusConfiguration = (baseUrlArg) => [
28
+ `external_url '${baseUrlArg}'`,
29
+ `nginx['listen_port'] = ${httpPort}`,
30
+ `nginx['listen_https'] = false`,
31
+ `letsencrypt['enable'] = false`,
32
+ `puma['worker_processes'] = 0`,
33
+ `sidekiq['concurrency'] = 10`,
34
+ `prometheus_monitoring['enable'] = false`,
35
+ `gitlab_kas['enable'] = false`,
36
+ `gitlab_rails['usage_ping_enabled'] = false`,
37
+ `gitlab_rails['env'] = { 'MALLOC_CONF' => 'dirty_decay_ms:1000,muzzy_decay_ms:1000' }`,
38
+ `gitaly['env'] = { 'MALLOC_CONF' => 'dirty_decay_ms:1000,muzzy_decay_ms:1000', 'GITALY_COMMAND_SPAWN_MAX_PARALLEL' => '2' }`,
39
+ ].join('\n');
40
+ /**
41
+ * A disposable self-managed GitLab CE from the pinned omnibus image, served over
42
+ * verified loopback TLS. The omnibus image must run as root, which this package
43
+ * permits only on a verified rootless Docker daemon.
44
+ */
45
+ export class GitlabFixture {
46
+ #options;
47
+ #lifecycle;
48
+ #runtime;
49
+ #starting;
50
+ constructor(optionsArg = {}) {
51
+ this.#options = { ...optionsArg };
52
+ this.#lifecycle = new ForgeFixtureContainerLifecycle('gitlab', {
53
+ ...optionsArg,
54
+ dockerSocketPath: optionsArg.dockerSocketPath ?? defaultRootlessDockerSocket(),
55
+ }, 12 * 60 * 1000);
56
+ }
57
+ get runtime() {
58
+ if (!this.#runtime)
59
+ throw new Error('The GitLab fixture is not running.');
60
+ return this.#runtime;
61
+ }
62
+ /** HTTPS client scoped to this fixture's origin and CA. */
63
+ get http() {
64
+ return this.#lifecycle.http;
65
+ }
66
+ /** Report of the stale-fixture reaper run that preceded this start. */
67
+ get reapReport() {
68
+ return this.#lifecycle.lastReapReport;
69
+ }
70
+ /**
71
+ * Starts the fixture and returns once the container is healthy, an
72
+ * administrator token exists and GitLab reports the pinned version.
73
+ * A fixture starts at most once.
74
+ */
75
+ start() {
76
+ if (this.#starting)
77
+ return Promise.reject(new Error('The GitLab fixture was already started.'));
78
+ this.#starting = this.#start();
79
+ return this.#starting;
80
+ }
81
+ async #start() {
82
+ const endpoint = await this.#lifecycle.prepare();
83
+ try {
84
+ await this.#lifecycle.startContainer({
85
+ image: gitlabFixtureImage,
86
+ user: 'root',
87
+ allowRootOnRootless: true,
88
+ command: ['/assets/init-container'],
89
+ env: {
90
+ GITLAB_OMNIBUS_CONFIG: omnibusConfiguration(endpoint.baseUrl),
91
+ GITLAB_POST_RECONFIGURE_SCRIPT: `touch ${reconfiguredMarker}`,
92
+ },
93
+ httpPort,
94
+ volumes: [
95
+ { purpose: 'config', target: '/etc/gitlab' },
96
+ { purpose: 'data', target: '/var/opt/gitlab' },
97
+ { purpose: 'logs', target: '/var/log/gitlab' },
98
+ ],
99
+ memoryBytes: this.#options.memoryBytes ?? 5 * 1024 * 1024 * 1024,
100
+ nanoCpus: this.#options.nanoCpus ?? 4_000_000_000,
101
+ pidsLimit: 4096,
102
+ shmSizeBytes: 256 * 1024 * 1024,
103
+ stopTimeoutSeconds: 30,
104
+ // The image's own HTTP check, gated on the finished reconfigure: before reconfigure
105
+ // renders its target, `gitlab-healthcheck` succeeds without checking anything.
106
+ healthcheck: {
107
+ test: ['CMD-SHELL', `test -e ${reconfiguredMarker} && exec /opt/gitlab/bin/gitlab-healthcheck --fail --max-time 10`],
108
+ intervalMs: 10_000,
109
+ timeoutMs: 30_000,
110
+ startPeriodMs: this.#lifecycle.startupTimeoutMs,
111
+ startIntervalMs: 5_000,
112
+ retries: 5,
113
+ },
114
+ });
115
+ await this.#waitForReady(Date.now() + this.#lifecycle.startupTimeoutMs);
116
+ const token = await this.createAccessToken({
117
+ username: adminUsername, tokenName: 'forgefixtures-admin', scopes: ['api', 'sudo'],
118
+ });
119
+ this.#lifecycle.assertNotStopping();
120
+ const version = await this.#readVersion(token);
121
+ if (version !== gitlabFixtureImage.version) {
122
+ throw new Error(`GitLab reports version ${version}; the pinned image is ${gitlabFixtureImage.version}.`);
123
+ }
124
+ this.#lifecycle.assertNotStopping();
125
+ this.#runtime = {
126
+ kind: 'gitlab',
127
+ lifecycleId: this.#lifecycle.lifecycleId,
128
+ baseUrl: endpoint.baseUrl,
129
+ caCertificatePem: endpoint.caCertificatePem,
130
+ version,
131
+ image: gitlabFixtureImage,
132
+ admin: { username: adminUsername, token },
133
+ };
134
+ return this.#runtime;
135
+ }
136
+ catch (error) {
137
+ const reported = this.#lifecycle.stopAwareError(error);
138
+ try {
139
+ await this.#lifecycle.stop();
140
+ }
141
+ catch (cleanupError) {
142
+ throw new AggregateError([reported, cleanupError], 'The GitLab fixture failed to start and to clean up.');
143
+ }
144
+ throw reported;
145
+ }
146
+ }
147
+ /**
148
+ * Creates a personal access token with GitLab's documented programmatic
149
+ * method (`gitlab-rails runner`). The script travels on stdin, so neither it
150
+ * nor the token appears in process arguments; the token is returned, never logged.
151
+ */
152
+ async createAccessToken(requestArg) {
153
+ if (!usernamePattern.test(requestArg.username) || !tokenNamePattern.test(requestArg.tokenName)
154
+ || requestArg.scopes.length === 0 || !requestArg.scopes.every((scopeArg) => scopePattern.test(scopeArg))) {
155
+ throw new TypeError('The access token request is malformed.');
156
+ }
157
+ const script = [
158
+ `user = User.find_by_username!(${JSON.stringify(requestArg.username)})`,
159
+ `token = user.personal_access_tokens.create!(name: ${JSON.stringify(requestArg.tokenName)}, `
160
+ + `scopes: ${JSON.stringify(requestArg.scopes)}, expires_at: 2.days.from_now)`,
161
+ `puts "${tokenMarker}#{token.token}"`,
162
+ ].join('\n');
163
+ const result = await this.#lifecycle.exec(['/opt/gitlab/bin/gitlab-rails', 'runner', '-'], {
164
+ stdin: script, timeoutMs: railsRunnerTimeoutMs,
165
+ });
166
+ if (result.exitCode !== 0) {
167
+ // A failed run printed no token; its output carries Rails' error report.
168
+ const diagnostics = `${result.stderr}\n${result.stdout}`.split('\n')
169
+ .filter((lineArg) => !lineArg.startsWith(tokenMarker)).join('\n').trim().slice(-2048);
170
+ throw new Error(`gitlab-rails runner failed to create a token for ${requestArg.username} (exit ${result.exitCode}): ${diagnostics}`);
171
+ }
172
+ const line = result.stdout.split('\n').find((lineArg) => lineArg.startsWith(tokenMarker));
173
+ const token = line?.slice(tokenMarker.length).trim();
174
+ if (!token || !tokenPattern.test(token))
175
+ throw new Error('gitlab-rails runner returned no access token in the expected format.');
176
+ return token;
177
+ }
178
+ /**
179
+ * Removes every resource of this fixture. A start in progress is cancelled at
180
+ * its next checkpoint and awaited first, so nothing it created survives.
181
+ * Idempotent; retries incomplete cleanup.
182
+ */
183
+ async stop() {
184
+ this.#runtime = undefined;
185
+ this.#lifecycle.requestStop();
186
+ const starting = this.#starting;
187
+ if (starting)
188
+ await starting.catch(() => undefined);
189
+ await this.#lifecycle.stop();
190
+ }
191
+ /**
192
+ * Waits until Docker reports the container healthy, which requires the
193
+ * finished reconfigure and a passing image HTTP check, and until the Rails
194
+ * API answers behind the proxy: an unauthenticated `/api/v4/version`
195
+ * answering 401 proves that nginx, Workhorse and Puma serve the API. Docker
196
+ * counts failed checks only after the first success, so `unhealthy` means
197
+ * GitLab broke after it was up.
198
+ */
199
+ async #waitForReady(deadlineArg) {
200
+ let health = 'none';
201
+ let api = 'not probed';
202
+ while (Date.now() < deadlineArg) {
203
+ this.#lifecycle.assertNotStopping();
204
+ await this.#lifecycle.assertContainerRunning();
205
+ health = await this.#lifecycle.containerHealth();
206
+ if (health === 'unhealthy')
207
+ break;
208
+ if (health === 'healthy') {
209
+ try {
210
+ const response = await this.#lifecycle.http.request({
211
+ method: 'GET', url: '/api/v4/version', expectedStatus: [401, 502, 503], timeoutMs: 10_000,
212
+ });
213
+ if (response.status === 401)
214
+ return;
215
+ api = `status ${response.status}`;
216
+ }
217
+ catch (error) {
218
+ api = error instanceof Error ? error.message : String(error);
219
+ }
220
+ }
221
+ await sleep(5_000);
222
+ }
223
+ this.#lifecycle.assertNotStopping();
224
+ const reason = health === 'unhealthy' ? 'became unhealthy' : `did not become ready within ${this.#lifecycle.startupTimeoutMs} ms`;
225
+ throw new Error(`GitLab ${reason} (health ${health}, API ${api}). Last logs:\n${await this.#lifecycle.logsTail(60)}`);
226
+ }
227
+ async #readVersion(tokenArg) {
228
+ const body = await this.#lifecycle.http.requestJson({
229
+ method: 'GET', url: '/api/v4/version', headers: { 'private-token': tokenArg }, expectedStatus: [200],
230
+ });
231
+ const version = typeof body === 'object' && body !== null ? Reflect.get(body, 'version') : undefined;
232
+ if (typeof version !== 'string')
233
+ throw new Error('GitLab answered /api/v4/version without a version.');
234
+ return version;
235
+ }
236
+ }
237
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5naXRsYWJmaXh0dXJlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5naXRsYWJmaXh0dXJlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3BELE9BQU8sRUFBRSw4QkFBOEIsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBbUJqRixNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUM7QUFDcEIsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFDO0FBQzdCLE1BQU0sZUFBZSxHQUFHLG9DQUFvQyxDQUFDO0FBQzdELE1BQU0sZ0JBQWdCLEdBQUcseUJBQXlCLENBQUM7QUFDbkQsTUFBTSxZQUFZLEdBQUcsZ0JBQWdCLENBQUM7QUFDdEMsTUFBTSxXQUFXLEdBQUcsc0JBQXNCLENBQUM7QUFDM0MsTUFBTSxZQUFZLEdBQUcsMEJBQTBCLENBQUM7QUFDaEQsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUM1Qzs7OztHQUlHO0FBQ0gsTUFBTSxrQkFBa0IsR0FBRyxpQ0FBaUMsQ0FBQztBQUU3RCxNQUFNLEtBQUssR0FBRyxDQUFDLEtBQWEsRUFBRSxFQUFFLENBQUMsSUFBSSxPQUFPLENBQU8sQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUVsRywwREFBMEQ7QUFDMUQsTUFBTSxDQUFDLE1BQU0sMkJBQTJCLEdBQUcsR0FBVyxFQUFFO0lBQ3RELE1BQU0sZ0JBQWdCLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLElBQUksYUFBYSxPQUFPLENBQUMsTUFBTSxFQUFFLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQztJQUMvRixPQUFPLFVBQVUsZ0JBQWdCLGNBQWMsQ0FBQztBQUNsRCxDQUFDLENBQUM7QUFFRjs7O0dBR0c7QUFD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
@@ -0,0 +1,13 @@
1
+ import type { GitlabFixture } from './classes.gitlabfixture.js';
2
+ import type { IGitlabSeedManifest, IGitlabSeedSpec } from './interfaces.gitlabseed.js';
3
+ /** Rejects inconsistent specs before the first mutation. */
4
+ export declare const validateGitlabSeedSpec: (specArg: IGitlabSeedSpec) => void;
5
+ /**
6
+ * Seeds a running {@link GitlabFixture} through GitLab's documented REST API,
7
+ * then reads everything back into a ground-truth manifest.
8
+ */
9
+ export declare class GitlabSeedBuilder {
10
+ #private;
11
+ constructor(fixtureArg: GitlabFixture);
12
+ apply(specArg: IGitlabSeedSpec): Promise<IGitlabSeedManifest>;
13
+ }