incus-ts 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,63 +1,183 @@
1
1
  # incus-ts
2
2
 
3
- First step of a Bun-first TypeScript port of the Incus Go client.
3
+ Lightweight Bun-first TypeScript client for Incus.
4
4
 
5
- Current status: core runtime is implemented for transport, raw requests, server,
6
- operations, images, and a practical subset of instance operations. Other
7
- domains are scaffolded and currently return explicit "not implemented yet"
8
- errors.
5
+ The API is instance-handle oriented: get a handle with
6
+ `client.instances.instance(name)`, then call `.exec()`, `.setState()`, `.remove()`, etc.
9
7
 
10
- ## Goals
8
+ Operation-returning calls are awaitable by default.
11
9
 
12
- - Keep setup lightweight (Bun for install/build/test).
13
- - Preserve Incus Go client capability domains.
14
- - Provide a Gondolin-like ergonomic TypeScript surface:
15
- - static factories (`Incus.connect*`)
16
- - grouped resource APIs (`client.instances`, `client.networks`, ...)
17
- - chainable context scoping (`client.project(...).target(...)`)
10
+ ## Mental Model
18
11
 
19
- ## Quick look
12
+ 1. Connect once (`Incus.connect*`).
13
+ 2. Use collection APIs for listing/creating (`client.instances.*`).
14
+ 3. Use a per-instance handle for day-to-day work (`client.instances.instance(name)`).
15
+
16
+ ## Quick Start
20
17
 
21
18
  ```ts
22
19
  import { Incus } from "incus-ts";
23
20
 
24
- const client = await Incus.connect("https://incus.example");
21
+ const client = await Incus.connectUnix(); // default: /var/lib/incus/unix.socket
22
+ const instance = client.instances.instance("my-container");
23
+
24
+ const proc = instance.exec(
25
+ { command: ["sh", "-lc", "echo hello from incus-ts"], interactive: false },
26
+ { stdout: "pipe", stderr: "pipe" },
27
+ );
28
+
29
+ for await (const chunk of proc) {
30
+ process.stdout.write(new TextDecoder().decode(chunk));
31
+ }
32
+
33
+ const result = await proc;
34
+ console.log(result.exitCode, result.ok);
35
+ ```
36
+
37
+ ## Common Snippets (From E2E Flow)
25
38
 
26
- const scoped = client.project("my-project").target("node-1");
39
+ ### 1. Create a container (same base image style as e2e/gondolin setup)
27
40
 
