@appthrust/kest 0.3.0 → 0.3.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 +62 -28
- package/package.json +2 -1
- package/ts/actions/apply-status.ts +8 -1
- package/ts/actions/apply.ts +8 -1
- package/ts/actions/assert-absence.ts +2 -0
- package/ts/actions/assert-list.ts +3 -0
- package/ts/actions/assert.ts +3 -0
- package/ts/actions/{apply-namespace.ts → create-namespace.ts} +16 -7
- package/ts/actions/create.ts +34 -0
- package/ts/actions/delete.ts +3 -0
- package/ts/actions/exec.ts +3 -0
- package/ts/actions/get.ts +3 -0
- package/ts/actions/label.ts +3 -0
- package/ts/actions/types.ts +3 -0
- package/ts/apis/index.ts +88 -0
- package/ts/k8s-resource/index.ts +22 -0
- package/ts/recording/index.ts +81 -115
- package/ts/reporter/markdown/index.ts +23 -0
- package/ts/reporter/markdown/model.ts +63 -0
- package/ts/reporter/markdown/parser/index.ts +361 -0
- package/ts/reporter/markdown/renderer/index.ts +296 -0
- package/ts/reporter/shiki.ts +58 -0
- package/ts/retry.ts +0 -6
- package/ts/scenario/index.ts +35 -35
- package/ts/test.ts +2 -1
- package/ts/reporter/index.ts +0 -0
- package/ts/reporter/markdown.ts +0 -962
package/README.md
CHANGED
|
@@ -119,6 +119,21 @@ await ns.assert(
|
|
|
119
119
|
);
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### Create Resources
|
|
123
|
+
|
|
124
|
+
Use `kubectl create` instead of `kubectl apply` when you need to ensure a resource is freshly created (e.g. the resource must not already exist, or you want to use `generateName`):
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
await ns.create({
|
|
128
|
+
apiVersion: "v1",
|
|
129
|
+
kind: "ConfigMap",
|
|
130
|
+
metadata: { name: "my-config" },
|
|
131
|
+
data: { mode: "demo" },
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Like `apply`, `create` registers a cleanup handler that deletes the resource when the test ends. The key difference is that `kubectl create` fails if the resource already exists, whereas `kubectl apply` performs an upsert.
|
|
136
|
+
|
|
122
137
|
### Multiple Manifest Formats
|
|
123
138
|
|
|
124
139
|
Apply resources using whichever format is most convenient:
|
|
@@ -239,8 +254,8 @@ await ns.label({
|
|
|
239
254
|
kind: "ConfigMap",
|
|
240
255
|
name: "my-config",
|
|
241
256
|
labels: {
|
|
242
|
-
env: "production",
|
|
243
|
-
deprecated: null,
|
|
257
|
+
env: "production", // add a label
|
|
258
|
+
deprecated: null, // remove a label
|
|
244
259
|
},
|
|
245
260
|
});
|
|
246
261
|
```
|
|
@@ -295,33 +310,51 @@ test("ConfigMap lifecycle", async (s) => {
|
|
|
295
310
|
|
|
296
311
|
### Markdown Test Reports
|
|
297
312
|
|
|
298
|
-
When a test fails (or when `KEST_SHOW_REPORT=1` is set), Kest generates a detailed Markdown report showing every action, the exact `kubectl` commands executed, stdout/stderr output, and cleanup results. This provides full transparency into what happened during the test, making troubleshooting straightforward -- for both humans and AI assistants.
|
|
313
|
+
When a test fails (or when `KEST_SHOW_REPORT=1` is set), Kest generates a detailed Markdown report showing every action, the exact `kubectl` commands executed (including stdin manifests), stdout/stderr output, and cleanup results. This provides full transparency into what happened during the test, making troubleshooting straightforward -- for both humans and AI assistants.
|
|
299
314
|
|
|
300
|
-
|
|
315
|
+
````markdown
|
|
301
316
|
# ConfigMap lifecycle
|
|
302
317
|
|
|
303
318
|
## Scenario Overview
|
|
304
319
|
|
|
305
|
-
| # | Action
|
|
306
|
-
| --- |
|
|
307
|
-
| 1 |
|
|
308
|
-
| 2 | Apply
|
|
309
|
-
| 3 | Assert
|
|
320
|
+
| # | Action | Status |
|
|
321
|
+
| --- | ------------------------------ | ------ |
|
|
322
|
+
| 1 | Apply Namespace `kest-9hdhj` | ✅ |
|
|
323
|
+
| 2 | Apply `ConfigMap` "my-config" | ✅ |
|
|
324
|
+
| 3 | Assert `ConfigMap` "my-config" | ✅ |
|
|
310
325
|
|
|
311
326
|
## Scenario Details
|
|
312
327
|
|
|
313
328
|
### Given: a namespace exists
|
|
314
329
|
|
|
315
|
-
|
|
330
|
+
**✅ Apply Namespace `kest-9hdhj`**
|
|
331
|
+
|
|
332
|
+
```shell
|
|
333
|
+
kubectl apply -f - <<EOF
|
|
334
|
+
apiVersion: v1
|
|
335
|
+
kind: Namespace
|
|
336
|
+
metadata:
|
|
337
|
+
name: kest-9hdhj
|
|
338
|
+
EOF
|
|
339
|
+
```
|
|
340
|
+
|
|
316
341
|
...
|
|
317
342
|
|
|
318
343
|
### Cleanup
|
|
319
344
|
|
|
320
|
-
| # | Action
|
|
321
|
-
| --- |
|
|
322
|
-
| 1 | Delete
|
|
323
|
-
| 2 | Delete
|
|
345
|
+
| # | Action | Status |
|
|
346
|
+
| --- | ------------------------------ | ------ |
|
|
347
|
+
| 1 | Delete `ConfigMap` "my-config" | ✅ |
|
|
348
|
+
| 2 | Delete Namespace `kest-9hdhj` | ✅ |
|
|
349
|
+
|
|
350
|
+
```shellsession
|
|
351
|
+
$ kubectl delete ConfigMap/my-config -n kest-9hdhj
|
|
352
|
+
configmap "my-config" deleted
|
|
353
|
+
|
|
354
|
+
$ kubectl delete namespace/kest-9hdhj
|
|
355
|
+
namespace "kest-9hdhj" deleted
|
|
324
356
|
```
|
|
357
|
+
````
|
|
325
358
|
|
|
326
359
|
## Getting Started
|
|
327
360
|
|
|
@@ -395,24 +428,25 @@ Entry point for defining a test scenario. The callback receives a `Scenario` obj
|
|
|
395
428
|
|
|
396
429
|
The top-level API surface available in every test callback.
|
|
397
430
|
|
|
398
|
-
| Method | Description
|
|
399
|
-
| ----------------------------------------------------------------------- |
|
|
400
|
-
| `apply(manifest, options?)` | Apply a Kubernetes manifest and register cleanup
|
|
401
|
-
| `
|
|
402
|
-
| `
|
|
403
|
-
| `
|
|
404
|
-
| `
|
|
405
|
-
| `
|
|
406
|
-
| `
|
|
407
|
-
| `
|
|
431
|
+
| Method | Description |
|
|
432
|
+
| ----------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
433
|
+
| `apply(manifest, options?)` | Apply a Kubernetes manifest and register cleanup |
|
|
434
|
+
| `create(manifest, options?)` | Create a Kubernetes resource and register cleanup |
|
|
435
|
+
| `applyStatus(manifest, options?)` | Apply a status subresource (server-side apply) |
|
|
436
|
+
| `delete(resource, options?)` | Delete a resource by API version, kind, and name |
|
|
437
|
+
| `label(input, options?)` | Add, update, or remove labels on a resource |
|
|
438
|
+
| `get(resource, options?)` | Fetch a resource by API version, kind, and name |
|
|
439
|
+
| `assert(resource, options?)` | Fetch a resource and run assertions with retries |
|
|
440
|
+
| `assertAbsence(resource, options?)` | Assert that a resource does not exist |
|
|
441
|
+
| `assertList(resource, options?)` | Fetch a list of resources and run assertions |
|
|
408
442
|
| `newNamespace(name?, options?)` | Create an ephemeral namespace (supports `{ generateName }`) |
|
|
409
|
-
| `exec(input, options?)` | Execute shell commands with optional revert
|
|
410
|
-
| `useCluster(ref)` | Create a cluster-bound API surface
|
|
411
|
-
| `given(desc)` / `when(desc)` / `then(desc)` / `and(desc)` / `but(desc)` | BDD annotations for reporting
|
|
443
|
+
| `exec(input, options?)` | Execute shell commands with optional revert |
|
|
444
|
+
| `useCluster(ref)` | Create a cluster-bound API surface |
|
|
445
|
+
| `given(desc)` / `when(desc)` / `then(desc)` / `and(desc)` / `but(desc)` | BDD annotations for reporting |
|
|
412
446
|
|
|
413
447
|
### Namespace / Cluster
|
|
414
448
|
|
|
415
|
-
Returned by `newNamespace()` and `useCluster()` respectively. They expose the same core methods (`apply`, `applyStatus`, `delete`, `label`, `get`, `assert`, `assertAbsence`, `assertList`) scoped to their namespace or cluster context. `Cluster` additionally supports `newNamespace`.
|
|
449
|
+
Returned by `newNamespace()` and `useCluster()` respectively. They expose the same core methods (`apply`, `create`, `applyStatus`, `delete`, `label`, `get`, `assert`, `assertAbsence`, `assertList`) scoped to their namespace or cluster context. `Cluster` additionally supports `newNamespace`.
|
|
416
450
|
|
|
417
451
|
### Action Options
|
|
418
452
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appthrust/kest",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Kubernetes E2E testing framework designed for humans and AI alike",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "ts/index.ts",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@biomejs/biome": "^2.3.13",
|
|
54
54
|
"@suin/biome.json": "^0.1.0",
|
|
55
|
+
"@taml/encoder": "^1.0.0",
|
|
55
56
|
"@tsconfig/bun": "^1.0.10",
|
|
56
57
|
"@tsconfig/strictest": "^2.0.8",
|
|
57
58
|
"@types/bun": "^1.3.7",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApplyingManifest } from "../apis";
|
|
2
|
-
import { parseK8sResourceAny } from "../k8s-resource";
|
|
2
|
+
import { getResourceMeta, parseK8sResourceAny } from "../k8s-resource";
|
|
3
3
|
import type { OneWayMutateDef } from "./types";
|
|
4
4
|
|
|
5
5
|
export const applyStatus = {
|
|
@@ -20,4 +20,11 @@ export const applyStatus = {
|
|
|
20
20
|
await kubectl.applyStatus(result.value);
|
|
21
21
|
return undefined;
|
|
22
22
|
},
|
|
23
|
+
describe: (manifest) => {
|
|
24
|
+
const meta = getResourceMeta(manifest);
|
|
25
|
+
if (meta === undefined) {
|
|
26
|
+
return "Apply status of a resource";
|
|
27
|
+
}
|
|
28
|
+
return `Apply status of \`${meta.kind}\` "${meta.name}"`;
|
|
29
|
+
},
|
|
23
30
|
} satisfies OneWayMutateDef<ApplyingManifest, void>;
|
package/ts/actions/apply.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ApplyingManifest } from "../apis";
|
|
2
|
-
import { parseK8sResourceAny } from "../k8s-resource";
|
|
2
|
+
import { getResourceMeta, parseK8sResourceAny } from "../k8s-resource";
|
|
3
3
|
import type { MutateDef } from "./types";
|
|
4
4
|
|
|
5
5
|
export const apply = {
|
|
@@ -24,4 +24,11 @@ export const apply = {
|
|
|
24
24
|
output: undefined,
|
|
25
25
|
};
|
|
26
26
|
},
|
|
27
|
+
describe: (manifest) => {
|
|
28
|
+
const meta = getResourceMeta(manifest);
|
|
29
|
+
if (meta === undefined) {
|
|
30
|
+
return "Apply a resource";
|
|
31
|
+
}
|
|
32
|
+
return `Apply \`${meta.kind}\` "${meta.name}"`;
|
|
33
|
+
},
|
|
27
34
|
} satisfies MutateDef<ApplyingManifest, void>;
|
|
@@ -22,6 +22,8 @@ export const assertAbsence = {
|
|
|
22
22
|
`Expected ${resource.kind} "${resource.name}" to be absent, but it exists`
|
|
23
23
|
);
|
|
24
24
|
},
|
|
25
|
+
describe: (resource) =>
|
|
26
|
+
`Assert that \`${resource.kind}\` "${resource.name}" is absent`,
|
|
25
27
|
} satisfies QueryDef<K8sResourceReference, void>;
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -28,6 +28,9 @@ export const assertList = {
|
|
|
28
28
|
await condition.test.call(typed, typed);
|
|
29
29
|
return typed;
|
|
30
30
|
},
|
|
31
|
+
describe: <T extends K8sResource>(condition: ResourceListTest<T>): string => {
|
|
32
|
+
return `Assert a list of \`${condition.kind}\` resources`;
|
|
33
|
+
},
|
|
31
34
|
} satisfies QueryDef<ResourceListTest, Array<K8sResource>>;
|
|
32
35
|
|
|
33
36
|
function isSameGVK<T extends K8sResource>(
|
package/ts/actions/assert.ts
CHANGED
|
@@ -19,6 +19,9 @@ export const assert = {
|
|
|
19
19
|
await condition.test.call(fetched, fetched);
|
|
20
20
|
return fetched;
|
|
21
21
|
},
|
|
22
|
+
describe: <T extends K8sResource>(condition: ResourceTest<T>): string => {
|
|
23
|
+
return `Assert \`${condition.kind}\` "${condition.name}"`;
|
|
24
|
+
},
|
|
22
25
|
} satisfies QueryDef<ResourceTest, K8sResource>;
|
|
23
26
|
|
|
24
27
|
function toKubectlType<T extends K8sResource>(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { create } from "./create";
|
|
2
2
|
import type { MutateDef } from "./types";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -9,19 +9,19 @@ import type { MutateDef } from "./types";
|
|
|
9
9
|
* - `{ generateName: string }` -- use the string as a prefix followed by
|
|
10
10
|
* random characters (e.g. `{ generateName: "foo-" }` → `"foo-d7kpn"`).
|
|
11
11
|
*/
|
|
12
|
-
export type
|
|
12
|
+
export type CreateNamespaceInput =
|
|
13
13
|
| undefined
|
|
14
14
|
| string
|
|
15
15
|
| { readonly generateName: string };
|
|
16
16
|
|
|
17
|
-
export const
|
|
17
|
+
export const createNamespace = {
|
|
18
18
|
type: "mutate",
|
|
19
|
-
name: "
|
|
19
|
+
name: "CreateNamespace",
|
|
20
20
|
mutate:
|
|
21
21
|
({ kubectl }) =>
|
|
22
22
|
async (input) => {
|
|
23
23
|
const name = resolveNamespaceName(input);
|
|
24
|
-
const { revert } = await
|
|
24
|
+
const { revert } = await create.mutate({ kubectl })({
|
|
25
25
|
apiVersion: "v1",
|
|
26
26
|
kind: "Namespace",
|
|
27
27
|
metadata: {
|
|
@@ -30,9 +30,18 @@ export const applyNamespace = {
|
|
|
30
30
|
});
|
|
31
31
|
return { revert, output: name };
|
|
32
32
|
},
|
|
33
|
-
|
|
33
|
+
describe: (input) => {
|
|
34
|
+
if (input === undefined) {
|
|
35
|
+
return "Create `Namespace` with auto-generated name";
|
|
36
|
+
}
|
|
37
|
+
if (typeof input === "string") {
|
|
38
|
+
return `Create \`Namespace\` "${input}"`;
|
|
39
|
+
}
|
|
40
|
+
return `Create \`Namespace\` with prefix "${input.generateName}"`;
|
|
41
|
+
},
|
|
42
|
+
} satisfies MutateDef<CreateNamespaceInput, string>;
|
|
34
43
|
|
|
35
|
-
function resolveNamespaceName(input:
|
|
44
|
+
function resolveNamespaceName(input: CreateNamespaceInput): string {
|
|
36
45
|
if (input === undefined) {
|
|
37
46
|
return `kest-${randomConsonantDigits(5)}`;
|
|
38
47
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ApplyingManifest } from "../apis";
|
|
2
|
+
import { getResourceMeta, parseK8sResourceAny } from "../k8s-resource";
|
|
3
|
+
import type { MutateDef } from "./types";
|
|
4
|
+
|
|
5
|
+
export const create = {
|
|
6
|
+
type: "mutate",
|
|
7
|
+
name: "Create",
|
|
8
|
+
mutate:
|
|
9
|
+
({ kubectl }) =>
|
|
10
|
+
async (manifest) => {
|
|
11
|
+
const result = await parseK8sResourceAny(manifest);
|
|
12
|
+
if (!result.ok) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`Invalid Kubernetes resource: ${result.violations.join(", ")}`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
await kubectl.create(result.value);
|
|
18
|
+
return {
|
|
19
|
+
async revert() {
|
|
20
|
+
await kubectl.delete(result.value.kind, result.value.metadata.name, {
|
|
21
|
+
ignoreNotFound: true,
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
output: undefined,
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
describe: (manifest) => {
|
|
28
|
+
const meta = getResourceMeta(manifest);
|
|
29
|
+
if (meta === undefined) {
|
|
30
|
+
return "Create a resource";
|
|
31
|
+
}
|
|
32
|
+
return `Create \`${meta.kind}\` "${meta.name}"`;
|
|
33
|
+
},
|
|
34
|
+
} satisfies MutateDef<ApplyingManifest, void>;
|
package/ts/actions/delete.ts
CHANGED
|
@@ -11,4 +11,7 @@ export const deleteResource = {
|
|
|
11
11
|
await kubectl.delete(toKubectlType(resource), resource.name);
|
|
12
12
|
return undefined;
|
|
13
13
|
},
|
|
14
|
+
describe: (resource) => {
|
|
15
|
+
return `Delete \`${resource.kind}\` "${resource.name}"`;
|
|
16
|
+
},
|
|
14
17
|
} satisfies OneWayMutateDef<K8sResourceReference, void>;
|
package/ts/actions/exec.ts
CHANGED
|
@@ -17,5 +17,8 @@ export const exec = {
|
|
|
17
17
|
revert: revert ? () => revert(context) : noopRevert,
|
|
18
18
|
};
|
|
19
19
|
},
|
|
20
|
+
describe: () => {
|
|
21
|
+
return "Execute arbitrary processing";
|
|
22
|
+
},
|
|
20
23
|
// biome-ignore lint/suspicious/noExplicitAny: 本当はunknownにしたいが、createMutateFnとの噛み合せが難しいためanyにしている
|
|
21
24
|
} satisfies MutateDef<ExecInput<any>, unknown>;
|
package/ts/actions/get.ts
CHANGED
|
@@ -15,6 +15,9 @@ export const get = {
|
|
|
15
15
|
...finding,
|
|
16
16
|
test: (fetched) => assertSameGVK<T>(finding, fetched),
|
|
17
17
|
}),
|
|
18
|
+
describe: (finding) => {
|
|
19
|
+
return `Get \`${finding.kind}\` "${finding.name}"`;
|
|
20
|
+
},
|
|
18
21
|
} satisfies QueryDef<K8sResourceReference, K8sResource>;
|
|
19
22
|
|
|
20
23
|
function isSameGVK<T extends K8sResource>(
|
package/ts/actions/label.ts
CHANGED
package/ts/actions/types.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface MutateDef<Input, Output> {
|
|
|
6
6
|
readonly type: "mutate";
|
|
7
7
|
readonly name: string;
|
|
8
8
|
readonly mutate: Mutate<Input, Output>;
|
|
9
|
+
readonly describe: (input: Input) => string;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export type Mutate<Input, Output> = (
|
|
@@ -27,6 +28,7 @@ export interface OneWayMutateDef<Input, Output> {
|
|
|
27
28
|
readonly type: "oneWayMutate";
|
|
28
29
|
readonly name: string;
|
|
29
30
|
readonly mutate: OneWayMutate<Input, Output>;
|
|
31
|
+
readonly describe: (input: Input) => string;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export type OneWayMutate<Input, Output> = (
|
|
@@ -37,6 +39,7 @@ export interface QueryDef<Input, Output> {
|
|
|
37
39
|
readonly type: "query";
|
|
38
40
|
readonly name: string;
|
|
39
41
|
readonly query: Query<Input, Output>;
|
|
42
|
+
readonly describe: (input: Input) => string;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export type Query<Input, Output> = (
|
package/ts/apis/index.ts
CHANGED
|
@@ -54,6 +54,39 @@ export interface Scenario {
|
|
|
54
54
|
options?: undefined | ActionOptions
|
|
55
55
|
): Promise<void>;
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Creates a Kubernetes resource with `kubectl create`.
|
|
59
|
+
*
|
|
60
|
+
* The manifest is validated and then created. When the action succeeds, Kest
|
|
61
|
+
* registers a cleanup handler that deletes the resource using
|
|
62
|
+
* `kubectl delete <kind>/<metadata.name>` during scenario cleanup.
|
|
63
|
+
*
|
|
64
|
+
* Unlike {@link Scenario.apply}, this action uses `kubectl create` which
|
|
65
|
+
* fails if the resource already exists. Use this when you need to ensure the
|
|
66
|
+
* resource is freshly created (e.g. for resources that use `generateName` or
|
|
67
|
+
* when you want to guarantee no prior state).
|
|
68
|
+
*
|
|
69
|
+
* This action is retried when it throws.
|
|
70
|
+
*
|
|
71
|
+
* @template T - The expected Kubernetes resource shape.
|
|
72
|
+
* @param manifest - YAML string, resource object, or imported YAML module.
|
|
73
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* await s.create({
|
|
78
|
+
* apiVersion: "v1",
|
|
79
|
+
* kind: "ConfigMap",
|
|
80
|
+
* metadata: { name: "my-config" },
|
|
81
|
+
* data: { mode: "demo" },
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
create<T extends K8sResource>(
|
|
86
|
+
manifest: ApplyingManifest<T>,
|
|
87
|
+
options?: undefined | ActionOptions
|
|
88
|
+
): Promise<void>;
|
|
89
|
+
|
|
57
90
|
/**
|
|
58
91
|
* Applies the `status` subresource using server-side apply.
|
|
59
92
|
*
|
|
@@ -442,6 +475,30 @@ export interface Cluster {
|
|
|
442
475
|
options?: undefined | ActionOptions
|
|
443
476
|
): Promise<void>;
|
|
444
477
|
|
|
478
|
+
/**
|
|
479
|
+
* Creates a Kubernetes resource with `kubectl create` and registers cleanup.
|
|
480
|
+
*
|
|
481
|
+
* Unlike {@link Cluster.apply}, this uses `kubectl create` which fails if the
|
|
482
|
+
* resource already exists.
|
|
483
|
+
*
|
|
484
|
+
* @template T - The expected Kubernetes resource shape.
|
|
485
|
+
* @param manifest - YAML string, resource object, or imported YAML module.
|
|
486
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```ts
|
|
490
|
+
* await cluster.create({
|
|
491
|
+
* apiVersion: "v1",
|
|
492
|
+
* kind: "Namespace",
|
|
493
|
+
* metadata: { name: "my-team" },
|
|
494
|
+
* });
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
497
|
+
create<T extends K8sResource>(
|
|
498
|
+
manifest: ApplyingManifest<T>,
|
|
499
|
+
options?: undefined | ActionOptions
|
|
500
|
+
): Promise<void>;
|
|
501
|
+
|
|
445
502
|
/**
|
|
446
503
|
* Applies the `status` subresource using server-side apply.
|
|
447
504
|
*
|
|
@@ -683,6 +740,37 @@ export interface Namespace {
|
|
|
683
740
|
options?: undefined | ActionOptions
|
|
684
741
|
): Promise<void>;
|
|
685
742
|
|
|
743
|
+
/**
|
|
744
|
+
* Creates a Kubernetes resource in this namespace with `kubectl create` and
|
|
745
|
+
* registers cleanup.
|
|
746
|
+
*
|
|
747
|
+
* The target namespace is controlled by this {@link Namespace} instance.
|
|
748
|
+
* Prefer omitting `manifest.metadata.namespace`; if it is set, it must match
|
|
749
|
+
* this namespace (otherwise `kubectl` fails).
|
|
750
|
+
*
|
|
751
|
+
* Unlike {@link Namespace.apply}, this uses `kubectl create` which fails if
|
|
752
|
+
* the resource already exists.
|
|
753
|
+
*
|
|
754
|
+
* @template T - The expected Kubernetes resource shape.
|
|
755
|
+
* @param manifest - YAML string, resource object, or imported YAML module.
|
|
756
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```ts
|
|
760
|
+
* const ns = await s.newNamespace("my-ns");
|
|
761
|
+
* await ns.create({
|
|
762
|
+
* apiVersion: "v1",
|
|
763
|
+
* kind: "ConfigMap",
|
|
764
|
+
* metadata: { name: "my-config" },
|
|
765
|
+
* data: { mode: "demo" },
|
|
766
|
+
* });
|
|
767
|
+
* ```
|
|
768
|
+
*/
|
|
769
|
+
create<T extends K8sResource>(
|
|
770
|
+
manifest: ApplyingManifest<T>,
|
|
771
|
+
options?: undefined | ActionOptions
|
|
772
|
+
): Promise<void>;
|
|
773
|
+
|
|
686
774
|
/**
|
|
687
775
|
* Applies the `status` subresource in this namespace using server-side apply.
|
|
688
776
|
*
|
package/ts/k8s-resource/index.ts
CHANGED
|
@@ -118,3 +118,25 @@ export interface ESM {
|
|
|
118
118
|
function isESM(value: unknown): value is ESM {
|
|
119
119
|
return typeof value === "object" && value !== null && "default" in value;
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
export function getResourceMeta(
|
|
123
|
+
value: unknown
|
|
124
|
+
): undefined | { kind: string; name: string } {
|
|
125
|
+
if (typeof value === "string") {
|
|
126
|
+
const result = parseK8sResourceYaml(value);
|
|
127
|
+
if (result.ok) {
|
|
128
|
+
return { kind: result.value.kind, name: result.value.metadata.name };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (isESM(value)) {
|
|
132
|
+
const result = parseK8sResourceFromESM(value);
|
|
133
|
+
if (result.ok) {
|
|
134
|
+
return { kind: result.value.kind, name: result.value.metadata.name };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const result = parseK8sResource(value);
|
|
138
|
+
if (result.ok) {
|
|
139
|
+
return { kind: result.value.kind, name: result.value.metadata.name };
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|