@appthrust/kest 0.2.0 → 0.3.1
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 +93 -13
- package/package.json +2 -1
- package/ts/actions/apply-namespace.ts +35 -3
- package/ts/actions/apply-status.ts +8 -1
- package/ts/actions/apply.ts +8 -1
- package/ts/actions/assert-absence.ts +38 -0
- package/ts/actions/assert-list.ts +3 -0
- package/ts/actions/assert.ts +3 -0
- package/ts/actions/create.ts +34 -0
- package/ts/actions/delete.ts +4 -12
- package/ts/actions/exec.ts +3 -0
- package/ts/actions/get.ts +3 -0
- package/ts/actions/kubectl-type.ts +19 -0
- package/ts/actions/label.ts +23 -0
- package/ts/actions/types.ts +3 -0
- package/ts/apis/index.ts +331 -4
- package/ts/k8s-resource/index.ts +22 -0
- package/ts/kubectl/index.ts +54 -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 +43 -31
- package/ts/test.ts +2 -1
- package/ts/reporter/index.ts +0 -0
- package/ts/reporter/markdown.ts +0 -962
package/README.md
CHANGED
|
@@ -71,6 +71,13 @@ const ns = await s.newNamespace();
|
|
|
71
71
|
// All resources applied through `ns` are scoped to this namespace.
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
You can also specify a custom prefix for the generated namespace name using `generateName`:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
const ns = await s.newNamespace({ generateName: "foo-" });
|
|
78
|
+
// Namespace name will be like "foo-d7kpn"
|
|
79
|
+
```
|
|
80
|
+
|
|
74
81
|
### Automatic Cleanup (Reverse-Order, Blocking)
|
|
75
82
|
|
|
76
83
|
Resources are deleted in the reverse order they were created (LIFO). Kest waits until each resource is fully removed before proceeding, preventing flaky failures caused by lingering resources or `Terminating` namespaces.
|
|
@@ -112,6 +119,21 @@ await ns.assert(
|
|
|
112
119
|
);
|
|
113
120
|
```
|
|
114
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
|
+
|
|
115
137
|
### Multiple Manifest Formats
|
|
116
138
|
|
|
117
139
|
Apply resources using whichever format is most convenient:
|
|
@@ -197,6 +219,61 @@ await ns.assertList<ConfigMap>({
|
|
|
197
219
|
});
|
|
198
220
|
```
|
|
199
221
|
|
|
222
|
+
### Absence Assertions
|
|
223
|
+
|
|
224
|
+
Assert that a resource does not exist (e.g. after deletion or to verify a controller hasn't created something):
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
await ns.assertAbsence({
|
|
228
|
+
apiVersion: "v1",
|
|
229
|
+
kind: "ConfigMap",
|
|
230
|
+
name: "deleted-config",
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
With retry-based polling to wait for a resource to disappear:
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
await ns.assertAbsence(
|
|
238
|
+
{
|
|
239
|
+
apiVersion: "apps/v1",
|
|
240
|
+
kind: "Deployment",
|
|
241
|
+
name: "my-app",
|
|
242
|
+
},
|
|
243
|
+
{ timeout: "30s", interval: "1s" },
|
|
244
|
+
);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Label Resources
|
|
248
|
+
|
|
249
|
+
Add, update, or remove labels on Kubernetes resources using `kubectl label`:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
await ns.label({
|
|
253
|
+
apiVersion: "v1",
|
|
254
|
+
kind: "ConfigMap",
|
|
255
|
+
name: "my-config",
|
|
256
|
+
labels: {
|
|
257
|
+
env: "production", // add a label
|
|
258
|
+
deprecated: null, // remove a label
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
To overwrite an existing label, set `overwrite: true`:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
await ns.label({
|
|
267
|
+
apiVersion: "apps/v1",
|
|
268
|
+
kind: "Deployment",
|
|
269
|
+
name: "my-app",
|
|
270
|
+
labels: {
|
|
271
|
+
version: "v2",
|
|
272
|
+
},
|
|
273
|
+
overwrite: true,
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
200
277
|
### Shell Command Execution
|
|
201
278
|
|
|
202
279
|
Run arbitrary shell commands with optional revert handlers for cleanup:
|
|
@@ -333,22 +410,25 @@ Entry point for defining a test scenario. The callback receives a `Scenario` obj
|
|
|
333
410
|
|
|
334
411
|
The top-level API surface available in every test callback.
|
|
335
412
|
|
|
336
|
-
| Method | Description
|
|
337
|
-
| ----------------------------------------------------------------------- |
|
|
338
|
-
| `apply(manifest, options?)` | Apply a Kubernetes manifest and register cleanup
|
|
339
|
-
| `
|
|
340
|
-
| `
|
|
341
|
-
| `
|
|
342
|
-
| `
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
413
|
+
| Method | Description |
|
|
414
|
+
| ----------------------------------------------------------------------- | ------------------------------------------------- |
|
|
415
|
+
| `apply(manifest, options?)` | Apply a Kubernetes manifest and register cleanup |
|
|
416
|
+
| `create(manifest, options?)` | Create a Kubernetes resource and register cleanup |
|
|
417
|
+
| `applyStatus(manifest, options?)` | Apply a status subresource (server-side apply) |
|
|
418
|
+
| `delete(resource, options?)` | Delete a resource by API version, kind, and name |
|
|
419
|
+
| `label(input, options?)` | Add, update, or remove labels on a resource |
|
|
420
|
+
| `get(resource, options?)` | Fetch a resource by API version, kind, and name |
|
|
421
|
+
| `assert(resource, options?)` | Fetch a resource and run assertions with retries |
|
|
422
|
+
| `assertAbsence(resource, options?)` | Assert that a resource does not exist |
|
|
423
|
+
| `assertList(resource, options?)` | Fetch a list of resources and run assertions |
|
|
424
|
+
| `newNamespace(name?, options?)` | Create an ephemeral namespace (supports `{ generateName }`) |
|
|
425
|
+
| `exec(input, options?)` | Execute shell commands with optional revert |
|
|
426
|
+
| `useCluster(ref)` | Create a cluster-bound API surface |
|
|
427
|
+
| `given(desc)` / `when(desc)` / `then(desc)` / `and(desc)` / `but(desc)` | BDD annotations for reporting |
|
|
348
428
|
|
|
349
429
|
### Namespace / Cluster
|
|
350
430
|
|
|
351
|
-
Returned by `newNamespace()` and `useCluster()` respectively. They expose the same core methods (`apply`, `applyStatus`, `delete`, `get`, `assert`, `assertList`) scoped to their namespace or cluster context. `Cluster` additionally supports `newNamespace`.
|
|
431
|
+
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`.
|
|
352
432
|
|
|
353
433
|
### Action Options
|
|
354
434
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appthrust/kest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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,13 +1,26 @@
|
|
|
1
1
|
import { apply } from "./apply";
|
|
2
2
|
import type { MutateDef } from "./types";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Input for namespace creation.
|
|
6
|
+
*
|
|
7
|
+
* - `undefined` -- auto-generate a name like `kest-{random}`.
|
|
8
|
+
* - `string` -- use the exact name provided.
|
|
9
|
+
* - `{ generateName: string }` -- use the string as a prefix followed by
|
|
10
|
+
* random characters (e.g. `{ generateName: "foo-" }` → `"foo-d7kpn"`).
|
|
11
|
+
*/
|
|
12
|
+
export type ApplyNamespaceInput =
|
|
13
|
+
| undefined
|
|
14
|
+
| string
|
|
15
|
+
| { readonly generateName: string };
|
|
16
|
+
|
|
4
17
|
export const applyNamespace = {
|
|
5
18
|
type: "mutate",
|
|
6
19
|
name: "ApplyNamespace",
|
|
7
20
|
mutate:
|
|
8
21
|
({ kubectl }) =>
|
|
9
|
-
async (
|
|
10
|
-
const name =
|
|
22
|
+
async (input) => {
|
|
23
|
+
const name = resolveNamespaceName(input);
|
|
11
24
|
const { revert } = await apply.mutate({ kubectl })({
|
|
12
25
|
apiVersion: "v1",
|
|
13
26
|
kind: "Namespace",
|
|
@@ -17,7 +30,26 @@ export const applyNamespace = {
|
|
|
17
30
|
});
|
|
18
31
|
return { revert, output: name };
|
|
19
32
|
},
|
|
20
|
-
|
|
33
|
+
describe: (input) => {
|
|
34
|
+
if (input === undefined) {
|
|
35
|
+
return "Apply `Namespace` with auto-generated name";
|
|
36
|
+
}
|
|
37
|
+
if (typeof input === "string") {
|
|
38
|
+
return `Apply \`Namespace\` "${input}"`;
|
|
39
|
+
}
|
|
40
|
+
return `Apply \`Namespace\` with prefix "${input.generateName}"`;
|
|
41
|
+
},
|
|
42
|
+
} satisfies MutateDef<ApplyNamespaceInput, string>;
|
|
43
|
+
|
|
44
|
+
function resolveNamespaceName(input: ApplyNamespaceInput): string {
|
|
45
|
+
if (input === undefined) {
|
|
46
|
+
return `kest-${randomConsonantDigits(5)}`;
|
|
47
|
+
}
|
|
48
|
+
if (typeof input === "string") {
|
|
49
|
+
return input;
|
|
50
|
+
}
|
|
51
|
+
return `${input.generateName}${randomConsonantDigits(5)}`;
|
|
52
|
+
}
|
|
21
53
|
|
|
22
54
|
function randomConsonantDigits(length = 8): string {
|
|
23
55
|
const chars = "bcdfghjklmnpqrstvwxyz0123456789";
|
|
@@ -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>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { K8sResource, K8sResourceReference } from "../apis";
|
|
2
|
+
import { toKubectlType } from "./kubectl-type";
|
|
3
|
+
import type { Deps, QueryDef } from "./types";
|
|
4
|
+
|
|
5
|
+
export const assertAbsence = {
|
|
6
|
+
type: "query",
|
|
7
|
+
name: "AssertAbsence",
|
|
8
|
+
query:
|
|
9
|
+
({ kubectl }: Deps) =>
|
|
10
|
+
async <T extends K8sResource>(
|
|
11
|
+
resource: K8sResourceReference<T>
|
|
12
|
+
): Promise<void> => {
|
|
13
|
+
try {
|
|
14
|
+
await kubectl.get(toKubectlType(resource), resource.name);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (isNotFoundError(error)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Expected ${resource.kind} "${resource.name}" to be absent, but it exists`
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
describe: (resource) =>
|
|
26
|
+
`Assert that \`${resource.kind}\` "${resource.name}" is absent`,
|
|
27
|
+
} satisfies QueryDef<K8sResourceReference, void>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Checks whether a kubectl error is a "NotFound" error.
|
|
31
|
+
*
|
|
32
|
+
* kubectl outputs `Error from server (NotFound):` when the resource does not
|
|
33
|
+
* exist, and the {@link RealKubectl} wrapper embeds that message in the
|
|
34
|
+
* thrown `Error`.
|
|
35
|
+
*/
|
|
36
|
+
function isNotFoundError(error: unknown): boolean {
|
|
37
|
+
return error instanceof Error && error.message.includes("(NotFound)");
|
|
38
|
+
}
|
|
@@ -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>(
|
|
@@ -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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { K8sResource, K8sResourceReference } from "../apis";
|
|
2
|
+
import { toKubectlType } from "./kubectl-type";
|
|
2
3
|
import type { OneWayMutateDef } from "./types";
|
|
3
4
|
|
|
4
5
|
export const deleteResource = {
|
|
@@ -10,16 +11,7 @@ export const deleteResource = {
|
|
|
10
11
|
await kubectl.delete(toKubectlType(resource), resource.name);
|
|
11
12
|
return undefined;
|
|
12
13
|
},
|
|
14
|
+
describe: (resource) => {
|
|
15
|
+
return `Delete \`${resource.kind}\` "${resource.name}"`;
|
|
16
|
+
},
|
|
13
17
|
} satisfies OneWayMutateDef<K8sResourceReference, void>;
|
|
14
|
-
|
|
15
|
-
function toKubectlType<T extends K8sResource>(
|
|
16
|
-
resource: K8sResourceReference<T>
|
|
17
|
-
): string {
|
|
18
|
-
const { kind, apiVersion } = resource;
|
|
19
|
-
const [group, version] = apiVersion.split("/");
|
|
20
|
-
if (version === undefined) {
|
|
21
|
-
// core group cannot include version in the type
|
|
22
|
-
return kind;
|
|
23
|
-
}
|
|
24
|
-
return [kind, version, group].filter(Boolean).join(".");
|
|
25
|
-
}
|
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>(
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts an `apiVersion` + `kind` pair into the resource type string
|
|
3
|
+
* expected by kubectl subcommands (e.g. `get`, `delete`, `label`).
|
|
4
|
+
*
|
|
5
|
+
* - Core-group resources (`apiVersion: "v1"`) → `"ConfigMap"`
|
|
6
|
+
* - Non-core resources (`apiVersion: "apps/v1"`) → `"Deployment.v1.apps"`
|
|
7
|
+
*/
|
|
8
|
+
export function toKubectlType(resource: {
|
|
9
|
+
readonly apiVersion: string;
|
|
10
|
+
readonly kind: string;
|
|
11
|
+
}): string {
|
|
12
|
+
const { kind, apiVersion } = resource;
|
|
13
|
+
const [group, version] = apiVersion.split("/");
|
|
14
|
+
if (version === undefined) {
|
|
15
|
+
// core group cannot include version in the type
|
|
16
|
+
return kind;
|
|
17
|
+
}
|
|
18
|
+
return [kind, version, group].filter(Boolean).join(".");
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { K8sResource, LabelInput } from "../apis";
|
|
2
|
+
import { toKubectlType } from "./kubectl-type";
|
|
3
|
+
import type { OneWayMutateDef } from "./types";
|
|
4
|
+
|
|
5
|
+
export const label = {
|
|
6
|
+
type: "oneWayMutate",
|
|
7
|
+
name: "Label",
|
|
8
|
+
mutate:
|
|
9
|
+
({ kubectl }) =>
|
|
10
|
+
async <T extends K8sResource>(input: LabelInput<T>) => {
|
|
11
|
+
const overrideContext = input.namespace
|
|
12
|
+
? { namespace: input.namespace }
|
|
13
|
+
: undefined;
|
|
14
|
+
await kubectl.label(toKubectlType(input), input.name, input.labels, {
|
|
15
|
+
overwrite: input.overwrite,
|
|
16
|
+
context: overrideContext,
|
|
17
|
+
});
|
|
18
|
+
return undefined;
|
|
19
|
+
},
|
|
20
|
+
describe: (input) => {
|
|
21
|
+
return `Label \`${input.kind}\` "${input.name}"`;
|
|
22
|
+
},
|
|
23
|
+
} satisfies OneWayMutateDef<LabelInput, void>;
|
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> = (
|