@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,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@git.zone/cli": {
|
|
3
|
+
"schemaVersion": 2,
|
|
4
|
+
"projectType": "npm",
|
|
5
|
+
"module": {
|
|
6
|
+
"githost": "code.foss.global",
|
|
7
|
+
"gitscope": "foss.global",
|
|
8
|
+
"gitrepo": "forgefixtures",
|
|
9
|
+
"description": "Disposable, digest-pinned Gitea and GitLab CE fixtures with owned cleanup, local TLS and ground-truth seed data for forge integration tests.",
|
|
10
|
+
"npmPackagename": "@foss.global/forgefixtures",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"projectDomain": "foss.global",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"foss.global",
|
|
15
|
+
"gitea",
|
|
16
|
+
"gitlab",
|
|
17
|
+
"forge",
|
|
18
|
+
"fixtures",
|
|
19
|
+
"docker",
|
|
20
|
+
"integration testing",
|
|
21
|
+
"typescript"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"release": {
|
|
25
|
+
"preflight": {
|
|
26
|
+
"requireCleanTree": true,
|
|
27
|
+
"test": true,
|
|
28
|
+
"build": true
|
|
29
|
+
},
|
|
30
|
+
"targets": {
|
|
31
|
+
"git": {
|
|
32
|
+
"enabled": true,
|
|
33
|
+
"remote": "origin",
|
|
34
|
+
"pushBranch": true,
|
|
35
|
+
"pushTags": true
|
|
36
|
+
},
|
|
37
|
+
"npm": {
|
|
38
|
+
"enabled": true,
|
|
39
|
+
"registries": [
|
|
40
|
+
"https://verdaccio.lossless.digital",
|
|
41
|
+
"https://registry.npmjs.org"
|
|
42
|
+
],
|
|
43
|
+
"accessLevel": "public",
|
|
44
|
+
"alreadyPublished": "success"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
package/changelog.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2026-09-25 - 0.2.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add disposable, digest-pinned Gitea fixtures with owned cleanup (gitea)
|
|
8
|
+
- Start `gitea/gitea:1.27.3-rootless` by index digest behind an in-process TLS-terminating reverse proxy with a lifecycle-local certificate authority, and verify the reported version against the pin.
|
|
9
|
+
- Bootstrap an administrator and access tokens through Gitea's admin CLI; expose a scoped HTTPS client that trusts only the fixture CA and origin.
|
|
10
|
+
- Label every container, network and volume with lifecycle ownership and reap the resources of expired owners, of owners on this host that rebooted, and of exited owners in the same PID namespace before each start; a stop during start cancels it and removes what it created.
|
|
11
|
+
- Seed users, organizations, repositories, branches, labels, milestones, tags, issues, pull requests, releases and Git LFS objects through the documented APIs and return a manifest read back from Gitea.
|
|
12
|
+
- add disposable, digest-pinned GitLab CE fixtures on a rootless Docker daemon (gitlab)
|
|
13
|
+
- Start `gitlab/gitlab-ce:19.4.1-ce.0` by index digest as root on the current user's rootless daemon with GitLab's memory-constrained omnibus settings behind the TLS terminator, limited to 5 GiB memory, 4 CPUs and 4096 PIDs; ready once reconfigure finished, the container is healthy, the API answers and the pinned version is reported.
|
|
14
|
+
- Create personal access tokens through `gitlab-rails runner` with the script on stdin.
|
|
15
|
+
- Seed users, nested groups, public, internal and private projects, members, branches, labels, milestones, tags, issues (confidential issues and incidents), merge requests and releases through the REST API, fail when GitLab silently drops an attribute, and return a manifest read back from GitLab.
|
|
16
|
+
- Record the owner's machine ID in the ownership labels; owners on another machine are judged by expiry only, labels without a machine ID are never interpreted, and a process whose system has no valid `/etc/machine-id` uses a random ID fixed for its lifetime.
|
|
17
|
+
- Choose the published loopback port from the kernel's free ports, because a rootless daemon allocates ports blind to host listeners.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autocreated commitinfo by @push.rocks/commitinfo
|
|
3
|
+
*/
|
|
4
|
+
export const commitinfo = {
|
|
5
|
+
name: '@foss.global/forgefixtures',
|
|
6
|
+
version: '0.2.0',
|
|
7
|
+
description: 'Disposable, digest-pinned Gitea and GitLab CE fixtures with owned cleanup, local TLS and ground-truth seed data for forge integration tests.'
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSw0QkFBNEI7SUFDbEMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLDhJQUE4STtDQUM1SixDQUFBIn0=
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface IForgeFixtureServerCertificate {
|
|
2
|
+
certificatePem: string;
|
|
3
|
+
privateKeyPem: string;
|
|
4
|
+
}
|
|
5
|
+
export interface IForgeFixtureServerCertificateRequest {
|
|
6
|
+
ipAddresses: string[];
|
|
7
|
+
dnsNames: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A certificate authority that exists only in the memory of one fixture
|
|
11
|
+
* lifecycle. Its private key is never exported; callers receive the public CA
|
|
12
|
+
* certificate to trust and freshly issued server leaves.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ForgeFixtureCertificateAuthority {
|
|
15
|
+
#private;
|
|
16
|
+
static create(lifecycleIdArg: string, validityMsArg: number): Promise<ForgeFixtureCertificateAuthority>;
|
|
17
|
+
private constructor();
|
|
18
|
+
/** Public CA certificate for scoped trust by fixture clients. */
|
|
19
|
+
get certificatePem(): string;
|
|
20
|
+
/** Issues a server leaf within the CA validity for exactly the requested names. */
|
|
21
|
+
issueServerCertificate(requestArg: IForgeFixtureServerCertificateRequest): Promise<IForgeFixtureServerCertificate>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
const certificateBackdateMs = 5 * 60 * 1000;
|
|
3
|
+
const signingAlgorithm = { name: 'ECDSA', hash: 'SHA-256' };
|
|
4
|
+
const generateP256KeyPair = async () => globalThis.crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify']);
|
|
5
|
+
const exportPrivateKeyPem = async (privateKeyArg) => {
|
|
6
|
+
const der = await globalThis.crypto.subtle.exportKey('pkcs8', privateKeyArg);
|
|
7
|
+
return plugins.crypto.createPrivateKey({ key: Buffer.from(der), format: 'der', type: 'pkcs8' })
|
|
8
|
+
.export({ format: 'pem', type: 'pkcs8' }).toString();
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* A certificate authority that exists only in the memory of one fixture
|
|
12
|
+
* lifecycle. Its private key is never exported; callers receive the public CA
|
|
13
|
+
* certificate to trust and freshly issued server leaves.
|
|
14
|
+
*/
|
|
15
|
+
export class ForgeFixtureCertificateAuthority {
|
|
16
|
+
static async create(lifecycleIdArg, validityMsArg) {
|
|
17
|
+
if (!lifecycleIdArg || /[,=+<>#;"\\]/.test(lifecycleIdArg)) {
|
|
18
|
+
throw new TypeError('The certificate authority lifecycle id is malformed.');
|
|
19
|
+
}
|
|
20
|
+
if (!Number.isSafeInteger(validityMsArg) || validityMsArg < 60_000) {
|
|
21
|
+
throw new TypeError('The certificate authority validity must be an integer of at least 60000 ms.');
|
|
22
|
+
}
|
|
23
|
+
const keys = await generateP256KeyPair();
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const certificate = await plugins.x509.X509CertificateGenerator.createSelfSigned({
|
|
26
|
+
serialNumber: plugins.crypto.randomBytes(16).toString('hex'),
|
|
27
|
+
name: `CN=forgefixtures ${lifecycleIdArg}`,
|
|
28
|
+
notBefore: new Date(now - certificateBackdateMs),
|
|
29
|
+
notAfter: new Date(now + validityMsArg),
|
|
30
|
+
signingAlgorithm,
|
|
31
|
+
keys,
|
|
32
|
+
extensions: [
|
|
33
|
+
new plugins.x509.BasicConstraintsExtension(true, 0, true),
|
|
34
|
+
new plugins.x509.KeyUsagesExtension(plugins.x509.KeyUsageFlags.keyCertSign | plugins.x509.KeyUsageFlags.cRLSign, true),
|
|
35
|
+
await plugins.x509.SubjectKeyIdentifierExtension.create(keys.publicKey),
|
|
36
|
+
await plugins.x509.AuthorityKeyIdentifierExtension.create(keys.publicKey),
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
return new ForgeFixtureCertificateAuthority(certificate, keys.privateKey);
|
|
40
|
+
}
|
|
41
|
+
#certificate;
|
|
42
|
+
#signingKey;
|
|
43
|
+
constructor(certificateArg, signingKeyArg) {
|
|
44
|
+
this.#certificate = certificateArg;
|
|
45
|
+
this.#signingKey = signingKeyArg;
|
|
46
|
+
}
|
|
47
|
+
/** Public CA certificate for scoped trust by fixture clients. */
|
|
48
|
+
get certificatePem() {
|
|
49
|
+
return this.#certificate.toString('pem');
|
|
50
|
+
}
|
|
51
|
+
/** Issues a server leaf within the CA validity for exactly the requested names. */
|
|
52
|
+
async issueServerCertificate(requestArg) {
|
|
53
|
+
const names = [
|
|
54
|
+
...requestArg.ipAddresses.map((ipArg) => {
|
|
55
|
+
if (plugins.net.isIP(ipArg) === 0)
|
|
56
|
+
throw new TypeError(`"${ipArg}" is not an IP address.`);
|
|
57
|
+
return { type: 'ip', value: ipArg };
|
|
58
|
+
}),
|
|
59
|
+
...requestArg.dnsNames.map((dnsNameArg) => {
|
|
60
|
+
if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/.test(dnsNameArg)) {
|
|
61
|
+
throw new TypeError(`"${dnsNameArg}" is not an exact lowercase DNS name.`);
|
|
62
|
+
}
|
|
63
|
+
return { type: 'dns', value: dnsNameArg };
|
|
64
|
+
}),
|
|
65
|
+
];
|
|
66
|
+
if (names.length === 0)
|
|
67
|
+
throw new TypeError('A server certificate needs at least one name.');
|
|
68
|
+
const keys = await generateP256KeyPair();
|
|
69
|
+
const certificate = await plugins.x509.X509CertificateGenerator.create({
|
|
70
|
+
serialNumber: plugins.crypto.randomBytes(16).toString('hex'),
|
|
71
|
+
subject: `CN=${names[0].value}`,
|
|
72
|
+
issuer: this.#certificate.subjectName,
|
|
73
|
+
notBefore: this.#certificate.notBefore,
|
|
74
|
+
notAfter: this.#certificate.notAfter,
|
|
75
|
+
signingAlgorithm,
|
|
76
|
+
publicKey: keys.publicKey,
|
|
77
|
+
signingKey: this.#signingKey,
|
|
78
|
+
extensions: [
|
|
79
|
+
new plugins.x509.BasicConstraintsExtension(false, undefined, true),
|
|
80
|
+
new plugins.x509.KeyUsagesExtension(plugins.x509.KeyUsageFlags.digitalSignature, true),
|
|
81
|
+
new plugins.x509.ExtendedKeyUsageExtension([plugins.x509.ExtendedKeyUsage.serverAuth]),
|
|
82
|
+
new plugins.x509.SubjectAlternativeNameExtension(names),
|
|
83
|
+
await plugins.x509.SubjectKeyIdentifierExtension.create(keys.publicKey),
|
|
84
|
+
await plugins.x509.AuthorityKeyIdentifierExtension.create(this.#certificate.publicKey),
|
|
85
|
+
],
|
|
86
|
+
});
|
|
87
|
+
return {
|
|
88
|
+
certificatePem: certificate.toString('pem'),
|
|
89
|
+
privateKeyPem: await exportPrivateKeyPem(keys.privateKey),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5jZXJ0aWZpY2F0ZWF1dGhvcml0eS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMuY2VydGlmaWNhdGVhdXRob3JpdHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFZeEMsTUFBTSxxQkFBcUIsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQztBQUM1QyxNQUFNLGdCQUFnQixHQUFHLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUM7QUFFNUQsTUFBTSxtQkFBbUIsR0FBRyxLQUFLLElBQTRCLEVBQUUsQ0FDN0QsVUFBVSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLEVBQUUsSUFBSSxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUM7QUFFekcsTUFBTSxtQkFBbUIsR0FBRyxLQUFLLEVBQUUsYUFBd0IsRUFBbUIsRUFBRTtJQUM5RSxNQUFNLEdBQUcsR0FBRyxNQUFNLFVBQVUsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxPQUFPLEVBQUUsYUFBYSxDQUFDLENBQUM7SUFDN0UsT0FBTyxPQUFPLENBQUMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLEVBQUUsR0FBRyxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLENBQUM7U0FDNUYsTUFBTSxDQUFDLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztBQUN6RCxDQUFDLENBQUM7QUFFRjs7OztHQUlHO0FBQ0gsTUFBTSxPQUFPLGdDQUFn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
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
import { ForgeFixtureHttpClient } from './classes.httpclient.js';
|
|
3
|
+
import type { IForgeFixtureEndpoint, IForgeFixtureImage, IForgeFixtureLifecycleOptions, IForgeFixtureReapReport, TForgeFixtureKind } from './interfaces.js';
|
|
4
|
+
/** What a forge driver asks the lifecycle to run. */
|
|
5
|
+
export interface IForgeFixtureContainerSpec {
|
|
6
|
+
image: IForgeFixtureImage;
|
|
7
|
+
/** Explicit container user. Root requires `allowRootOnRootless` and a verified rootless daemon. */
|
|
8
|
+
user: string;
|
|
9
|
+
allowRootOnRootless?: true;
|
|
10
|
+
command: plugins.docker.TContainerCommand;
|
|
11
|
+
env: Record<string, string>;
|
|
12
|
+
/** Plain-HTTP port inside the container that the TLS terminator forwards to. */
|
|
13
|
+
httpPort: number;
|
|
14
|
+
tmpfsMounts?: plugins.docker.IContainerTmpfsMount[];
|
|
15
|
+
/** Named volumes created and removed by this lifecycle, mounted at `target`. */
|
|
16
|
+
volumes?: Array<{
|
|
17
|
+
purpose: string;
|
|
18
|
+
target: string;
|
|
19
|
+
}>;
|
|
20
|
+
memoryBytes: number;
|
|
21
|
+
nanoCpus: number;
|
|
22
|
+
pidsLimit: number;
|
|
23
|
+
shmSizeBytes?: number;
|
|
24
|
+
stopTimeoutSeconds: number;
|
|
25
|
+
healthcheck?: plugins.docker.IContainerHealthcheck;
|
|
26
|
+
}
|
|
27
|
+
export type TForgeFixtureContainerHealth = 'none' | 'starting' | 'healthy' | 'unhealthy';
|
|
28
|
+
export type TForgeFixtureLifecycleState = 'idle' | 'preparing' | 'prepared' | 'starting' | 'running' | 'stopping' | 'stopped';
|
|
29
|
+
/** Raised by lifecycle steps that observe a stop request. */
|
|
30
|
+
export declare class ForgeFixtureStoppedError extends Error {
|
|
31
|
+
constructor(kindArg: TForgeFixtureKind, optionsArg?: ErrorOptions);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Owns one fixture's Docker resources, local CA, TLS terminator and scoped
|
|
35
|
+
* HTTPS client. Every resource carries ownership labels, so resources left by a
|
|
36
|
+
* crashed process are removed by the next lifecycle's reaper run.
|
|
37
|
+
*/
|
|
38
|
+
export declare class ForgeFixtureContainerLifecycle {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(kindArg: TForgeFixtureKind, optionsArg: IForgeFixtureLifecycleOptions, startupTimeoutMsArg: number);
|
|
41
|
+
get state(): TForgeFixtureLifecycleState;
|
|
42
|
+
get startupTimeoutMs(): number;
|
|
43
|
+
get lifecycleId(): string;
|
|
44
|
+
get endpoint(): IForgeFixtureEndpoint;
|
|
45
|
+
get http(): ForgeFixtureHttpClient;
|
|
46
|
+
get dockerHost(): plugins.docker.DockerHost;
|
|
47
|
+
/** Aborted once `stop()` or `requestStop()` was called. */
|
|
48
|
+
get stopSignal(): AbortSignal;
|
|
49
|
+
/** Throws {@link ForgeFixtureStoppedError} once a stop was requested. */
|
|
50
|
+
assertNotStopping(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Once a stop was requested, any failure it provoked (an aborted pull, a
|
|
53
|
+
* refused request) is reported as {@link ForgeFixtureStoppedError} with the
|
|
54
|
+
* original failure as its cause.
|
|
55
|
+
*/
|
|
56
|
+
stopAwareError(errorArg: unknown): unknown;
|
|
57
|
+
/**
|
|
58
|
+
* Marks the lifecycle as stopping without waiting. Running steps observe it
|
|
59
|
+
* at their next checkpoint, an image pull is cancelled, and the lifecycle can
|
|
60
|
+
* no longer be prepared or started.
|
|
61
|
+
*/
|
|
62
|
+
requestStop(): void;
|
|
63
|
+
/** Report of the reaper run that preceded this lifecycle. */
|
|
64
|
+
get lastReapReport(): IForgeFixtureReapReport | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Reaps stale fixtures, creates ownership, CA and TLS terminator. The
|
|
67
|
+
* returned endpoint is final: its port is bound before the forge starts.
|
|
68
|
+
*/
|
|
69
|
+
prepare(): Promise<IForgeFixtureEndpoint>;
|
|
70
|
+
/** Pulls the pinned image, creates the owned network and container, starts it and wires the terminator. */
|
|
71
|
+
startContainer(specArg: IForgeFixtureContainerSpec): Promise<void>;
|
|
72
|
+
/** Runs argv in the container as its configured user. Never logs stdin or output. */
|
|
73
|
+
exec(commandArg: plugins.docker.TContainerCommand, optionsArg?: plugins.docker.IContainerExecOptions): Promise<plugins.docker.IContainerExecResult>;
|
|
74
|
+
/** Fails with the container's exit state and recent logs when it stopped running. */
|
|
75
|
+
assertContainerRunning(): Promise<void>;
|
|
76
|
+
/** Docker's view of the container healthcheck (`none` without a healthcheck). */
|
|
77
|
+
containerHealth(): Promise<TForgeFixtureContainerHealth>;
|
|
78
|
+
/** Recent container logs, for diagnostics after a failed readiness wait. */
|
|
79
|
+
logsTail(linesArg: number): Promise<string>;
|
|
80
|
+
/**
|
|
81
|
+
* Requests a stop, waits for a running prepare or container start to observe
|
|
82
|
+
* it, then removes every owned resource in dependency order, including those
|
|
83
|
+
* that step created. Idempotent. When a removal fails, the remaining
|
|
84
|
+
* resources stay recorded and a later call retries them; the failures are
|
|
85
|
+
* thrown together.
|
|
86
|
+
*/
|
|
87
|
+
stop(): Promise<void>;
|
|
88
|
+
}
|