endia 0.0.0-stage → 0.0.2
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/LICENSE +202 -0
- package/README.md +34 -2
- package/dist/archives/archive-file.d.ts +36 -0
- package/dist/archives/archive-file.js +71 -0
- package/dist/archives/build-context.d.ts +63 -0
- package/dist/archives/build-context.js +132 -0
- package/dist/archives/docker-ignore.d.ts +57 -0
- package/dist/archives/docker-ignore.js +250 -0
- package/dist/archives/fleet-archive.d.ts +33 -0
- package/dist/archives/fleet-archive.js +134 -0
- package/dist/compose-model.d.ts +32 -0
- package/dist/compose-model.js +39 -0
- package/dist/contract/archives.d.ts +14 -0
- package/dist/contract/archives.js +1 -0
- package/dist/contract/bases.d.ts +48 -0
- package/dist/contract/bases.js +1 -0
- package/dist/contract/clients.d.ts +29 -0
- package/dist/contract/clients.js +1 -0
- package/dist/contract/common.d.ts +30 -0
- package/dist/contract/common.js +1 -0
- package/dist/contract/compose.d.ts +27 -0
- package/dist/contract/compose.js +1 -0
- package/dist/contract/desktop.d.ts +269 -0
- package/dist/contract/desktop.js +1 -0
- package/dist/contract/execs.d.ts +61 -0
- package/dist/contract/execs.js +1 -0
- package/dist/contract/fleets.d.ts +136 -0
- package/dist/contract/fleets.js +1 -0
- package/dist/contract/images.d.ts +130 -0
- package/dist/contract/images.js +1 -0
- package/dist/contract/machines.d.ts +199 -0
- package/dist/contract/machines.js +1 -0
- package/dist/contract/operations.d.ts +28 -0
- package/dist/contract/operations.js +1 -0
- package/dist/contract/processes.d.ts +75 -0
- package/dist/contract/processes.js +1 -0
- package/dist/contract/purposes.d.ts +62 -0
- package/dist/contract/purposes.js +1 -0
- package/dist/contract/space.d.ts +114 -0
- package/dist/contract/space.js +1 -0
- package/dist/contract/stats.d.ts +58 -0
- package/dist/contract/stats.js +1 -0
- package/dist/contract/status.d.ts +151 -0
- package/dist/contract/status.js +1 -0
- package/dist/contract/volumes.d.ts +22 -0
- package/dist/contract/volumes.js +1 -0
- package/dist/eastindia-client.d.ts +118 -0
- package/dist/eastindia-client.js +142 -0
- package/dist/environment.d.ts +52 -0
- package/dist/environment.js +133 -0
- package/dist/errors.d.ts +81 -0
- package/dist/errors.js +108 -0
- package/dist/exec/exec-frame.d.ts +38 -0
- package/dist/exec/exec-frame.js +50 -0
- package/dist/exec/exec-session.d.ts +64 -0
- package/dist/exec/exec-session.js +117 -0
- package/dist/groups/archives.d.ts +78 -0
- package/dist/groups/archives.js +117 -0
- package/dist/groups/bases.d.ts +28 -0
- package/dist/groups/bases.js +39 -0
- package/dist/groups/certificates.d.ts +32 -0
- package/dist/groups/certificates.js +43 -0
- package/dist/groups/desktop.d.ts +143 -0
- package/dist/groups/desktop.js +182 -0
- package/dist/groups/execs.d.ts +110 -0
- package/dist/groups/execs.js +119 -0
- package/dist/groups/fleets.d.ts +206 -0
- package/dist/groups/fleets.js +255 -0
- package/dist/groups/images.d.ts +130 -0
- package/dist/groups/images.js +109 -0
- package/dist/groups/machines.d.ts +124 -0
- package/dist/groups/machines.js +127 -0
- package/dist/groups/operations.d.ts +127 -0
- package/dist/groups/operations.js +174 -0
- package/dist/groups/processes.d.ts +77 -0
- package/dist/groups/processes.js +75 -0
- package/dist/groups/stats.d.ts +29 -0
- package/dist/groups/stats.js +33 -0
- package/dist/groups/system.d.ts +33 -0
- package/dist/groups/system.js +40 -0
- package/dist/groups/volumes.d.ts +36 -0
- package/dist/groups/volumes.js +40 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +20 -0
- package/dist/purposes.d.ts +17 -0
- package/dist/purposes.js +21 -0
- package/dist/routes.d.ts +71 -0
- package/dist/routes.js +73 -0
- package/dist/transport/api-connection.d.ts +185 -0
- package/dist/transport/api-connection.js +396 -0
- package/dist/transport/connection-failures.d.ts +14 -0
- package/dist/transport/connection-failures.js +57 -0
- package/dist/transport/json-lines.d.ts +10 -0
- package/dist/transport/json-lines.js +23 -0
- package/dist/transport/query-text.d.ts +49 -0
- package/dist/transport/query-text.js +62 -0
- package/dist/transport/service-address.d.ts +30 -0
- package/dist/transport/service-address.js +37 -0
- package/dist/transport/tls-files.d.ts +9 -0
- package/dist/transport/tls-files.js +1 -0
- package/package.json +44 -5
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { Timestamp } from "./common.js";
|
|
2
|
+
/** The service's version, as `endia version` prints it. */
|
|
3
|
+
export interface VersionInfo {
|
|
4
|
+
readonly version: string;
|
|
5
|
+
readonly apiVersion: string;
|
|
6
|
+
readonly gitCommit: string;
|
|
7
|
+
readonly built: Timestamp;
|
|
8
|
+
readonly os: string;
|
|
9
|
+
readonly arch: string;
|
|
10
|
+
/** The version of the guest service the service installs in new machines, or null when it has none. */
|
|
11
|
+
readonly guestServiceVersion?: string | null;
|
|
12
|
+
}
|
|
13
|
+
/** The host's description, the configuration in effect and what needs attention, as `endia info` prints them. */
|
|
14
|
+
export interface SystemInfo {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly serverVersion: string;
|
|
17
|
+
readonly operatingSystem: string;
|
|
18
|
+
readonly osVersion: string;
|
|
19
|
+
readonly architecture: string;
|
|
20
|
+
readonly logicalProcessors: number;
|
|
21
|
+
readonly memoryTotalBytes: number;
|
|
22
|
+
readonly storageRoot: string;
|
|
23
|
+
readonly imageFolder: string;
|
|
24
|
+
readonly machines: number;
|
|
25
|
+
readonly machinesRunning: number;
|
|
26
|
+
readonly machinesExited: number;
|
|
27
|
+
readonly images: number;
|
|
28
|
+
readonly fleets: number;
|
|
29
|
+
readonly volumes: number;
|
|
30
|
+
readonly hyperV: HyperVStatus;
|
|
31
|
+
/** The service's configuration in effect. */
|
|
32
|
+
readonly config: HostConfigurationView;
|
|
33
|
+
readonly warnings: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
/** Whether Hyper-V can run machines, and the switch that gives them the internet. */
|
|
36
|
+
export interface HyperVStatus {
|
|
37
|
+
readonly ready: boolean;
|
|
38
|
+
readonly reason?: string | null;
|
|
39
|
+
readonly internetSwitch: string;
|
|
40
|
+
readonly internetSwitchPresent: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** The host's configuration as `endia info` answers it: each key of `config\policy.json` with the value in effect, under the key's camel-case name. The service fills it from its configuration, and the members, their order and their nullability are the wire. This type holds no logic and no defaults, so every member is required. */
|
|
43
|
+
export interface HostConfigurationView {
|
|
44
|
+
/** The `imageFolder` key in effect: the absolute path of the image folder, or null when it is not set. */
|
|
45
|
+
readonly imageFolder: string | null;
|
|
46
|
+
/** The `listenAddress` key in effect. */
|
|
47
|
+
readonly listenAddress: string;
|
|
48
|
+
/** The `listenPort` key in effect. */
|
|
49
|
+
readonly listenPort: number;
|
|
50
|
+
/** The `resourcePrefix` key in effect. */
|
|
51
|
+
readonly resourcePrefix: string;
|
|
52
|
+
/** The `internetSwitch` key in effect. */
|
|
53
|
+
readonly internetSwitch: string;
|
|
54
|
+
/** The `hostReserveMiB` key in effect. */
|
|
55
|
+
readonly hostReserveMiB: number;
|
|
56
|
+
/** The `minFreeDiskGiB` key in effect. */
|
|
57
|
+
readonly minFreeDiskGiB: number;
|
|
58
|
+
/** The `builderMemoryMiB` key in effect. */
|
|
59
|
+
readonly builderMemoryMiB: number;
|
|
60
|
+
/** The `builderProcessorCount` key in effect. */
|
|
61
|
+
readonly builderProcessorCount: number;
|
|
62
|
+
/** The `basePrepareDiskGiB` key in effect. */
|
|
63
|
+
readonly basePrepareDiskGiB: number;
|
|
64
|
+
/** The `basePrepareTimeoutSeconds` key in effect. */
|
|
65
|
+
readonly basePrepareTimeoutSeconds: number;
|
|
66
|
+
/** The `guestProgramsFolder` key in effect, or null when it is not set. */
|
|
67
|
+
readonly guestProgramsFolder: string | null;
|
|
68
|
+
/** The `machineMemoryMiB` key in effect. */
|
|
69
|
+
readonly machineMemoryMiB: number;
|
|
70
|
+
/** The `machineMemoryFloorMiB` key in effect. */
|
|
71
|
+
readonly machineMemoryFloorMiB: number;
|
|
72
|
+
/** The `machineMemoryCeilingMiB` key in effect. */
|
|
73
|
+
readonly machineMemoryCeilingMiB: number;
|
|
74
|
+
/** The `machineProcessorCount` key in effect. */
|
|
75
|
+
readonly machineProcessorCount: number;
|
|
76
|
+
/** The `maxLayerDepth` key in effect. */
|
|
77
|
+
readonly maxLayerDepth: number;
|
|
78
|
+
/** The `maxParallelBoots` key in effect. */
|
|
79
|
+
readonly maxParallelBoots: number;
|
|
80
|
+
/** The `guestConnectTimeoutSeconds` key in effect. */
|
|
81
|
+
readonly guestConnectTimeoutSeconds: number;
|
|
82
|
+
/** The `guestReadyTimeoutSeconds` key in effect. */
|
|
83
|
+
readonly guestReadyTimeoutSeconds: number;
|
|
84
|
+
/** The `desktopReadyTimeoutSeconds` key in effect. */
|
|
85
|
+
readonly desktopReadyTimeoutSeconds: number;
|
|
86
|
+
/** The `setupTimeoutSeconds` key in effect. */
|
|
87
|
+
readonly setupTimeoutSeconds: number;
|
|
88
|
+
/** The `stopTimeoutSeconds` key in effect. */
|
|
89
|
+
readonly stopTimeoutSeconds: number;
|
|
90
|
+
/** The `buildStepTimeoutSeconds` key in effect. */
|
|
91
|
+
readonly buildStepTimeoutSeconds: number;
|
|
92
|
+
/** The `restartTimeoutSeconds` key in effect. */
|
|
93
|
+
readonly restartTimeoutSeconds: number;
|
|
94
|
+
/** The `desktopActionTimeoutSeconds` key in effect. */
|
|
95
|
+
readonly desktopActionTimeoutSeconds: number;
|
|
96
|
+
/** The `uacSettleSeconds` key in effect. */
|
|
97
|
+
readonly uacSettleSeconds: number;
|
|
98
|
+
/** The `restartDelayInitialMilliseconds` key in effect. */
|
|
99
|
+
readonly restartDelayInitialMilliseconds: number;
|
|
100
|
+
/** The `restartDelayMaxSeconds` key in effect. */
|
|
101
|
+
readonly restartDelayMaxSeconds: number;
|
|
102
|
+
/** The `restartResetAfterSeconds` key in effect. */
|
|
103
|
+
readonly restartResetAfterSeconds: number;
|
|
104
|
+
/** The `defaultAddressPools` key in effect, or null when it is not set. */
|
|
105
|
+
readonly defaultAddressPools: readonly AddressPoolView[] | null;
|
|
106
|
+
/** The `volumeSizeGiB` key in effect. */
|
|
107
|
+
readonly volumeSizeGiB: number;
|
|
108
|
+
/** The `machineLogMaxBytes` key in effect. */
|
|
109
|
+
readonly machineLogMaxBytes: number;
|
|
110
|
+
/** The `statsIntervalSeconds` key in effect. */
|
|
111
|
+
readonly statsIntervalSeconds: number;
|
|
112
|
+
/** The `operationRetentionDays` key in effect. */
|
|
113
|
+
readonly operationRetentionDays: number;
|
|
114
|
+
/** The `idempotencyRetentionHours` key in effect. */
|
|
115
|
+
readonly idempotencyRetentionHours: number;
|
|
116
|
+
/** The `serviceLogRetentionDays` key in effect. */
|
|
117
|
+
readonly serviceLogRetentionDays: number;
|
|
118
|
+
/** The `retentionSweepMinutes` key in effect. */
|
|
119
|
+
readonly retentionSweepMinutes: number;
|
|
120
|
+
/** The `maxContextBytes` key in effect. */
|
|
121
|
+
readonly maxContextBytes: number;
|
|
122
|
+
/** The `clientCertificateValidityDays` key in effect. */
|
|
123
|
+
readonly clientCertificateValidityDays: number;
|
|
124
|
+
/** The `evaluationWarningDays` key in effect. */
|
|
125
|
+
readonly evaluationWarningDays: number;
|
|
126
|
+
/** The `hyperVAccessFolder` key in effect, or null when it is not set. */
|
|
127
|
+
readonly hyperVAccessFolder: string | null;
|
|
128
|
+
/** The `hyperVEndpoint` key in effect, or null when it is not set. */
|
|
129
|
+
readonly hyperVEndpoint: string | null;
|
|
130
|
+
/** The `hostPathMap` key in effect, or null when it is not set. */
|
|
131
|
+
readonly hostPathMap: string | null;
|
|
132
|
+
/** The `guestRelay` key in effect, or null when it is not set. */
|
|
133
|
+
readonly guestRelay: string | null;
|
|
134
|
+
/** The `hyperVRequestTimeoutSeconds` key in effect. */
|
|
135
|
+
readonly hyperVRequestTimeoutSeconds: number;
|
|
136
|
+
/** The `hyperVJobTimeoutSeconds` key in effect. */
|
|
137
|
+
readonly hyperVJobTimeoutSeconds: number;
|
|
138
|
+
/** The `turnOffTimeoutSeconds` key in effect. */
|
|
139
|
+
readonly turnOffTimeoutSeconds: number;
|
|
140
|
+
/** The `sshReadyTimeoutSeconds` key in effect. */
|
|
141
|
+
readonly sshReadyTimeoutSeconds: number;
|
|
142
|
+
/** The `statsReadTimeoutSeconds` key in effect. */
|
|
143
|
+
readonly statsReadTimeoutSeconds: number;
|
|
144
|
+
}
|
|
145
|
+
/** One entry of the `defaultAddressPools` key as `HostConfigurationView` writes it: a range of IPv4 addresses and the prefix length of the subnets cut from it. */
|
|
146
|
+
export interface AddressPoolView {
|
|
147
|
+
/** The range in CIDR form, for example `10.231.0.0/16`. */
|
|
148
|
+
readonly base: string;
|
|
149
|
+
/** The prefix length of each subnet cut from the range, for example `24`. */
|
|
150
|
+
readonly size: number;
|
|
151
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Timestamp } from "./common.js";
|
|
2
|
+
/** One named volume as `endia volume ls` and `endia volume inspect` show it: its disk, its size, the fleet that created it and the machine it is attached to. */
|
|
3
|
+
export interface VolumeDetail {
|
|
4
|
+
/** The volume's name. */
|
|
5
|
+
readonly name: string;
|
|
6
|
+
/** The driver, always `local`. */
|
|
7
|
+
readonly driver: string;
|
|
8
|
+
/** The full path of the volume's disk on the host. */
|
|
9
|
+
readonly mountpoint: string;
|
|
10
|
+
/** The volume's labels from the Compose model. */
|
|
11
|
+
readonly labels: Readonly<Record<string, string>>;
|
|
12
|
+
/** The most bytes the disk may grow to. */
|
|
13
|
+
readonly sizeBytes: number;
|
|
14
|
+
/** The size of the disk's file now, in bytes, or null when the file cannot be read. */
|
|
15
|
+
readonly usedBytes: number | null;
|
|
16
|
+
/** The name of the fleet whose `compose up` created the volume. */
|
|
17
|
+
readonly fleet: string;
|
|
18
|
+
/** The name of the machine the volume is attached to, or null when it is attached to none. */
|
|
19
|
+
readonly attachedTo: string | null;
|
|
20
|
+
/** When the volume was created. */
|
|
21
|
+
readonly created: Timestamp;
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { SystemInfo, VersionInfo } from "./contract/status.js";
|
|
2
|
+
import { ArchivesClient } from "./groups/archives.js";
|
|
3
|
+
import { BasesClient } from "./groups/bases.js";
|
|
4
|
+
import { CertificatesClient } from "./groups/certificates.js";
|
|
5
|
+
import { DesktopClient } from "./groups/desktop.js";
|
|
6
|
+
import { ExecsClient } from "./groups/execs.js";
|
|
7
|
+
import { FleetsClient } from "./groups/fleets.js";
|
|
8
|
+
import { ImagesClient } from "./groups/images.js";
|
|
9
|
+
import { MachinesClient } from "./groups/machines.js";
|
|
10
|
+
import { OperationsClient } from "./groups/operations.js";
|
|
11
|
+
import { ProcessesClient } from "./groups/processes.js";
|
|
12
|
+
import { StatsClient } from "./groups/stats.js";
|
|
13
|
+
import { SystemClient } from "./groups/system.js";
|
|
14
|
+
import { VolumesClient } from "./groups/volumes.js";
|
|
15
|
+
import type { TlsFiles } from "./transport/tls-files.js";
|
|
16
|
+
/** Everything a client needs to reach one EastIndia service as one caller. */
|
|
17
|
+
export interface ClientOptions {
|
|
18
|
+
/**
|
|
19
|
+
* The service's address, in one of the two forms `EASTINDIA_HOST` takes.
|
|
20
|
+
*
|
|
21
|
+
* - `tcp://<host>:<port>`: the client connects over HTTPS with mutual TLS.
|
|
22
|
+
* - `npipe:////./pipe/<name>`: the client connects through a named pipe on this Windows PC. The pipe needs no certificate,
|
|
23
|
+
* because Windows checks which account opens it.
|
|
24
|
+
*/
|
|
25
|
+
readonly host: string;
|
|
26
|
+
/** The certificate files as PEM text; required over TCP, unused on the named pipe. `readTlsFolder` reads them from a folder. */
|
|
27
|
+
readonly tls?: TlsFiles;
|
|
28
|
+
/**
|
|
29
|
+
* The time allowed for one request, in milliseconds; 100 seconds when undefined. It does not apply to an upload, a streamed
|
|
30
|
+
* answer such as a followed log, a wait the service answers only when it ends, or a request sent once.
|
|
31
|
+
*/
|
|
32
|
+
readonly requestTimeoutMilliseconds?: number;
|
|
33
|
+
/** How many times a changing request is sent again after its connection failed before any reply; 2 when undefined. */
|
|
34
|
+
readonly requestRetries?: number;
|
|
35
|
+
/** The time between those attempts, in milliseconds; 1000 when undefined. */
|
|
36
|
+
readonly requestRetryDelayMilliseconds?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The typed client of the EastIndia API. EastIndia is a Windows service that runs Hyper-V Windows machines for tests, as Docker runs
|
|
40
|
+
* containers. A caller creates one client, with `fromEnv` or with explicit options, and calls the route groups below.
|
|
41
|
+
*
|
|
42
|
+
* - `images` builds and keeps images; `bases` imports the images that have no parent.
|
|
43
|
+
* - `fleets` brings Compose projects up and down; `machines` and `volumes` show and control what fleets made.
|
|
44
|
+
* - `execs` runs programs in machines, and `archives` copies files into and out of them.
|
|
45
|
+
* - `processes` waits for and stops the programs in a machine; `desktop` operates the windows of the signed-in user's programs.
|
|
46
|
+
* - `operations` follows the long work the other groups start; `stats` and `system` read and reclaim the host's resources.
|
|
47
|
+
* - `certificates` issues and revokes client certificates, on the named pipe only.
|
|
48
|
+
*
|
|
49
|
+
* Every call reports a failure as one of two errors. `EastIndiaApiError` means the service answered and refused, with the API's error
|
|
50
|
+
* code; `EastIndiaConnectionError` means no answer came.
|
|
51
|
+
*/
|
|
52
|
+
export declare class EastIndiaClient {
|
|
53
|
+
private readonly connection;
|
|
54
|
+
/** The image routes: build, list, inspect, tag and remove. */
|
|
55
|
+
readonly images: ImagesClient;
|
|
56
|
+
/** The base routes: import and prepare. */
|
|
57
|
+
readonly bases: BasesClient;
|
|
58
|
+
/** The operation routes: list, inspect, read the log and cancel; and `handle`, which wraps an operation to wait for its end. */
|
|
59
|
+
readonly operations: OperationsClient;
|
|
60
|
+
/** The fleet routes: up and its dry run, down, stop, start, restart, list, inspect and logs. */
|
|
61
|
+
readonly fleets: FleetsClient;
|
|
62
|
+
/** The machine routes: list, inspect, start, stop, restart and logs. */
|
|
63
|
+
readonly machines: MachinesClient;
|
|
64
|
+
/** The named volume routes: list, inspect and remove. */
|
|
65
|
+
readonly volumes: VolumesClient;
|
|
66
|
+
/** The exec routes: run a program attached or detached. */
|
|
67
|
+
readonly execs: ExecsClient;
|
|
68
|
+
/** The archive routes: copy files into and out of a machine. */
|
|
69
|
+
readonly archives: ArchivesClient;
|
|
70
|
+
/** The process routes: list, inspect, wait, stop and logs. */
|
|
71
|
+
readonly processes: ProcessesClient;
|
|
72
|
+
/** The desktop routes: launch programs and operate their windows. */
|
|
73
|
+
readonly desktop: DesktopClient;
|
|
74
|
+
/** The readings of the machines and of the host. */
|
|
75
|
+
readonly stats: StatsClient;
|
|
76
|
+
/** The disk space routes: df and prune. */
|
|
77
|
+
readonly system: SystemClient;
|
|
78
|
+
/** The client certificate routes, which the service answers on its named pipe only. */
|
|
79
|
+
readonly certificates: CertificatesClient;
|
|
80
|
+
/**
|
|
81
|
+
* Creates a client. No request is sent until a route is called.
|
|
82
|
+
*
|
|
83
|
+
* @param options - The address, the certificate files and the retries.
|
|
84
|
+
* @throws Error when the address has neither form, or when an address over TCP comes without the certificate files.
|
|
85
|
+
*/
|
|
86
|
+
constructor(options: ClientOptions);
|
|
87
|
+
/**
|
|
88
|
+
* Creates a client that connects as the `endia` CLI does. The address comes from the first of these that is set:
|
|
89
|
+
*
|
|
90
|
+
* 1. `EASTINDIA_HOST`, with the certificate files in `EASTINDIA_CERT_PATH`;
|
|
91
|
+
* 2. the context `EASTINDIA_CONTEXT` names;
|
|
92
|
+
* 3. the CLI's current context in `~/.eastindia`;
|
|
93
|
+
* 4. the CLI's built-in context: the named pipe on Windows, `tcp://127.0.0.1:7443` elsewhere.
|
|
94
|
+
*
|
|
95
|
+
* The retries are the CLI's `requestRetries` and `requestRetryDelayMilliseconds`.
|
|
96
|
+
*
|
|
97
|
+
* @param environment - The environment variables; the process's own when undefined.
|
|
98
|
+
* @returns The client.
|
|
99
|
+
* @throws Error when the CLI's settings are not valid, the context named does not exist, or a certificate file cannot be read.
|
|
100
|
+
*/
|
|
101
|
+
static fromEnv(environment?: Readonly<Record<string, string | undefined>>): EastIndiaClient;
|
|
102
|
+
/**
|
|
103
|
+
* Reads the service's version: `endia version`.
|
|
104
|
+
*
|
|
105
|
+
* @param signal - Cancels the request.
|
|
106
|
+
* @returns The version.
|
|
107
|
+
*/
|
|
108
|
+
version(signal?: AbortSignal): Promise<VersionInfo>;
|
|
109
|
+
/**
|
|
110
|
+
* Reads the host's description, the configuration in effect and what needs attention: `endia info`.
|
|
111
|
+
*
|
|
112
|
+
* @param signal - Cancels the request.
|
|
113
|
+
* @returns The description.
|
|
114
|
+
*/
|
|
115
|
+
info(signal?: AbortSignal): Promise<SystemInfo>;
|
|
116
|
+
/** Closes the connections kept open for later requests. Each request sent after `close` opens a new connection. */
|
|
117
|
+
close(): void;
|
|
118
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { connectionFromEnvironment } from "./environment.js";
|
|
2
|
+
import { ArchivesClient } from "./groups/archives.js";
|
|
3
|
+
import { BasesClient } from "./groups/bases.js";
|
|
4
|
+
import { CertificatesClient } from "./groups/certificates.js";
|
|
5
|
+
import { DesktopClient } from "./groups/desktop.js";
|
|
6
|
+
import { ExecsClient } from "./groups/execs.js";
|
|
7
|
+
import { FleetsClient } from "./groups/fleets.js";
|
|
8
|
+
import { ImagesClient } from "./groups/images.js";
|
|
9
|
+
import { MachinesClient } from "./groups/machines.js";
|
|
10
|
+
import { OperationsClient } from "./groups/operations.js";
|
|
11
|
+
import { ProcessesClient } from "./groups/processes.js";
|
|
12
|
+
import { StatsClient } from "./groups/stats.js";
|
|
13
|
+
import { SystemClient } from "./groups/system.js";
|
|
14
|
+
import { VolumesClient } from "./groups/volumes.js";
|
|
15
|
+
import { Routes } from "./routes.js";
|
|
16
|
+
import { ApiConnection } from "./transport/api-connection.js";
|
|
17
|
+
import { parseServiceAddress } from "./transport/service-address.js";
|
|
18
|
+
/** The time allowed for one request when the options give none: the CLI's own. */
|
|
19
|
+
const DefaultRequestTimeoutMilliseconds = 100_000;
|
|
20
|
+
/** The retries when the options give none: the defaults of the CLI's `requestRetries` and `requestRetryDelayMilliseconds`. */
|
|
21
|
+
const DefaultRequestRetries = 2;
|
|
22
|
+
const DefaultRequestRetryDelayMilliseconds = 1000;
|
|
23
|
+
/**
|
|
24
|
+
* The typed client of the EastIndia API. EastIndia is a Windows service that runs Hyper-V Windows machines for tests, as Docker runs
|
|
25
|
+
* containers. A caller creates one client, with `fromEnv` or with explicit options, and calls the route groups below.
|
|
26
|
+
*
|
|
27
|
+
* - `images` builds and keeps images; `bases` imports the images that have no parent.
|
|
28
|
+
* - `fleets` brings Compose projects up and down; `machines` and `volumes` show and control what fleets made.
|
|
29
|
+
* - `execs` runs programs in machines, and `archives` copies files into and out of them.
|
|
30
|
+
* - `processes` waits for and stops the programs in a machine; `desktop` operates the windows of the signed-in user's programs.
|
|
31
|
+
* - `operations` follows the long work the other groups start; `stats` and `system` read and reclaim the host's resources.
|
|
32
|
+
* - `certificates` issues and revokes client certificates, on the named pipe only.
|
|
33
|
+
*
|
|
34
|
+
* Every call reports a failure as one of two errors. `EastIndiaApiError` means the service answered and refused, with the API's error
|
|
35
|
+
* code; `EastIndiaConnectionError` means no answer came.
|
|
36
|
+
*/
|
|
37
|
+
export class EastIndiaClient {
|
|
38
|
+
connection;
|
|
39
|
+
/** The image routes: build, list, inspect, tag and remove. */
|
|
40
|
+
images;
|
|
41
|
+
/** The base routes: import and prepare. */
|
|
42
|
+
bases;
|
|
43
|
+
/** The operation routes: list, inspect, read the log and cancel; and `handle`, which wraps an operation to wait for its end. */
|
|
44
|
+
operations;
|
|
45
|
+
/** The fleet routes: up and its dry run, down, stop, start, restart, list, inspect and logs. */
|
|
46
|
+
fleets;
|
|
47
|
+
/** The machine routes: list, inspect, start, stop, restart and logs. */
|
|
48
|
+
machines;
|
|
49
|
+
/** The named volume routes: list, inspect and remove. */
|
|
50
|
+
volumes;
|
|
51
|
+
/** The exec routes: run a program attached or detached. */
|
|
52
|
+
execs;
|
|
53
|
+
/** The archive routes: copy files into and out of a machine. */
|
|
54
|
+
archives;
|
|
55
|
+
/** The process routes: list, inspect, wait, stop and logs. */
|
|
56
|
+
processes;
|
|
57
|
+
/** The desktop routes: launch programs and operate their windows. */
|
|
58
|
+
desktop;
|
|
59
|
+
/** The readings of the machines and of the host. */
|
|
60
|
+
stats;
|
|
61
|
+
/** The disk space routes: df and prune. */
|
|
62
|
+
system;
|
|
63
|
+
/** The client certificate routes, which the service answers on its named pipe only. */
|
|
64
|
+
certificates;
|
|
65
|
+
/**
|
|
66
|
+
* Creates a client. No request is sent until a route is called.
|
|
67
|
+
*
|
|
68
|
+
* @param options - The address, the certificate files and the retries.
|
|
69
|
+
* @throws Error when the address has neither form, or when an address over TCP comes without the certificate files.
|
|
70
|
+
*/
|
|
71
|
+
constructor(options) {
|
|
72
|
+
this.connection = EastIndiaClient.connect(parseServiceAddress(options.host, "host"), options);
|
|
73
|
+
this.operations = new OperationsClient(this.connection);
|
|
74
|
+
this.images = new ImagesClient(this.connection, this.operations);
|
|
75
|
+
this.bases = new BasesClient(this.connection, this.operations);
|
|
76
|
+
this.fleets = new FleetsClient(this.connection, this.operations);
|
|
77
|
+
this.machines = new MachinesClient(this.connection, this.operations);
|
|
78
|
+
this.volumes = new VolumesClient(this.connection);
|
|
79
|
+
this.execs = new ExecsClient(this.connection);
|
|
80
|
+
this.archives = new ArchivesClient(this.connection);
|
|
81
|
+
this.processes = new ProcessesClient(this.connection);
|
|
82
|
+
this.desktop = new DesktopClient(this.connection);
|
|
83
|
+
this.stats = new StatsClient(this.connection);
|
|
84
|
+
this.system = new SystemClient(this.connection, this.operations);
|
|
85
|
+
this.certificates = new CertificatesClient(this.connection);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Creates a client that connects as the `endia` CLI does. The address comes from the first of these that is set:
|
|
89
|
+
*
|
|
90
|
+
* 1. `EASTINDIA_HOST`, with the certificate files in `EASTINDIA_CERT_PATH`;
|
|
91
|
+
* 2. the context `EASTINDIA_CONTEXT` names;
|
|
92
|
+
* 3. the CLI's current context in `~/.eastindia`;
|
|
93
|
+
* 4. the CLI's built-in context: the named pipe on Windows, `tcp://127.0.0.1:7443` elsewhere.
|
|
94
|
+
*
|
|
95
|
+
* The retries are the CLI's `requestRetries` and `requestRetryDelayMilliseconds`.
|
|
96
|
+
*
|
|
97
|
+
* @param environment - The environment variables; the process's own when undefined.
|
|
98
|
+
* @returns The client.
|
|
99
|
+
* @throws Error when the CLI's settings are not valid, the context named does not exist, or a certificate file cannot be read.
|
|
100
|
+
*/
|
|
101
|
+
static fromEnv(environment = process.env) {
|
|
102
|
+
const chosen = connectionFromEnvironment(environment);
|
|
103
|
+
return new EastIndiaClient({
|
|
104
|
+
host: chosen.host,
|
|
105
|
+
tls: chosen.tls,
|
|
106
|
+
requestRetries: chosen.requestRetries,
|
|
107
|
+
requestRetryDelayMilliseconds: chosen.requestRetryDelayMilliseconds,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Reads the service's version: `endia version`.
|
|
112
|
+
*
|
|
113
|
+
* @param signal - Cancels the request.
|
|
114
|
+
* @returns The version.
|
|
115
|
+
*/
|
|
116
|
+
version(signal) {
|
|
117
|
+
return this.connection.read(Routes.version, signal);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Reads the host's description, the configuration in effect and what needs attention: `endia info`.
|
|
121
|
+
*
|
|
122
|
+
* @param signal - Cancels the request.
|
|
123
|
+
* @returns The description.
|
|
124
|
+
*/
|
|
125
|
+
info(signal) {
|
|
126
|
+
return this.connection.read(Routes.info, signal);
|
|
127
|
+
}
|
|
128
|
+
/** Closes the connections kept open for later requests. Each request sent after `close` opens a new connection. */
|
|
129
|
+
close() {
|
|
130
|
+
this.connection.close();
|
|
131
|
+
}
|
|
132
|
+
/** @internal Opens the connection; tests reach a local socket through it, which `host` has no form for. */
|
|
133
|
+
static connect(address, options) {
|
|
134
|
+
return new ApiConnection({
|
|
135
|
+
address,
|
|
136
|
+
tls: options.tls,
|
|
137
|
+
requestTimeoutMilliseconds: options.requestTimeoutMilliseconds ?? DefaultRequestTimeoutMilliseconds,
|
|
138
|
+
requestRetries: options.requestRetries ?? DefaultRequestRetries,
|
|
139
|
+
requestRetryDelayMilliseconds: options.requestRetryDelayMilliseconds ?? DefaultRequestRetryDelayMilliseconds,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TlsFiles } from "./transport/tls-files.js";
|
|
2
|
+
/** What `EastIndiaClient.fromEnv` connects with, as the `endia` CLI would choose it. */
|
|
3
|
+
export interface EnvironmentConnection {
|
|
4
|
+
/** The address, `tcp://<host>:<port>` or `npipe:////./pipe/<name>`. */
|
|
5
|
+
readonly host: string;
|
|
6
|
+
/** The certificate files, read for an address over TCP; undefined for a named pipe. */
|
|
7
|
+
readonly tls: TlsFiles | undefined;
|
|
8
|
+
/** The CLI's `requestRetries`. */
|
|
9
|
+
readonly requestRetries: number;
|
|
10
|
+
/** The CLI's `requestRetryDelayMilliseconds`. */
|
|
11
|
+
readonly requestRetryDelayMilliseconds: number;
|
|
12
|
+
}
|
|
13
|
+
/** The environment variables the CLI reads to choose its connection, and the folder that holds its settings and contexts. */
|
|
14
|
+
export declare const EnvironmentVariables: {
|
|
15
|
+
/** The address, which wins over every context. */
|
|
16
|
+
readonly host: "EASTINDIA_HOST";
|
|
17
|
+
/** The folder of `ca.pem`, `cert.pem` and `key.pem` for the address in `EASTINDIA_HOST`. */
|
|
18
|
+
readonly certPath: "EASTINDIA_CERT_PATH";
|
|
19
|
+
/** The context to use instead of the current one. */
|
|
20
|
+
readonly context: "EASTINDIA_CONTEXT";
|
|
21
|
+
/** The configuration folder instead of `.eastindia` in the home folder. */
|
|
22
|
+
readonly config: "EASTINDIA_CONFIG";
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Chooses the connection as the `endia` CLI chooses it when it is given neither `--host` nor `--context`:
|
|
26
|
+
*
|
|
27
|
+
* 1. `EASTINDIA_HOST`, with the certificate files in `EASTINDIA_CERT_PATH`, or else in the configuration folder;
|
|
28
|
+
* 2. otherwise the context `EASTINDIA_CONTEXT` names;
|
|
29
|
+
* 3. otherwise `currentContext` in `config.json`;
|
|
30
|
+
* 4. otherwise the built-in context `default`: the named pipe on Windows, `tcp://127.0.0.1:7443` elsewhere with the certificate files
|
|
31
|
+
* in the configuration folder.
|
|
32
|
+
*
|
|
33
|
+
* The configuration folder is `EASTINDIA_CONFIG`, or else `.eastindia` in the home folder. A stored context lives where the CLI
|
|
34
|
+
* stores it, in Docker's layout: `contexts/meta/<SHA-256 of the name>/meta.json`, and the certificate files in
|
|
35
|
+
* `contexts/tls/<SHA-256 of the name>/eastindia/`.
|
|
36
|
+
*
|
|
37
|
+
* @param environment - The environment variables.
|
|
38
|
+
* @param home - The home folder.
|
|
39
|
+
* @param windows - Whether this computer runs Windows, which decides the built-in context's address.
|
|
40
|
+
* @returns The connection.
|
|
41
|
+
* @throws Error when `config.json` holds an unknown key or a value out of range, when the context named does not exist, or when a
|
|
42
|
+
* certificate file cannot be read; the message names the file or the context.
|
|
43
|
+
*/
|
|
44
|
+
export declare function connectionFromEnvironment(environment: Readonly<Record<string, string | undefined>>, home?: string, windows?: boolean): EnvironmentConnection;
|
|
45
|
+
/**
|
|
46
|
+
* Reads the three certificate files of a folder: `ca.pem`, `cert.pem` and `key.pem`.
|
|
47
|
+
*
|
|
48
|
+
* @param folder - The folder.
|
|
49
|
+
* @returns The files' text.
|
|
50
|
+
* @throws Error naming the file that is missing or cannot be read.
|
|
51
|
+
*/
|
|
52
|
+
export declare function readTlsFolder(folder: string): TlsFiles;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
/** The environment variables the CLI reads to choose its connection, and the folder that holds its settings and contexts. */
|
|
6
|
+
export const EnvironmentVariables = {
|
|
7
|
+
/** The address, which wins over every context. */
|
|
8
|
+
host: "EASTINDIA_HOST",
|
|
9
|
+
/** The folder of `ca.pem`, `cert.pem` and `key.pem` for the address in `EASTINDIA_HOST`. */
|
|
10
|
+
certPath: "EASTINDIA_CERT_PATH",
|
|
11
|
+
/** The context to use instead of the current one. */
|
|
12
|
+
context: "EASTINDIA_CONTEXT",
|
|
13
|
+
/** The configuration folder instead of `.eastindia` in the home folder. */
|
|
14
|
+
config: "EASTINDIA_CONFIG",
|
|
15
|
+
};
|
|
16
|
+
/** The configuration folder's name in the home folder, as Docker's is `.docker`. */
|
|
17
|
+
const ConfigFolderName = ".eastindia";
|
|
18
|
+
/** The settings file in the configuration folder. */
|
|
19
|
+
const SettingsFileName = "config.json";
|
|
20
|
+
/** The name of the built-in context, which no file stores. */
|
|
21
|
+
const DefaultContext = "default";
|
|
22
|
+
/** The built-in context's address on Windows: the installed service's named pipe. */
|
|
23
|
+
const DefaultPipeHost = "npipe:////./pipe/eastindia_engine";
|
|
24
|
+
/** The built-in context's address elsewhere: the service's port on this computer. */
|
|
25
|
+
const DefaultTcpHost = "tcp://127.0.0.1:7443";
|
|
26
|
+
/** The key under which a context's `meta.json` names its endpoint, as Docker's is `docker`. */
|
|
27
|
+
const EndpointKey = "eastindia";
|
|
28
|
+
/** The defaults of the settings that `config.json` may leave out, and their ranges. */
|
|
29
|
+
const RequestRetries = { default: 2, minimum: 0, maximum: 10 };
|
|
30
|
+
const RequestRetryDelayMilliseconds = { default: 1000, minimum: 100, maximum: 2147483647 };
|
|
31
|
+
/**
|
|
32
|
+
* Chooses the connection as the `endia` CLI chooses it when it is given neither `--host` nor `--context`:
|
|
33
|
+
*
|
|
34
|
+
* 1. `EASTINDIA_HOST`, with the certificate files in `EASTINDIA_CERT_PATH`, or else in the configuration folder;
|
|
35
|
+
* 2. otherwise the context `EASTINDIA_CONTEXT` names;
|
|
36
|
+
* 3. otherwise `currentContext` in `config.json`;
|
|
37
|
+
* 4. otherwise the built-in context `default`: the named pipe on Windows, `tcp://127.0.0.1:7443` elsewhere with the certificate files
|
|
38
|
+
* in the configuration folder.
|
|
39
|
+
*
|
|
40
|
+
* The configuration folder is `EASTINDIA_CONFIG`, or else `.eastindia` in the home folder. A stored context lives where the CLI
|
|
41
|
+
* stores it, in Docker's layout: `contexts/meta/<SHA-256 of the name>/meta.json`, and the certificate files in
|
|
42
|
+
* `contexts/tls/<SHA-256 of the name>/eastindia/`.
|
|
43
|
+
*
|
|
44
|
+
* @param environment - The environment variables.
|
|
45
|
+
* @param home - The home folder.
|
|
46
|
+
* @param windows - Whether this computer runs Windows, which decides the built-in context's address.
|
|
47
|
+
* @returns The connection.
|
|
48
|
+
* @throws Error when `config.json` holds an unknown key or a value out of range, when the context named does not exist, or when a
|
|
49
|
+
* certificate file cannot be read; the message names the file or the context.
|
|
50
|
+
*/
|
|
51
|
+
export function connectionFromEnvironment(environment, home = homedir(), windows = process.platform === "win32") {
|
|
52
|
+
const configFolder = path.resolve(environment[EnvironmentVariables.config] ?? path.join(home, ConfigFolderName));
|
|
53
|
+
const settings = readSettings(path.join(configFolder, SettingsFileName));
|
|
54
|
+
const chosen = chooseContext(environment, settings, configFolder, windows);
|
|
55
|
+
return {
|
|
56
|
+
host: chosen.host,
|
|
57
|
+
tls: chosen.host.startsWith("tcp://") ? readTlsFolder(chosen.certificateFolder) : undefined,
|
|
58
|
+
requestRetries: settings.requestRetries,
|
|
59
|
+
requestRetryDelayMilliseconds: settings.requestRetryDelayMilliseconds,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Reads the three certificate files of a folder: `ca.pem`, `cert.pem` and `key.pem`.
|
|
64
|
+
*
|
|
65
|
+
* @param folder - The folder.
|
|
66
|
+
* @returns The files' text.
|
|
67
|
+
* @throws Error naming the file that is missing or cannot be read.
|
|
68
|
+
*/
|
|
69
|
+
export function readTlsFolder(folder) {
|
|
70
|
+
const read = (name) => {
|
|
71
|
+
const file = path.join(folder, name);
|
|
72
|
+
try {
|
|
73
|
+
return readFileSync(file, "utf8");
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
throw new Error(`Cannot read ${name} at ${file}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
return { ca: read("ca.pem"), cert: read("cert.pem"), key: read("key.pem") };
|
|
80
|
+
}
|
|
81
|
+
function chooseContext(environment, settings, configFolder, windows) {
|
|
82
|
+
const host = environment[EnvironmentVariables.host];
|
|
83
|
+
if (host !== undefined) {
|
|
84
|
+
return { host, certificateFolder: environment[EnvironmentVariables.certPath] ?? configFolder };
|
|
85
|
+
}
|
|
86
|
+
const name = environment[EnvironmentVariables.context] ?? settings.currentContext ?? DefaultContext;
|
|
87
|
+
if (name === DefaultContext) {
|
|
88
|
+
return { host: windows ? DefaultPipeHost : DefaultTcpHost, certificateFolder: configFolder };
|
|
89
|
+
}
|
|
90
|
+
// Docker names a context's folders by the SHA-256 of its name, so that any name is a valid folder name.
|
|
91
|
+
const folderName = createHash("sha256").update(name, "utf8").digest("hex");
|
|
92
|
+
const metaFile = path.join(configFolder, "contexts", "meta", folderName, "meta.json");
|
|
93
|
+
if (!existsSync(metaFile)) {
|
|
94
|
+
throw new Error(`Context "${name}" does not exist: ${metaFile} is missing.`);
|
|
95
|
+
}
|
|
96
|
+
const meta = JSON.parse(readFileSync(metaFile, "utf8"));
|
|
97
|
+
const endpoint = meta.Endpoints?.[EndpointKey]?.Host;
|
|
98
|
+
if (endpoint === undefined) {
|
|
99
|
+
throw new Error(`${metaFile} names no ${EndpointKey} endpoint.`);
|
|
100
|
+
}
|
|
101
|
+
return { host: endpoint, certificateFolder: path.join(configFolder, "contexts", "tls", folderName, EndpointKey) };
|
|
102
|
+
}
|
|
103
|
+
// Reads config.json as the CLI reads it: an unknown key, or a value of the wrong type or out of range, is refused and named.
|
|
104
|
+
function readSettings(file) {
|
|
105
|
+
if (!existsSync(file)) {
|
|
106
|
+
return { currentContext: undefined, requestRetries: RequestRetries.default, requestRetryDelayMilliseconds: RequestRetryDelayMilliseconds.default };
|
|
107
|
+
}
|
|
108
|
+
const keys = JSON.parse(readFileSync(file, "utf8"));
|
|
109
|
+
for (const key of Object.keys(keys)) {
|
|
110
|
+
if (!["currentContext", "requestRetries", "requestRetryDelayMilliseconds"].includes(key)) {
|
|
111
|
+
throw new Error(`${file} holds the unknown key '${key}'.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const currentContext = keys["currentContext"];
|
|
115
|
+
if (currentContext !== undefined && currentContext !== null && typeof currentContext !== "string") {
|
|
116
|
+
throw new Error(`${file}: 'currentContext' must be a string.`);
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
currentContext: currentContext ?? undefined,
|
|
120
|
+
requestRetries: integerSetting(file, keys, "requestRetries", RequestRetries),
|
|
121
|
+
requestRetryDelayMilliseconds: integerSetting(file, keys, "requestRetryDelayMilliseconds", RequestRetryDelayMilliseconds),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function integerSetting(file, keys, key, range) {
|
|
125
|
+
const value = keys[key];
|
|
126
|
+
if (value === undefined) {
|
|
127
|
+
return range.default;
|
|
128
|
+
}
|
|
129
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < range.minimum || value > range.maximum) {
|
|
130
|
+
throw new Error(`${file}: '${key}' must be an integer from ${String(range.minimum)} to ${String(range.maximum)}.`);
|
|
131
|
+
}
|
|
132
|
+
return value;
|
|
133
|
+
}
|