@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.
- package/.smartconfig.json +49 -0
- package/changelog.md +17 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.certificateauthority.d.ts +22 -0
- package/dist_ts/classes.certificateauthority.js +93 -0
- package/dist_ts/classes.containerlifecycle.d.ts +88 -0
- package/dist_ts/classes.containerlifecycle.js +383 -0
- package/dist_ts/classes.giteafixture.d.ts +41 -0
- package/dist_ts/classes.giteafixture.js +182 -0
- package/dist_ts/classes.giteaseed.d.ts +13 -0
- package/dist_ts/classes.giteaseed.js +432 -0
- package/dist_ts/classes.gitlabfixture.d.ts +49 -0
- package/dist_ts/classes.gitlabfixture.js +237 -0
- package/dist_ts/classes.gitlabseed.d.ts +13 -0
- package/dist_ts/classes.gitlabseed.js +466 -0
- package/dist_ts/classes.httpclient.d.ts +53 -0
- package/dist_ts/classes.httpclient.js +116 -0
- package/dist_ts/classes.reaper.d.ts +25 -0
- package/dist_ts/classes.reaper.js +102 -0
- package/dist_ts/classes.tlsterminator.d.ts +21 -0
- package/dist_ts/classes.tlsterminator.js +131 -0
- package/dist_ts/constants.d.ts +22 -0
- package/dist_ts/constants.js +30 -0
- package/dist_ts/giteaseed.default.d.ts +10 -0
- package/dist_ts/giteaseed.default.js +87 -0
- package/dist_ts/gitlabseed.default.d.ts +12 -0
- package/dist_ts/gitlabseed.default.js +84 -0
- package/dist_ts/index.d.ts +17 -0
- package/dist_ts/index.js +17 -0
- package/dist_ts/interfaces.d.ts +92 -0
- package/dist_ts/interfaces.giteaseed.d.ts +182 -0
- package/dist_ts/interfaces.giteaseed.js +2 -0
- package/dist_ts/interfaces.gitlabseed.d.ts +183 -0
- package/dist_ts/interfaces.gitlabseed.js +2 -0
- package/dist_ts/interfaces.js +2 -0
- package/dist_ts/ownership.d.ts +29 -0
- package/dist_ts/ownership.js +140 -0
- package/dist_ts/plugins.d.ts +13 -0
- package/dist_ts/plugins.js +18 -0
- package/dist_ts/responses.d.ts +7 -0
- package/dist_ts/responses.js +30 -0
- package/license.md +21 -0
- package/package.json +65 -0
- package/readme.md +206 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.certificateauthority.ts +117 -0
- package/ts/classes.containerlifecycle.ts +432 -0
- package/ts/classes.giteafixture.ts +205 -0
- package/ts/classes.giteaseed.ts +486 -0
- package/ts/classes.gitlabfixture.ts +258 -0
- package/ts/classes.gitlabseed.ts +502 -0
- package/ts/classes.httpclient.ts +156 -0
- package/ts/classes.reaper.ts +126 -0
- package/ts/classes.tlsterminator.ts +136 -0
- package/ts/constants.ts +35 -0
- package/ts/giteaseed.default.ts +88 -0
- package/ts/gitlabseed.default.ts +86 -0
- package/ts/index.ts +17 -0
- package/ts/interfaces.giteaseed.ts +130 -0
- package/ts/interfaces.gitlabseed.ts +135 -0
- package/ts/interfaces.ts +94 -0
- package/ts/ownership.ts +160 -0
- package/ts/plugins.ts +23 -0
- package/ts/responses.ts +33 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { defaultMaxLifetimeMs, defaultPullTimeoutMs } from './constants.js';
|
|
3
|
+
import { ForgeFixtureCertificateAuthority } from './classes.certificateauthority.js';
|
|
4
|
+
import { ForgeFixtureHttpClient } from './classes.httpclient.js';
|
|
5
|
+
import { createForgeFixtureDockerHost, reapStaleForgeFixtures } from './classes.reaper.js';
|
|
6
|
+
import { ForgeFixtureTlsTerminator } from './classes.tlsterminator.js';
|
|
7
|
+
import { createForgeFixtureOwner, forgeFixtureOwnerLabels } from './ownership.js';
|
|
8
|
+
const maxPortAttempts = 5;
|
|
9
|
+
/** Asks the kernel for a currently free loopback port. The caller must tolerate losing a race for it. */
|
|
10
|
+
const reserveLoopbackPort = async () => {
|
|
11
|
+
const server = plugins.net.createServer();
|
|
12
|
+
await new Promise((resolveArg, rejectArg) => {
|
|
13
|
+
server.once('error', rejectArg);
|
|
14
|
+
server.listen({ host: '127.0.0.1', port: 0, exclusive: true }, () => resolveArg());
|
|
15
|
+
});
|
|
16
|
+
const address = server.address();
|
|
17
|
+
await new Promise((resolveArg) => server.close(() => resolveArg()));
|
|
18
|
+
if (address === null || typeof address === 'string')
|
|
19
|
+
throw new Error('The loopback port probe has no TCP address.');
|
|
20
|
+
return address.port;
|
|
21
|
+
};
|
|
22
|
+
const isPortConflict = (errorArg) => errorArg instanceof Error && /address already in use|port is already allocated/.test(errorArg.message);
|
|
23
|
+
/** Raised by lifecycle steps that observe a stop request. */
|
|
24
|
+
export class ForgeFixtureStoppedError extends Error {
|
|
25
|
+
constructor(kindArg, optionsArg) {
|
|
26
|
+
super(`The ${kindArg} fixture was stopped.`, optionsArg);
|
|
27
|
+
this.name = 'ForgeFixtureStoppedError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Owns one fixture's Docker resources, local CA, TLS terminator and scoped
|
|
32
|
+
* HTTPS client. Every resource carries ownership labels, so resources left by a
|
|
33
|
+
* crashed process are removed by the next lifecycle's reaper run.
|
|
34
|
+
*/
|
|
35
|
+
export class ForgeFixtureContainerLifecycle {
|
|
36
|
+
#kind;
|
|
37
|
+
#options;
|
|
38
|
+
#state = 'idle';
|
|
39
|
+
#dockerHost;
|
|
40
|
+
#owner;
|
|
41
|
+
#terminator;
|
|
42
|
+
#http;
|
|
43
|
+
#endpoint;
|
|
44
|
+
#container;
|
|
45
|
+
#network;
|
|
46
|
+
#volumes = [];
|
|
47
|
+
#lastReapReport;
|
|
48
|
+
#stopController = new AbortController();
|
|
49
|
+
/** The prepare or container start currently running; `stop()` waits for it before cleaning up. */
|
|
50
|
+
#inflight;
|
|
51
|
+
/** Serialises cleanup runs so concurrent stops never remove the same resource twice. */
|
|
52
|
+
#cleanupQueue = Promise.resolve();
|
|
53
|
+
constructor(kindArg, optionsArg, startupTimeoutMsArg) {
|
|
54
|
+
this.#kind = kindArg;
|
|
55
|
+
this.#options = {
|
|
56
|
+
...(optionsArg.dockerSocketPath === undefined ? {} : { dockerSocketPath: optionsArg.dockerSocketPath }),
|
|
57
|
+
maxLifetimeMs: optionsArg.maxLifetimeMs ?? defaultMaxLifetimeMs,
|
|
58
|
+
pullTimeoutMs: optionsArg.pullTimeoutMs ?? defaultPullTimeoutMs,
|
|
59
|
+
startupTimeoutMs: optionsArg.startupTimeoutMs ?? startupTimeoutMsArg,
|
|
60
|
+
};
|
|
61
|
+
for (const [name, value] of Object.entries(this.#options)) {
|
|
62
|
+
if (name !== 'dockerSocketPath' && (!Number.isSafeInteger(value) || value < 1)) {
|
|
63
|
+
throw new TypeError(`${name} must be a positive integer.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
get state() {
|
|
68
|
+
return this.#state;
|
|
69
|
+
}
|
|
70
|
+
get startupTimeoutMs() {
|
|
71
|
+
return this.#options.startupTimeoutMs;
|
|
72
|
+
}
|
|
73
|
+
get lifecycleId() {
|
|
74
|
+
return this.#require(this.#owner, 'lifecycle owner').lifecycleId;
|
|
75
|
+
}
|
|
76
|
+
get endpoint() {
|
|
77
|
+
return this.#require(this.#endpoint, 'endpoint');
|
|
78
|
+
}
|
|
79
|
+
get http() {
|
|
80
|
+
return this.#require(this.#http, 'HTTPS client');
|
|
81
|
+
}
|
|
82
|
+
get dockerHost() {
|
|
83
|
+
return this.#require(this.#dockerHost, 'Docker host');
|
|
84
|
+
}
|
|
85
|
+
/** Aborted once `stop()` or `requestStop()` was called. */
|
|
86
|
+
get stopSignal() {
|
|
87
|
+
return this.#stopController.signal;
|
|
88
|
+
}
|
|
89
|
+
/** Throws {@link ForgeFixtureStoppedError} once a stop was requested. */
|
|
90
|
+
assertNotStopping() {
|
|
91
|
+
if (this.#stopController.signal.aborted)
|
|
92
|
+
throw new ForgeFixtureStoppedError(this.#kind);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Once a stop was requested, any failure it provoked (an aborted pull, a
|
|
96
|
+
* refused request) is reported as {@link ForgeFixtureStoppedError} with the
|
|
97
|
+
* original failure as its cause.
|
|
98
|
+
*/
|
|
99
|
+
stopAwareError(errorArg) {
|
|
100
|
+
if (!this.#stopController.signal.aborted || errorArg instanceof ForgeFixtureStoppedError)
|
|
101
|
+
return errorArg;
|
|
102
|
+
return new ForgeFixtureStoppedError(this.#kind, { cause: errorArg });
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Marks the lifecycle as stopping without waiting. Running steps observe it
|
|
106
|
+
* at their next checkpoint, an image pull is cancelled, and the lifecycle can
|
|
107
|
+
* no longer be prepared or started.
|
|
108
|
+
*/
|
|
109
|
+
requestStop() {
|
|
110
|
+
if (!this.#stopController.signal.aborted)
|
|
111
|
+
this.#stopController.abort(new ForgeFixtureStoppedError(this.#kind));
|
|
112
|
+
}
|
|
113
|
+
/** Report of the reaper run that preceded this lifecycle. */
|
|
114
|
+
get lastReapReport() {
|
|
115
|
+
return this.#lastReapReport;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Reaps stale fixtures, creates ownership, CA and TLS terminator. The
|
|
119
|
+
* returned endpoint is final: its port is bound before the forge starts.
|
|
120
|
+
*/
|
|
121
|
+
async prepare() {
|
|
122
|
+
this.assertNotStopping();
|
|
123
|
+
if (this.#state !== 'idle')
|
|
124
|
+
throw new Error(`Cannot prepare a ${this.#state} lifecycle.`);
|
|
125
|
+
this.#state = 'preparing';
|
|
126
|
+
return this.#track(this.#prepare());
|
|
127
|
+
}
|
|
128
|
+
async #prepare() {
|
|
129
|
+
try {
|
|
130
|
+
this.#dockerHost = createForgeFixtureDockerHost(this.#options.dockerSocketPath);
|
|
131
|
+
this.#lastReapReport = await reapStaleForgeFixtures(this.#dockerHost);
|
|
132
|
+
this.assertNotStopping();
|
|
133
|
+
this.#owner = await createForgeFixtureOwner(this.#kind, this.#options.maxLifetimeMs);
|
|
134
|
+
const authority = await ForgeFixtureCertificateAuthority.create(this.#owner.lifecycleId, this.#options.maxLifetimeMs + 24 * 60 * 60 * 1000);
|
|
135
|
+
const certificate = await authority.issueServerCertificate({ ipAddresses: ['127.0.0.1'], dnsNames: ['localhost'] });
|
|
136
|
+
this.assertNotStopping();
|
|
137
|
+
this.#terminator = new ForgeFixtureTlsTerminator(certificate);
|
|
138
|
+
const port = await this.#terminator.listen();
|
|
139
|
+
this.assertNotStopping();
|
|
140
|
+
this.#endpoint = { baseUrl: `https://127.0.0.1:${port}`, caCertificatePem: authority.certificatePem };
|
|
141
|
+
this.#http = new ForgeFixtureHttpClient({ baseUrl: this.#endpoint.baseUrl, caCertificatePem: authority.certificatePem });
|
|
142
|
+
this.#state = 'prepared';
|
|
143
|
+
return this.#endpoint;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const reported = this.stopAwareError(error);
|
|
147
|
+
await this.#failAndCleanup(reported);
|
|
148
|
+
throw reported;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Pulls the pinned image, creates the owned network and container, starts it and wires the terminator. */
|
|
152
|
+
async startContainer(specArg) {
|
|
153
|
+
this.assertNotStopping();
|
|
154
|
+
if (this.#state !== 'prepared')
|
|
155
|
+
throw new Error(`Cannot start a container in a ${this.#state} lifecycle.`);
|
|
156
|
+
this.#state = 'starting';
|
|
157
|
+
return this.#track(this.#startContainer(specArg));
|
|
158
|
+
}
|
|
159
|
+
async #startContainer(specArg) {
|
|
160
|
+
const dockerHost = this.dockerHost;
|
|
161
|
+
const owner = this.#require(this.#owner, 'lifecycle owner');
|
|
162
|
+
const labels = forgeFixtureOwnerLabels(owner);
|
|
163
|
+
const name = `forgefixtures-${owner.kind}-${owner.lifecycleId}`;
|
|
164
|
+
try {
|
|
165
|
+
const image = await dockerHost.pullImage({
|
|
166
|
+
reference: specArg.image.repoDigest,
|
|
167
|
+
expectedRepoDigest: specArg.image.repoDigest,
|
|
168
|
+
signal: AbortSignal.any([AbortSignal.timeout(this.#options.pullTimeoutMs), this.#stopController.signal]),
|
|
169
|
+
});
|
|
170
|
+
this.assertNotStopping();
|
|
171
|
+
this.#network = await dockerHost.createNetwork({ Name: name, Driver: 'bridge', Labels: labels });
|
|
172
|
+
this.assertNotStopping();
|
|
173
|
+
for (const volume of specArg.volumes ?? []) {
|
|
174
|
+
if (!/^[a-z][a-z0-9-]{0,30}$/.test(volume.purpose))
|
|
175
|
+
throw new TypeError(`Volume purpose "${volume.purpose}" is malformed.`);
|
|
176
|
+
this.#volumes.push(await dockerHost.createVolume({ name: `${name}-${volume.purpose}`, labels }));
|
|
177
|
+
this.assertNotStopping();
|
|
178
|
+
}
|
|
179
|
+
// The host port is chosen here rather than by Docker: a rootless daemon allocates
|
|
180
|
+
// "ephemeral" ports inside its own namespace, blind to host listeners, and then
|
|
181
|
+
// fails to bind them on the host. A conflict recreates the container on a new port.
|
|
182
|
+
for (let attempt = 1;; attempt++) {
|
|
183
|
+
const hostPort = await reserveLoopbackPort();
|
|
184
|
+
this.#container = await dockerHost.createContainer({
|
|
185
|
+
name,
|
|
186
|
+
imageId: image.Id,
|
|
187
|
+
imageReference: specArg.image.repoDigest,
|
|
188
|
+
user: specArg.user,
|
|
189
|
+
...(specArg.allowRootOnRootless ? { allowRootOnRootless: true } : {}),
|
|
190
|
+
command: specArg.command,
|
|
191
|
+
env: specArg.env,
|
|
192
|
+
labels,
|
|
193
|
+
stopTimeout: specArg.stopTimeoutSeconds,
|
|
194
|
+
memoryBytes: specArg.memoryBytes,
|
|
195
|
+
memorySwapBytes: specArg.memoryBytes,
|
|
196
|
+
nanoCpus: specArg.nanoCpus,
|
|
197
|
+
pidsLimit: specArg.pidsLimit,
|
|
198
|
+
...(specArg.shmSizeBytes === undefined ? {} : { shmSize: specArg.shmSizeBytes }),
|
|
199
|
+
...(specArg.tmpfsMounts === undefined ? {} : { tmpfsMounts: specArg.tmpfsMounts }),
|
|
200
|
+
...(specArg.healthcheck === undefined ? {} : { healthcheck: specArg.healthcheck }),
|
|
201
|
+
namedVolumeMounts: (specArg.volumes ?? []).map((volumeArg, indexArg) => ({
|
|
202
|
+
source: this.#volumes[indexArg].Name, target: volumeArg.target,
|
|
203
|
+
})),
|
|
204
|
+
networkEndpoints: [{ networkId: this.#network.Id }],
|
|
205
|
+
portBindings: [{ containerPort: specArg.httpPort, hostPort, hostIp: '127.0.0.1', protocol: 'tcp' }],
|
|
206
|
+
});
|
|
207
|
+
this.assertNotStopping();
|
|
208
|
+
try {
|
|
209
|
+
await this.#container.start();
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (attempt >= maxPortAttempts || !isPortConflict(error))
|
|
214
|
+
throw error;
|
|
215
|
+
await this.#container.remove({ force: true, removeAnonymousVolumes: true });
|
|
216
|
+
this.#container = undefined;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
this.assertNotStopping();
|
|
220
|
+
const publishedPort = await this.#publishedPort(this.#container, specArg.httpPort);
|
|
221
|
+
this.assertNotStopping();
|
|
222
|
+
this.#require(this.#terminator, 'TLS terminator').setUpstreamPort(publishedPort);
|
|
223
|
+
this.#state = 'running';
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
const reported = this.stopAwareError(error);
|
|
227
|
+
await this.#failAndCleanup(reported);
|
|
228
|
+
throw reported;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/** Runs argv in the container as its configured user. Never logs stdin or output. */
|
|
232
|
+
async exec(commandArg, optionsArg = {}) {
|
|
233
|
+
if (this.#state !== 'running')
|
|
234
|
+
throw new Error(`Cannot exec in a ${this.#state} lifecycle.`);
|
|
235
|
+
return this.#require(this.#container, 'container').exec(commandArg, optionsArg);
|
|
236
|
+
}
|
|
237
|
+
/** Fails with the container's exit state and recent logs when it stopped running. */
|
|
238
|
+
async assertContainerRunning() {
|
|
239
|
+
const container = this.#require(this.#container, 'container');
|
|
240
|
+
const state = await container.inspectState();
|
|
241
|
+
if (state.Running)
|
|
242
|
+
return;
|
|
243
|
+
const logs = await container.logs({ tail: 40 });
|
|
244
|
+
throw new Error(`The ${this.#kind} fixture container is ${state.Status} (exit ${state.ExitCode}, OOM ${state.OOMKilled}). Last logs:\n${logs}`);
|
|
245
|
+
}
|
|
246
|
+
/** Docker's view of the container healthcheck (`none` without a healthcheck). */
|
|
247
|
+
async containerHealth() {
|
|
248
|
+
const inspection = await this.#require(this.#container, 'container').inspect();
|
|
249
|
+
const state = inspection.State;
|
|
250
|
+
const health = typeof state === 'object' && state !== null ? Reflect.get(state, 'Health') : undefined;
|
|
251
|
+
if (health === undefined || health === null)
|
|
252
|
+
return 'none';
|
|
253
|
+
const status = typeof health === 'object' ? Reflect.get(health, 'Status') : undefined;
|
|
254
|
+
if (status === 'starting' || status === 'healthy' || status === 'unhealthy')
|
|
255
|
+
return status;
|
|
256
|
+
throw new Error('Docker reported a malformed container health status.');
|
|
257
|
+
}
|
|
258
|
+
/** Recent container logs, for diagnostics after a failed readiness wait. */
|
|
259
|
+
async logsTail(linesArg) {
|
|
260
|
+
return this.#require(this.#container, 'container').logs({ tail: linesArg });
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Requests a stop, waits for a running prepare or container start to observe
|
|
264
|
+
* it, then removes every owned resource in dependency order, including those
|
|
265
|
+
* that step created. Idempotent. When a removal fails, the remaining
|
|
266
|
+
* resources stay recorded and a later call retries them; the failures are
|
|
267
|
+
* thrown together.
|
|
268
|
+
*/
|
|
269
|
+
async stop() {
|
|
270
|
+
this.requestStop();
|
|
271
|
+
const inflight = this.#inflight;
|
|
272
|
+
if (inflight)
|
|
273
|
+
await inflight.catch(() => undefined);
|
|
274
|
+
await this.#cleanup();
|
|
275
|
+
}
|
|
276
|
+
async #track(operationArg) {
|
|
277
|
+
this.#inflight = operationArg;
|
|
278
|
+
try {
|
|
279
|
+
return await operationArg;
|
|
280
|
+
}
|
|
281
|
+
finally {
|
|
282
|
+
if (this.#inflight === operationArg)
|
|
283
|
+
this.#inflight = undefined;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
#cleanup() {
|
|
287
|
+
const run = this.#cleanupQueue.catch(() => undefined).then(() => this.#removeResources());
|
|
288
|
+
this.#cleanupQueue = run;
|
|
289
|
+
return run;
|
|
290
|
+
}
|
|
291
|
+
async #removeResources() {
|
|
292
|
+
if (!this.#hasResources()) {
|
|
293
|
+
this.#http = undefined;
|
|
294
|
+
this.#endpoint = undefined;
|
|
295
|
+
this.#state = 'stopped';
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
this.#state = 'stopping';
|
|
299
|
+
const failures = [];
|
|
300
|
+
const attempt = async (operationArg) => {
|
|
301
|
+
try {
|
|
302
|
+
await operationArg();
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
failures.push(error);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
this.#http?.close();
|
|
309
|
+
this.#http = undefined;
|
|
310
|
+
await attempt(async () => {
|
|
311
|
+
await this.#terminator?.close();
|
|
312
|
+
this.#terminator = undefined;
|
|
313
|
+
});
|
|
314
|
+
await attempt(async () => {
|
|
315
|
+
await this.#container?.remove({ force: true, removeAnonymousVolumes: true });
|
|
316
|
+
this.#container = undefined;
|
|
317
|
+
});
|
|
318
|
+
await attempt(async () => {
|
|
319
|
+
if (this.#container)
|
|
320
|
+
return;
|
|
321
|
+
await this.#network?.remove();
|
|
322
|
+
this.#network = undefined;
|
|
323
|
+
});
|
|
324
|
+
while (this.#volumes.length > 0 && !this.#container) {
|
|
325
|
+
const volume = this.#volumes[0];
|
|
326
|
+
try {
|
|
327
|
+
await volume.remove({ force: true });
|
|
328
|
+
this.#volumes.shift();
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
failures.push(error);
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!this.#hasResources()) {
|
|
336
|
+
await attempt(async () => {
|
|
337
|
+
await this.#dockerHost?.stop();
|
|
338
|
+
this.#dockerHost = undefined;
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
this.#endpoint = undefined;
|
|
342
|
+
this.#state = this.#hasResources() ? 'stopping' : 'stopped';
|
|
343
|
+
if (failures.length > 0) {
|
|
344
|
+
throw new AggregateError(failures, `The ${this.#kind} fixture ${this.#owner?.lifecycleId ?? ''} did not clean up completely.`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
#hasResources() {
|
|
348
|
+
return this.#terminator !== undefined || this.#container !== undefined
|
|
349
|
+
|| this.#network !== undefined || this.#volumes.length > 0 || this.#dockerHost !== undefined;
|
|
350
|
+
}
|
|
351
|
+
async #failAndCleanup(errorArg) {
|
|
352
|
+
try {
|
|
353
|
+
await this.#cleanup();
|
|
354
|
+
}
|
|
355
|
+
catch (cleanupError) {
|
|
356
|
+
throw new AggregateError([errorArg, cleanupError], `The ${this.#kind} fixture failed to start and to clean up.`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async #publishedPort(containerArg, containerPortArg) {
|
|
360
|
+
const inspection = await containerArg.inspect();
|
|
361
|
+
const settings = inspection.NetworkSettings;
|
|
362
|
+
const ports = typeof settings === 'object' && settings !== null ? Reflect.get(settings, 'Ports') : undefined;
|
|
363
|
+
const bindings = typeof ports === 'object' && ports !== null ? Reflect.get(ports, `${containerPortArg}/tcp`) : undefined;
|
|
364
|
+
if (Array.isArray(bindings)) {
|
|
365
|
+
for (const binding of bindings) {
|
|
366
|
+
if (typeof binding !== 'object' || binding === null)
|
|
367
|
+
continue;
|
|
368
|
+
const hostIp = Reflect.get(binding, 'HostIp');
|
|
369
|
+
const hostPort = Reflect.get(binding, 'HostPort');
|
|
370
|
+
if (hostIp === '127.0.0.1' && typeof hostPort === 'string' && /^[0-9]{1,5}$/.test(hostPort)) {
|
|
371
|
+
return Number(hostPort);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
throw new Error(`Docker did not publish container port ${containerPortArg} on 127.0.0.1.`);
|
|
376
|
+
}
|
|
377
|
+
#require(valueArg, nameArg) {
|
|
378
|
+
if (valueArg === undefined)
|
|
379
|
+
throw new Error(`The ${this.#kind} fixture has no ${nameArg} in state ${this.#state}.`);
|
|
380
|
+
return valueArg;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jb250YWluZXJsaWZlY3ljbGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLmNvbnRhaW5lcmxpZmVjeWNsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUM1RSxPQUFPLEVBQUUsZ0NBQWdDLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUNyRixPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNqRSxPQUFPLEVBQUUsNEJBQTRCLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUMzRixPQUFPLEVBQUUseUJBQXlCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUN2RSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQW1DbEYsTUFBTSxlQUFlLEdBQUcsQ0FBQyxDQUFDO0FBRTFCLHlHQUF5RztBQUN6RyxNQUFNLG1CQUFtQixHQUFHLEtBQUssSUFBcUIsRUFBRTtJQUN0RCxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksRUFBRSxDQUFDO0lBQzFDLE1BQU0sSUFBSSxPQUFPLENBQU8sQ0FBQyxVQUFVLEVBQUUsU0FBUyxFQUFFLEVBQUU7UUFDaEQsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDaEMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLEVBQUUsR0FBRyxFQUFFLENBQUMsVUFBVSxFQUFFLENBQUMsQ0FBQztJQUNyRixDQUFDLENBQUMsQ0FBQztJQUNILE1BQU0sT0FBTyxHQUFH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
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ForgeFixtureHttpClient } from './classes.httpclient.js';
|
|
2
|
+
import type { IForgeFixtureLifecycleOptions, IForgeFixtureReapReport, IGiteaFixtureRuntime } from './interfaces.js';
|
|
3
|
+
export interface IGiteaFixtureOptions extends IForgeFixtureLifecycleOptions {
|
|
4
|
+
/** Container memory limit; swap is disabled. Defaults to 512 MiB. */
|
|
5
|
+
memoryBytes?: number;
|
|
6
|
+
/** CPU limit in billionths of a CPU. Defaults to 2 CPUs. */
|
|
7
|
+
nanoCpus?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface IGiteaAccessTokenRequest {
|
|
10
|
+
username: string;
|
|
11
|
+
/** Unique per user. */
|
|
12
|
+
tokenName: string;
|
|
13
|
+
/** Gitea scopes such as `all`, `public-only` or `read:repository`. */
|
|
14
|
+
scopes: string[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A disposable Gitea instance from the pinned rootless image, served over
|
|
18
|
+
* verified loopback TLS, with SQLite and all data in one owned named volume.
|
|
19
|
+
*/
|
|
20
|
+
export declare class GiteaFixture {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(optionsArg?: IGiteaFixtureOptions);
|
|
23
|
+
get runtime(): IGiteaFixtureRuntime;
|
|
24
|
+
/** HTTPS client scoped to this fixture's origin and CA. */
|
|
25
|
+
get http(): ForgeFixtureHttpClient;
|
|
26
|
+
/** Report of the stale-fixture reaper run that preceded this start. */
|
|
27
|
+
get reapReport(): IForgeFixtureReapReport | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Starts the fixture and returns once Gitea answers with the pinned version
|
|
30
|
+
* and an admin token exists. A fixture starts at most once.
|
|
31
|
+
*/
|
|
32
|
+
start(): Promise<IGiteaFixtureRuntime>;
|
|
33
|
+
/** Creates an access token through Gitea's supported admin CLI. The token is returned, never logged. */
|
|
34
|
+
createAccessToken(requestArg: IGiteaAccessTokenRequest): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Removes every resource of this fixture. A start in progress is cancelled at
|
|
37
|
+
* its next checkpoint and awaited first, so nothing it created survives.
|
|
38
|
+
* Idempotent; retries incomplete cleanup.
|
|
39
|
+
*/
|
|
40
|
+
stop(): Promise<void>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { giteaFixtureImage } from './constants.js';
|
|
3
|
+
import { ForgeFixtureContainerLifecycle } from './classes.containerlifecycle.js';
|
|
4
|
+
const httpPort = 3000;
|
|
5
|
+
const dataDirectory = '/var/lib/gitea';
|
|
6
|
+
/** The configuration lives in the owned data volume, whose directories belong to the Gitea user. */
|
|
7
|
+
const appIni = `${dataDirectory}/conf/app.ini`;
|
|
8
|
+
const giteaBinary = '/usr/local/bin/gitea';
|
|
9
|
+
const adminUsername = 'forgeadmin';
|
|
10
|
+
const usernamePattern = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,38}$/;
|
|
11
|
+
const scopePattern = /^(all|public-only|(read|write):[a-z]+)$/;
|
|
12
|
+
const tokenPattern = /^[0-9a-f]{40}$/;
|
|
13
|
+
const execTimeoutMs = 60_000;
|
|
14
|
+
const sleep = (msArg) => new Promise((resolveArg) => setTimeout(resolveArg, msArg));
|
|
15
|
+
/**
|
|
16
|
+
* A disposable Gitea instance from the pinned rootless image, served over
|
|
17
|
+
* verified loopback TLS, with SQLite and all data in one owned named volume.
|
|
18
|
+
*/
|
|
19
|
+
export class GiteaFixture {
|
|
20
|
+
#options;
|
|
21
|
+
#lifecycle;
|
|
22
|
+
#runtime;
|
|
23
|
+
#starting;
|
|
24
|
+
constructor(optionsArg = {}) {
|
|
25
|
+
this.#options = { ...optionsArg };
|
|
26
|
+
this.#lifecycle = new ForgeFixtureContainerLifecycle('gitea', optionsArg, 120_000);
|
|
27
|
+
}
|
|
28
|
+
get runtime() {
|
|
29
|
+
if (!this.#runtime)
|
|
30
|
+
throw new Error('The Gitea fixture is not running.');
|
|
31
|
+
return this.#runtime;
|
|
32
|
+
}
|
|
33
|
+
/** HTTPS client scoped to this fixture's origin and CA. */
|
|
34
|
+
get http() {
|
|
35
|
+
return this.#lifecycle.http;
|
|
36
|
+
}
|
|
37
|
+
/** Report of the stale-fixture reaper run that preceded this start. */
|
|
38
|
+
get reapReport() {
|
|
39
|
+
return this.#lifecycle.lastReapReport;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Starts the fixture and returns once Gitea answers with the pinned version
|
|
43
|
+
* and an admin token exists. A fixture starts at most once.
|
|
44
|
+
*/
|
|
45
|
+
start() {
|
|
46
|
+
if (this.#starting)
|
|
47
|
+
return Promise.reject(new Error('The Gitea fixture was already started.'));
|
|
48
|
+
this.#starting = this.#start();
|
|
49
|
+
return this.#starting;
|
|
50
|
+
}
|
|
51
|
+
async #start() {
|
|
52
|
+
const endpoint = await this.#lifecycle.prepare();
|
|
53
|
+
try {
|
|
54
|
+
await this.#lifecycle.startContainer({
|
|
55
|
+
image: giteaFixtureImage,
|
|
56
|
+
user: '1000:1000',
|
|
57
|
+
command: [giteaBinary, '--config', appIni, 'web'],
|
|
58
|
+
env: {
|
|
59
|
+
GITEA_APP_INI: appIni,
|
|
60
|
+
GITEA__security__INSTALL_LOCK: 'true',
|
|
61
|
+
GITEA__security__SECRET_KEY: plugins.crypto.randomBytes(32).toString('hex'),
|
|
62
|
+
GITEA__server__ROOT_URL: `${endpoint.baseUrl}/`,
|
|
63
|
+
GITEA__server__DOMAIN: '127.0.0.1',
|
|
64
|
+
GITEA__server__SSH_DOMAIN: '127.0.0.1',
|
|
65
|
+
GITEA__server__HTTP_PORT: String(httpPort),
|
|
66
|
+
GITEA__server__LFS_START_SERVER: 'true',
|
|
67
|
+
GITEA__server__OFFLINE_MODE: 'true',
|
|
68
|
+
GITEA__service__DISABLE_REGISTRATION: 'true',
|
|
69
|
+
GITEA__database__DB_TYPE: 'sqlite3',
|
|
70
|
+
GITEA__log__LEVEL: 'Warn',
|
|
71
|
+
},
|
|
72
|
+
httpPort,
|
|
73
|
+
// Git hooks live in the data directory and must be executable, so it is an owned
|
|
74
|
+
// named volume (Docker mounts tmpfs noexec). It is removed with the lifecycle.
|
|
75
|
+
volumes: [{ purpose: 'data', target: dataDirectory }],
|
|
76
|
+
// Declared image volume, unused because the configuration lives in the data volume;
|
|
77
|
+
// mounted so Docker creates no anonymous volume for it.
|
|
78
|
+
tmpfsMounts: [{ target: '/etc/gitea', sizeBytes: 1024 * 1024 }],
|
|
79
|
+
memoryBytes: this.#options.memoryBytes ?? 512 * 1024 * 1024,
|
|
80
|
+
nanoCpus: this.#options.nanoCpus ?? 2_000_000_000,
|
|
81
|
+
pidsLimit: 512,
|
|
82
|
+
stopTimeoutSeconds: 10,
|
|
83
|
+
});
|
|
84
|
+
await this.#waitForHealth(Date.now() + this.#lifecycle.startupTimeoutMs);
|
|
85
|
+
const version = await this.#readVersion();
|
|
86
|
+
this.#lifecycle.assertNotStopping();
|
|
87
|
+
if (version !== giteaFixtureImage.version) {
|
|
88
|
+
throw new Error(`Gitea reports version ${version}; the pinned image is ${giteaFixtureImage.version}.`);
|
|
89
|
+
}
|
|
90
|
+
await this.#giteaCli([
|
|
91
|
+
'admin', 'user', 'create', '--username', adminUsername, '--email', `${adminUsername}@example.com`,
|
|
92
|
+
'--random-password', '--admin', '--must-change-password=false',
|
|
93
|
+
], 'create the fixture administrator');
|
|
94
|
+
const token = await this.createAccessToken({ username: adminUsername, tokenName: 'forgefixtures-admin', scopes: ['all'] });
|
|
95
|
+
this.#lifecycle.assertNotStopping();
|
|
96
|
+
this.#runtime = {
|
|
97
|
+
kind: 'gitea',
|
|
98
|
+
lifecycleId: this.#lifecycle.lifecycleId,
|
|
99
|
+
baseUrl: endpoint.baseUrl,
|
|
100
|
+
caCertificatePem: endpoint.caCertificatePem,
|
|
101
|
+
version,
|
|
102
|
+
image: giteaFixtureImage,
|
|
103
|
+
admin: { username: adminUsername, token },
|
|
104
|
+
};
|
|
105
|
+
return this.#runtime;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const reported = this.#lifecycle.stopAwareError(error);
|
|
109
|
+
try {
|
|
110
|
+
await this.#lifecycle.stop();
|
|
111
|
+
}
|
|
112
|
+
catch (cleanupError) {
|
|
113
|
+
throw new AggregateError([reported, cleanupError], 'The Gitea fixture failed to start and to clean up.');
|
|
114
|
+
}
|
|
115
|
+
throw reported;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Creates an access token through Gitea's supported admin CLI. The token is returned, never logged. */
|
|
119
|
+
async createAccessToken(requestArg) {
|
|
120
|
+
if (!usernamePattern.test(requestArg.username) || !/^[A-Za-z0-9_.-]{1,64}$/.test(requestArg.tokenName)
|
|
121
|
+
|| requestArg.scopes.length === 0 || !requestArg.scopes.every((scopeArg) => scopePattern.test(scopeArg))) {
|
|
122
|
+
throw new TypeError('The access token request is malformed.');
|
|
123
|
+
}
|
|
124
|
+
const stdout = await this.#giteaCli([
|
|
125
|
+
'admin', 'user', 'generate-access-token', '--username', requestArg.username,
|
|
126
|
+
'--token-name', requestArg.tokenName, '--scopes', requestArg.scopes.join(','), '--raw',
|
|
127
|
+
], `create an access token for ${requestArg.username}`);
|
|
128
|
+
const token = stdout.trim();
|
|
129
|
+
if (!tokenPattern.test(token))
|
|
130
|
+
throw new Error('Gitea returned an access token in an unexpected format.');
|
|
131
|
+
return token;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Removes every resource of this fixture. A start in progress is cancelled at
|
|
135
|
+
* its next checkpoint and awaited first, so nothing it created survives.
|
|
136
|
+
* Idempotent; retries incomplete cleanup.
|
|
137
|
+
*/
|
|
138
|
+
async stop() {
|
|
139
|
+
this.#runtime = undefined;
|
|
140
|
+
this.#lifecycle.requestStop();
|
|
141
|
+
const starting = this.#starting;
|
|
142
|
+
if (starting)
|
|
143
|
+
await starting.catch(() => undefined);
|
|
144
|
+
await this.#lifecycle.stop();
|
|
145
|
+
}
|
|
146
|
+
async #waitForHealth(deadlineArg) {
|
|
147
|
+
let lastFailure = 'no attempt';
|
|
148
|
+
while (Date.now() < deadlineArg) {
|
|
149
|
+
this.#lifecycle.assertNotStopping();
|
|
150
|
+
await this.#lifecycle.assertContainerRunning();
|
|
151
|
+
try {
|
|
152
|
+
const response = await this.#lifecycle.http.request({
|
|
153
|
+
method: 'GET', url: '/api/healthz', expectedStatus: [200, 503], timeoutMs: 5_000,
|
|
154
|
+
});
|
|
155
|
+
if (response.status === 200)
|
|
156
|
+
return;
|
|
157
|
+
lastFailure = `status ${response.status}`;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
lastFailure = error instanceof Error ? error.message : String(error);
|
|
161
|
+
}
|
|
162
|
+
await sleep(500);
|
|
163
|
+
}
|
|
164
|
+
throw new Error(`Gitea did not become healthy within ${this.#lifecycle.startupTimeoutMs} ms (${lastFailure}). Last logs:\n${await this.#lifecycle.logsTail(40)}`);
|
|
165
|
+
}
|
|
166
|
+
async #readVersion() {
|
|
167
|
+
const body = await this.#lifecycle.http.requestJson({ method: 'GET', url: '/api/v1/version', expectedStatus: [200] });
|
|
168
|
+
const version = typeof body === 'object' && body !== null ? Reflect.get(body, 'version') : undefined;
|
|
169
|
+
if (typeof version !== 'string')
|
|
170
|
+
throw new Error('Gitea answered /api/v1/version without a version.');
|
|
171
|
+
return version;
|
|
172
|
+
}
|
|
173
|
+
/** Runs the Gitea CLI as the Gitea user. Output is returned but never included in errors. */
|
|
174
|
+
async #giteaCli(argsArg, purposeArg) {
|
|
175
|
+
const result = await this.#lifecycle.exec([giteaBinary, '--config', appIni, ...argsArg], { timeoutMs: execTimeoutMs });
|
|
176
|
+
if (result.exitCode !== 0) {
|
|
177
|
+
throw new Error(`Gitea CLI failed to ${purposeArg} (exit ${result.exitCode}): ${result.stderr.slice(0, 1024)}`);
|
|
178
|
+
}
|
|
179
|
+
return result.stdout;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5naXRlYWZpeHR1cmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLmdpdGVhZml4dHVyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNuRCxPQUFPLEVBQUUsOEJBQThCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQW1CakYsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDO0FBQ3RCLE1BQU0sYUFBYSxHQUFHLGdCQUFnQixDQUFDO0FBQ3ZDLG9HQUFvRztBQUNwRyxNQUFNLE1BQU0sR0FBRyxHQUFHLGFBQWEsZUFBZSxDQUFDO0FBQy9DLE1BQU0sV0FBVyxHQUFHLHNCQUFzQixDQUFDO0FBQzNDLE1BQU0sYUFBYSxHQUFHLFlBQVksQ0FBQztBQUNuQyxNQUFNLGVBQWUsR0FBRyxtQ0FBbUMsQ0FBQztBQUM1RCxNQUFNLFlBQVksR0FBRyx5Q0FBeUMsQ0FBQztBQUMvRCxNQUFNLFlBQVksR0FBRyxnQkFBZ0IsQ0FBQztBQUN0QyxNQUFNLGFBQWEsR0FBRyxNQUFNLENBQUM7QUFFN0IsTUFBTSxLQUFLLEdBQUcsQ0FBQyxLQUFhLEVBQUUsRUFBRSxDQUFDLElBQUksT0FBTyxDQUFPLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsVUFBVSxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUM7QUFFbEc7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFlBQVk7SUFDZCxRQUFRLENBQXVCO0lBQy9CLFVBQVUsQ0FBaUM7SUFDcEQsUUFBUSxDQUFtQztJQUMzQyxTQUFTLENBQTRDO0lBRXJELFlBQVksYUFBbUMsRUFBRTtRQUMvQyxJQUFJLENBQUMsUUFBUSxHQUFHLEVBQUUsR0FBRyxVQUFVLEVBQUUsQ0FBQztRQUNsQyxJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksOEJBQThCLENBQUMsT0FBTyxFQUFFLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUNyRixDQUFDO0lBRUQsSUFBVyxPQUFPO1FBQ2hCLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUTtZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMsbUNBQW1DLENBQUMsQ0FBQztRQUN6RSxPQUFPLElBQUksQ0FBQyxRQUFRLENBQUM7SUFDdkIsQ0FBQztJQUVELDJEQUEyRDtJQUMzRCxJQUFXLElBQUk7UUFDYixPQUFPLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDO0lBQzlCLENBQUM7SUFFRCx1RUFBdUU7SUFDdkUsSUFBVyxVQUFVO1FBQ25CLE9BQU8sSUFBSSxDQUFDLFVBQVUsQ0FBQyxjQUFjLENBQUM7SUFDeEMsQ0FBQztJQUVEOzs7T0FHRztJQUNJLEtBQUs7UUFDVixJQUFJLElBQUksQ0FBQyxTQUFTO1lBQUUsT0FBTyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksS0FBSyxDQUFDLHdDQUF3QyxDQUFDLENBQUMsQ0FBQztRQUMvRixJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUMvQixPQUFPLElBQUksQ0FBQyxTQUFTLENBQUM7SUFDeEIsQ0FBQztJQUVELEtBQUssQ0FBQyxNQUFNO1FBQ1YsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2pELElBQUksQ0FBQztZQUNILE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxjQUFjLENBQUM7Z0JBQ25DLEtBQUssRUFBRSxpQkFBaUI7Z0JBQ3hCLElBQUksRUFBRSxXQUFXO2dCQUNqQixPQUFPLEVBQUUsQ0FBQyxXQUFXLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxLQUFLLENBQUM7Z0JBQ2pELEdBQUcsRUFBRTtvQkFDSCxhQUFhLEVBQUUsTUFBTTtvQkFDckIsNkJBQTZCLEVBQUUsTUFBTTtvQkFDckMsMkJBQTJCLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQztvQkFDM0UsdUJBQXVCLEVBQUUsR0FBRyxRQUFRLENBQUMsT0FBTyxHQUFHO29CQUMvQyxxQkFBcUIsRUFBRSxXQUFXO29CQUNsQyx5QkFBeUIsRUFBRSxXQUFXO29CQUN0Qyx3QkFBd0IsRUFBRSxNQUFNLENBQUMsUUFBUSxDQUFDO29CQUMxQywrQkFBK0IsRUFBRSxNQUFNO29CQUN2QywyQkFBMkIsRUFBRSxNQUFNO29CQUNuQyxvQ0FBb0MsRUFBRSxNQUFNO29CQUM1Qyx3QkFBd0IsRUFBRSxTQUFTO29CQUNuQyxpQkFBaUIsRUFBRSxNQUFNO2lCQUMxQjtnQkFDRCxRQUFRO2dCQUNSLGlGQUFpRjtnQkFDakYsK0VBQStFO2dCQUMvRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLGFBQWEsRUFBRSxDQUFDO2dCQUNyRCxvRkFBb0Y7Z0JBQ3BGLHdEQUF3RDtnQkFDeEQsV0FBVyxFQUFFLENBQUMsRUFBRSxNQUFNLEVBQUUsWUFBWSxFQUFFLFNBQVMsRUFBRSxJQUFJLEdBQUcsSUFBSSxFQUFFLENBQUM7Z0JBQy9ELFdBQVcsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLFdBQVcsSUFBSSxHQUFHLEdBQUcsSUFBSSxHQUFHLElBQUk7Z0JBQzNELFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsSUFBSSxhQUFhO2dCQUNqRCxTQUFTLEVBQUUsR0FBRztnQkFDZCxrQkFBa0IsRUFBRSxFQUFFO2FBQ3ZCLENBQUMsQ0FBQztZQUNILE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1lBQ3pFLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQzFDLElBQUksQ0FBQyxVQUFVLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztZQUNwQyxJQUFJLE9BQU8sS0FBSyxpQkFBaUIsQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDMUMsTUFBTSxJQUFJLEtBQUssQ0FBQyx5QkFBeUIsT0FBTyx5QkFBeUIsaUJBQWlCLENBQUMsT0FBTyxHQUFHLENBQUMsQ0FBQztZQUN6RyxDQUFDO1lBQ0QsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDO2dCQUNuQixPQUFPLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxZQUFZLEVBQUUsYUFBYSxFQUFFLFNBQVMsRUFBRSxHQUFHLGFBQWEsY0FBYztnQkFDakcsbUJBQW1CLEVBQUUsU0FBUyxFQUFFLDhCQUE4QjthQUMvRCxFQUFFLGtDQUFrQyxDQUFDLENBQUM7WUFDdkMsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsaUJBQWlCLENBQUMsRUFBRSxRQUFRLEVBQUUsYUFBYSxFQUFFLFNBQVMsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLEVBQUUsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7WUFDM0gsSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1lBQ3BDLElBQUksQ0FBQyxRQUFRLEdBQUc7Z0JBQ2QsSUFBSSxFQUFFLE9BQU87Z0JBQ2IsV0FBVyxFQUFFLElBQUksQ0FBQyxVQUFVLENBQUMsV0FBVztnQkFDeEMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxPQUFPO2dCQUN6QixnQkFBZ0IsRUFBRSxRQUFRLENBQUMsZ0JBQWdCO2dCQUMzQyxPQUFPO2dCQUNQLEtBQUssRUFBRSxpQkFBaUI7Z0JBQ3hCLEtBQUssRUFBRSxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsS0FBSyxFQUFFO2FBQzFDLENBQUM7WUFDRixPQUFPLElBQUksQ0FBQyxRQUFRLENBQUM7UUFDdkIsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUN2RCxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQy9CLENBQUM7WUFBQyxPQUFPLFlBQVksRUFBRSxDQUFDO2dCQUN0QixNQUFNLElBQUksY0FBYyxDQUFDLENBQUMsUUFBUSxFQUFFLFlBQVksQ0FBQyxFQUFFLG9EQUFvRCxDQUFDLENBQUM7WUFDM0csQ0FBQztZQUNELE1BQU0sUUFBUSxDQUFDO1FBQ2pCLENBQUM7SUFDSCxDQUFDO0lBRUQsd0dBQXdHO0lBQ2pHLEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxVQUFvQztRQUNqRSxJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQztlQUNqRyxVQUFVLENBQUMsTUFBTSxDQUFDLE1BQU0sS0FBSyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDM0csTUFBTSxJQUFJLFNBQVMsQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFDO1FBQ2hFLENBQUM7UUFDRCxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUM7WUFDbEMsT0FBTyxFQUFFLE1BQU0sRUFBRSx1QkFBdUIsRUFBRSxZQUFZLEVBQUUsVUFBVSxDQUFDLFFBQVE7WUFDM0UsY0FBYyxFQUFFLFVBQVUsQ0FBQyxTQUFTLEVBQUUsVUFBVSxFQUFFLFVBQVUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLE9BQU87U0FDdkYsRUFBRSw4QkFBOEIsVUFBVSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFDeEQsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQzVCLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQztZQUFFLE1BQU0sSUFBSSxLQUFLLENBQUMseURBQXlELENBQUMsQ0FBQztRQUMxRyxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksS0FBSyxDQUFDLElBQUk7UUFDZixJQUFJLENBQUMsUUFBUSxHQUFHLFNBQVMsQ0FBQztRQUMxQixJQUFJLENBQUMsVUFBVSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQzlCLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7UUFDaEMsSUFBSSxRQUFRO1lBQUUsTUFBTSxRQUFRLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ3BELE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUMvQixDQUFDO0lBRUQsS0FBSyxDQUFDLGNBQWMsQ0FBQyxXQUFtQjtRQUN0QyxJQUFJLFdBQVcsR0FBRyxZQUFZLENBQUM7UUFDL0IsT0FBTyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsV0FBVyxFQUFFLENBQUM7WUFDaEMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1lBQ3BDLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxzQkFBc0IsRUFBRSxDQUFDO1lBQy9DLElBQUksQ0FBQztnQkFDSCxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQztvQkFDbEQsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsY0FBYyxFQUFFLGNBQWMsRUFBRSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsRUFBRSxTQUFTLEVBQUUsS0FBSztpQkFDakYsQ0FBQyxDQUFDO2dCQUNILElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxHQUFHO29CQUFFLE9BQU87Z0JBQ3BDLFdBQVcsR0FBRyxVQUFVLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUM1QyxDQUFDO1lBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztnQkFDZixXQUFXLEdBQUcsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ3ZFLENBQUM7WUFDRCxNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNuQixDQUFDO1FBQ0QsTUFBTSxJQUFJLEtBQUssQ0FDYix1Q0FBdUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0IsUUFBUSxXQUFXLGtCQUFrQixNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQ2pKLENBQUM7SUFDSixDQUFDO0lBRUQsS0FBSyxDQUFDLFlBQVk7UUFDaEIsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsRUFBRSxpQkFBaUIsRUFBRSxjQUFjLEVBQUUsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDdEgsTUFBTSxPQUFPLEdBQVksT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7UUFDOUcsSUFBSSxPQUFPLE9BQU8sS0FBSyxRQUFRO1lBQUUsTUFBTSxJQUFJLEtBQUssQ0FBQyxtREFBbUQsQ0FBQyxDQUFDO1FBQ3RHLE9BQU8sT0FBTyxDQUFDO0lBQ2pCLENBQUM7SUFFRCw2RkFBNkY7SUFDN0YsS0FBSyxDQUFDLFNBQVMsQ0FBQyxPQUFpQixFQUFFLFVBQWtCO1FBQ25ELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxXQUFXLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxHQUFHLE9BQU8sQ0FBQyxFQUFFLEVBQUUsU0FBUyxFQUFFLGFBQWEsRUFBRSxDQUFDLENBQUM7UUFDdkgsSUFBSSxNQUFNLENBQUMsUUFBUSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQzFCLE1BQU0sSUFBSSxLQUFLLENBQUMsdUJBQXVCLFVBQVUsVUFBVSxNQUFNLENBQUMsUUFBUSxNQUFNLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDbEgsQ0FBQztRQUNELE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQztJQUN2QixDQUFDO0NBQ0YifQ==
|