28
- await scoped.instances.list({ type: "container", allProjects: false });
29
- await scoped.images.aliases.get("alpine/3.20");
41
+ ```ts
42
+ import { Incus } from "incus-ts";
43
+
44
+ const client = await Incus.connectUnix();
45
+ const name = `incus-ts-demo-${Date.now().toString(36)}`;
46
+
47
+ await client.instances.create({
48
+ name,
49
+ type: "container",
50
+ source: {
51
+ type: "image",
52
+ mode: "pull",
53
+ server: "https://images.linuxcontainers.org",
54
+ protocol: "simplestreams",
55
+ alias: "alpine/3.20",
56
+ },
57
+ });
58
+ ```
59
+
60
+ ### 2. Start it
61
+
62
+ ```ts
63
+ const instance = client.instances.instance(name);
64
+ await instance.setState({ action: "start", timeout: 180 });
65
+ ```
66
+
67
+ ### 3. Stream output while command is running
68
+
69
+ ```ts
70
+ const instance = client.instances.instance(name);
71
+
72
+ const proc = instance.exec(
73
+ { command: ["sh", "-lc", "echo stream:1; cat >/dev/null; echo stream:2"], interactive: false },
74
+ { stdout: "pipe", stderr: "pipe" },
75
+ );
30
76
 
31
- const instance = scoped.instances.instance("my-container");
32
- const proc = instance.exec({ command: ["sh", "-lc", "echo hello"] }, { stdout: "pipe" });
33
77
  for await (const chunk of proc) {
34
78
  process.stdout.write(new TextDecoder().decode(chunk));
35
79
  }
80
+
36
81
  const result = await proc;
37
- console.log(result.exitCode);
82
+ console.log(result.exitCode, result.ok);
83
+ ```
84
+
85
+ ### 4. Run a network check inside the container
86
+
87
+ ```ts
88
+ const instance = client.instances.instance(name);
89
+ const decoder = new TextDecoder();
90
+ let stdout = "";
91
+ let stderr = "";
92
+
93
+ const net = instance.exec(
94
+ {
95
+ command: [
96
+ "sh",
97
+ "-lc",
98
+ "GW=$(ip route | awk '/default/ {print $3; exit}'); "
99
+ + "ping -c 1 -W 2 \"${GW:-192.168.100.1}\" >/tmp/ping.out 2>&1; "
100
+ + "rc=$?; cat /tmp/ping.out; "
101
+ + "if [ \"$rc\" -eq 0 ]; then echo __PING_OK__; fi; "
102
+ + "exit \"$rc\"",
103
+ ],
104
+ interactive: false,
105
+ },
106
+ { stdout: "pipe", stderr: "pipe" },
107
+ );
108
+
109
+ const readStdout = (async () => {
110
+ for await (const chunk of net.stdout) stdout += decoder.decode(chunk, { stream: true });
111
+ stdout += decoder.decode();
112
+ })();
113
+ const readStderr = (async () => {
114
+ for await (const chunk of net.stderr) stderr += decoder.decode(chunk, { stream: true });
115
+ stderr += decoder.decode();
116
+ })();
117
+
118
+ const netResult = await net;
119
+ await Promise.all([readStdout, readStderr]);
120
+ if (!netResult.ok || !`${stdout}\n${stderr}`.includes("__PING_OK__")) {
121
+ throw new Error("network check failed");
122
+ }
123
+ ```
124
+
125
+ ### 5. Cleanup
126
+
127
+ ```ts
128
+ const instance = client.instances.instance(name);
129
+
130
+ try {
131
+ await instance.setState({ action: "stop", timeout: 30, force: true });
132
+ } catch {
133
+ // Instance might already be stopped.
134
+ }
135
+
136
+ await instance.remove();
137
+ client.disconnect();
138
+ ```
139
+
140
+ ### 6. Snapshot + restore
141
+
142
+ ```ts
143
+ const instance = client.instances.instance("my-container");
144
+
145
+ await instance.snapshots.create({ name: "snap0" });
146
+ await instance.restore("snap0");
147
+ ```
148
+
149
+ ### 7. Fork (copy) an instance
150
+
151
+ ```ts
152
+ const source = client.instances.instance("my-container");
153
+
154
+ // Clone current state
155
+ await source.fork("my-container-copy");
156
+
157
+ // Clone from a specific snapshot
158
+ await source.fork("my-container-from-snap", {
159
+ fromSnapshot: "snap0",
160
+ });
38
161
  ```
39
162
 
40
- ## Implemented now
163
+ ## Implemented
41
164
 
42
- - `connection`, `raw`
43
- - `server`
44
- - `operations`
45
- - `images` + `images.aliases` (simple streams remains unimplemented)
46
- - `instances` collection + per-instance handles (`instances.instance(name)`) for
47
- CRUD/state/exec/console/metadata/logs/files with websocket exec stream attach over Unix sockets
165
+ - `connection`, `raw`, `server`, `operations`
166
+ - `images` + `images.aliases` (simple-streams runtime methods are still scaffolded)
167
+ - `instances` collection + instance handles (`instances.instance(name)`) with:
168
+ - CRUD/state
169
+ - snapshots (`create`, `list`, `get`, `update`, `rename`, `remove`, `restore`)
170
+ - `exec` (Unix-socket websocket attach, async streaming, promise-style completion)
171
+ - `logs`, `files`, `metadata`, `console`
48
172
 
49
- ## Sketched but not implemented yet
173
+ ## Still Scaffolded
50
174
 
51
- - `certificates`
52
- - `events`
53
- - `networks` and nested groups
54
- - `profiles`, `projects`
55
- - `storage` and nested groups
56
- - `cluster`
57
- - `warnings`
58
- - `instances.templates`, `instances.snapshots`, `instances.backups`
175
+ - `certificates`, `events`
176
+ - `networks`, `profiles`, `projects`
177
+ - `storage`, `cluster`, `warnings`
178
+ - `instances.templates`, `instances.backups`
59
179
 
60
- ## Scripts
180
+ ## Bun Scripts
61
181
 
62
182
  - `bun run typecheck`
63
183
  - `bun run test`