kubernetes-fluent-client 3.11.11 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +53 -0
  2. package/dist/test/diagnostics.d.ts +36 -0
  3. package/dist/test/diagnostics.d.ts.map +1 -0
  4. package/dist/test/diagnostics.js +24 -0
  5. package/dist/test/environment.d.ts +28 -0
  6. package/dist/test/environment.d.ts.map +1 -0
  7. package/dist/test/environment.js +50 -0
  8. package/dist/test/index.d.ts +6 -0
  9. package/dist/test/index.d.ts.map +1 -0
  10. package/dist/test/index.js +7 -0
  11. package/dist/test/preflight.d.ts +29 -0
  12. package/dist/test/preflight.d.ts.map +1 -0
  13. package/dist/test/preflight.js +43 -0
  14. package/dist/test/resources.d.ts +90 -0
  15. package/dist/test/resources.d.ts.map +1 -0
  16. package/dist/test/resources.js +248 -0
  17. package/dist/test/vitest/config.d.ts +14 -0
  18. package/dist/test/vitest/config.d.ts.map +1 -0
  19. package/dist/test/vitest/config.js +25 -0
  20. package/dist/test/vitest/index.d.ts +2 -0
  21. package/dist/test/vitest/index.d.ts.map +1 -0
  22. package/dist/test/vitest/index.js +3 -0
  23. package/dist/test/vitest/setup.d.ts +11 -0
  24. package/dist/test/vitest/setup.d.ts.map +1 -0
  25. package/dist/test/vitest/setup.js +15 -0
  26. package/dist/test/wait.d.ts +65 -0
  27. package/dist/test/wait.d.ts.map +1 -0
  28. package/dist/test/wait.js +299 -0
  29. package/package.json +40 -8
  30. package/src/test/diagnostics.ts +50 -0
  31. package/src/test/environment.ts +76 -0
  32. package/src/test/index.ts +41 -0
  33. package/src/test/preflight.ts +72 -0
  34. package/src/test/resources.ts +368 -0
  35. package/src/test/vitest/config.ts +28 -0
  36. package/src/test/vitest/index.ts +4 -0
  37. package/src/test/vitest/setup.ts +18 -0
  38. package/src/test/wait.ts +385 -0
package/README.md CHANGED
@@ -13,6 +13,59 @@ To install the Kubernetes Fluent Client, run the following command:
13
13
  npm install kubernetes-fluent-client
14
14
  ```
15
15
 
16
+ ### Kubernetes integration testing
17
+
18
+ Runner-neutral helpers for polling, preflight checks, diagnostics, ownership-aware apply, and
19
+ label-scoped cleanup are available from the test-only entry point. The ownership helpers stamp and
20
+ delete through the same exact labels, preventing one suite from cleaning up another suite's
21
+ resources.
22
+
23
+ ```typescript
24
+ import { kind, K8s } from "kubernetes-fluent-client";
25
+ import {
26
+ applyWithOwnership,
27
+ deleteAllByOwnership,
28
+ preflight,
29
+ waitFor,
30
+ } from "kubernetes-fluent-client/test";
31
+
32
+ const ownership = { owner: "example-suite" };
33
+
34
+ // Fail before the suite starts when the active Kubernetes context is unusable.
35
+ await preflight();
36
+
37
+ // Apply resources with labels that can be targeted safely during cleanup.
38
+ await applyWithOwnership(
39
+ kind.ConfigMap,
40
+ { metadata: { name: "example", namespace: "testing" } },
41
+ ownership,
42
+ );
43
+
44
+ // Poll with a bounded deadline and Kubernetes-aware error handling.
45
+ await waitFor("example ConfigMap", () => K8s(kind.ConfigMap).InNamespace("testing").Get("example"));
46
+
47
+ // Delete only ConfigMaps carrying this suite's exact ownership labels.
48
+ await deleteAllByOwnership(kind.ConfigMap, { ...ownership, namespace: "testing" });
49
+ ```
50
+
51
+ Vitest projects can also share the integration-test configuration and register preflight as a
52
+ suite hook. Vitest is an optional peer dependency and is only required when this entry point is
53
+ used.
54
+
55
+ ```typescript
56
+ // vitest.config.ts
57
+ import { defineKubernetesTestConfig } from "kubernetes-fluent-client/test/vitest";
58
+
59
+ export default defineKubernetesTestConfig();
60
+ ```
61
+
62
+ ```typescript
63
+ // example.test.ts
64
+ import { setupKubernetesPreflight } from "kubernetes-fluent-client/test/vitest/setup";
65
+
66
+ setupKubernetesPreflight();
67
+ ```
68
+
16
69
  See below for some example uses of the library.
17
70
 
18
71
  ```typescript
@@ -0,0 +1,36 @@
1
+ /** A named, independently executed diagnostics operation. */
2
+ export interface DiagnosticCollector<TContext = void, TValue = unknown> {
3
+ /** Stable name identifying the diagnostic in the report. */
4
+ name: string;
5
+ /** Collect a diagnostic value without writing output. */
6
+ collect: (context: TContext) => TValue | Promise<TValue>;
7
+ }
8
+ /** Result from one diagnostics collector. */
9
+ export type DiagnosticEntry = {
10
+ name: string;
11
+ value: unknown;
12
+ error?: never;
13
+ } | {
14
+ name: string;
15
+ value?: never;
16
+ error: unknown;
17
+ };
18
+ /** Ordered, runner-neutral diagnostics report. */
19
+ export interface DiagnosticReport {
20
+ /** ISO timestamp recorded immediately before collectors run. */
21
+ collectedAt: string;
22
+ /** One entry for every requested collector, in input order. */
23
+ entries: DiagnosticEntry[];
24
+ }
25
+ /**
26
+ * Run diagnostics collectors independently and return structured results.
27
+ *
28
+ * A failed collector is represented in its entry and does not prevent the
29
+ * remaining collectors from running. This function never prints or writes files.
30
+ *
31
+ * @param context - Value shared with every collector.
32
+ * @param collectors - Diagnostics operations to run.
33
+ * @returns A structured report suitable for any test runner or artifact format.
34
+ */
35
+ export declare function collectDiagnostics<TContext>(context: TContext, collectors: readonly DiagnosticCollector<TContext>[]): Promise<DiagnosticReport>;
36
+ //# sourceMappingURL=diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../src/test/diagnostics.ts"],"names":[],"mappings":"AAGA,6DAA6D;AAC7D,MAAM,WAAW,mBAAmB,CAAC,QAAQ,GAAG,IAAI,EAAE,MAAM,GAAG,OAAO;IACpE,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1D;AAED,6CAA6C;AAC7C,MAAM,MAAM,eAAe,GACzB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpG,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAC/C,OAAO,EAAE,QAAQ,EACjB,UAAU,EAAE,SAAS,mBAAmB,CAAC,QAAQ,CAAC,EAAE,GACnD,OAAO,CAAC,gBAAgB,CAAC,CAa3B"}
@@ -0,0 +1,24 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026-Present The Kubernetes Fluent Client Authors
3
+ /**
4
+ * Run diagnostics collectors independently and return structured results.
5
+ *
6
+ * A failed collector is represented in its entry and does not prevent the
7
+ * remaining collectors from running. This function never prints or writes files.
8
+ *
9
+ * @param context - Value shared with every collector.
10
+ * @param collectors - Diagnostics operations to run.
11
+ * @returns A structured report suitable for any test runner or artifact format.
12
+ */
13
+ export async function collectDiagnostics(context, collectors) {
14
+ const collectedAt = new Date().toISOString();
15
+ const entries = await Promise.all(collectors.map(async ({ name, collect }) => {
16
+ try {
17
+ return { name, value: await collect(context) };
18
+ }
19
+ catch (error) {
20
+ return { name, error };
21
+ }
22
+ }));
23
+ return { collectedAt, entries };
24
+ }
@@ -0,0 +1,28 @@
1
+ /** Environment variable used to override the default waiter timeout. */
2
+ export declare const TEST_TIMEOUT_ENV = "KFC_TEST_TIMEOUT_MS";
3
+ /** Environment variable used to override the default polling interval. */
4
+ export declare const TEST_INTERVAL_ENV = "KFC_TEST_POLL_INTERVAL_MS";
5
+ /** Default waiter timeout used by KFC integration tests. */
6
+ export declare const DEFAULT_TEST_TIMEOUT_MS = 60000;
7
+ /** Default polling interval used by KFC integration tests. */
8
+ export declare const DEFAULT_TEST_INTERVAL_MS = 2000;
9
+ /** Timing configuration shared by integration test helpers. */
10
+ export interface KubernetesTestEnvironment {
11
+ /** Maximum time to wait for an eventually consistent condition. */
12
+ timeoutMs: number;
13
+ /** Delay between attempts. */
14
+ intervalMs: number;
15
+ }
16
+ /** Optional timing overrides for {@link env}. */
17
+ export type KubernetesTestEnvironmentOverrides = Partial<KubernetesTestEnvironment>;
18
+ /**
19
+ * Resolve integration-test timing from explicit overrides, environment variables, and defaults.
20
+ *
21
+ * Explicit values take precedence over `KFC_TEST_TIMEOUT_MS` and
22
+ * `KFC_TEST_POLL_INTERVAL_MS`. Values must be positive whole milliseconds.
23
+ *
24
+ * @param overrides - Per-call timing values.
25
+ * @returns Resolved waiter timing.
26
+ */
27
+ export declare function env(overrides?: KubernetesTestEnvironmentOverrides): KubernetesTestEnvironment;
28
+ //# sourceMappingURL=environment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.d.ts","sourceRoot":"","sources":["../../src/test/environment.ts"],"names":[],"mappings":"AAGA,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,wBAAwB,CAAC;AAEtD,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,8BAA8B,CAAC;AAE7D,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,8DAA8D;AAC9D,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB;IACxC,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iDAAiD;AACjD,MAAM,MAAM,kCAAkC,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;AA+BpF;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,SAAS,GAAE,kCAAuC,GAAG,yBAAyB,CAWjG"}
@@ -0,0 +1,50 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026-Present The Kubernetes Fluent Client Authors
3
+ /** Environment variable used to override the default waiter timeout. */
4
+ export const TEST_TIMEOUT_ENV = "KFC_TEST_TIMEOUT_MS";
5
+ /** Environment variable used to override the default polling interval. */
6
+ export const TEST_INTERVAL_ENV = "KFC_TEST_POLL_INTERVAL_MS";
7
+ /** Default waiter timeout used by KFC integration tests. */
8
+ export const DEFAULT_TEST_TIMEOUT_MS = 60_000;
9
+ /** Default polling interval used by KFC integration tests. */
10
+ export const DEFAULT_TEST_INTERVAL_MS = 2_000;
11
+ /**
12
+ * Parse and validate a millisecond value.
13
+ *
14
+ * @param name - Setting name used in validation errors.
15
+ * @param value - Candidate millisecond value.
16
+ * @returns The validated positive integer.
17
+ */
18
+ function positiveInteger(name, value) {
19
+ const parsed = typeof value === "number" ? value : Number(value);
20
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
21
+ throw new Error(`${name} must be a positive integer; received ${String(value)}`);
22
+ }
23
+ return parsed;
24
+ }
25
+ /**
26
+ * Read an optional timing setting from the process environment.
27
+ *
28
+ * @param name - Environment variable name.
29
+ * @param fallback - Value returned when the variable is unset or empty.
30
+ * @returns The configured or fallback millisecond value.
31
+ */
32
+ function environmentValue(name, fallback) {
33
+ const value = process.env[name]?.trim();
34
+ return value ? positiveInteger(name, value) : fallback;
35
+ }
36
+ /**
37
+ * Resolve integration-test timing from explicit overrides, environment variables, and defaults.
38
+ *
39
+ * Explicit values take precedence over `KFC_TEST_TIMEOUT_MS` and
40
+ * `KFC_TEST_POLL_INTERVAL_MS`. Values must be positive whole milliseconds.
41
+ *
42
+ * @param overrides - Per-call timing values.
43
+ * @returns Resolved waiter timing.
44
+ */
45
+ export function env(overrides = {}) {
46
+ return {
47
+ timeoutMs: positiveInteger("timeoutMs", overrides.timeoutMs ?? environmentValue(TEST_TIMEOUT_ENV, DEFAULT_TEST_TIMEOUT_MS)),
48
+ intervalMs: positiveInteger("intervalMs", overrides.intervalMs ?? environmentValue(TEST_INTERVAL_ENV, DEFAULT_TEST_INTERVAL_MS)),
49
+ };
50
+ }
@@ -0,0 +1,6 @@
1
+ export { collectDiagnostics, type DiagnosticCollector, type DiagnosticEntry, type DiagnosticReport, } from "./diagnostics.js";
2
+ export { DEFAULT_TEST_INTERVAL_MS, DEFAULT_TEST_TIMEOUT_MS, TEST_INTERVAL_ENV, TEST_TIMEOUT_ENV, env, type KubernetesTestEnvironment, type KubernetesTestEnvironmentOverrides, } from "./environment.js";
3
+ export { preflight, type PreflightOptions, type PreflightResult } from "./preflight.js";
4
+ export { TEST_OWNERSHIP_LABEL, TEST_RUN_ID_LABEL, applyWithOwnership, deleteAllByOwnership, ownershipLabels, waitForResource, type ApplyWithOwnershipOptions, type DeleteAllByOwnershipOptions, type OwnershipLabels, type OwnershipOptions, type ResourceReference, type WaitForResourceOptions, } from "./resources.js";
5
+ export { WaitForTimeoutError, waitFor, type ErrorClassifier, type ErrorDisposition, type WaitForOptions, type WaitForTimeoutDetails, } from "./wait.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,GAAG,EACH,KAAK,yBAAyB,EAC9B,KAAK,kCAAkC,GACxC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxF,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,OAAO,EACP,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC"}
@@ -0,0 +1,7 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026-Present The Kubernetes Fluent Client Authors
3
+ export { collectDiagnostics, } from "./diagnostics.js";
4
+ export { DEFAULT_TEST_INTERVAL_MS, DEFAULT_TEST_TIMEOUT_MS, TEST_INTERVAL_ENV, TEST_TIMEOUT_ENV, env, } from "./environment.js";
5
+ export { preflight } from "./preflight.js";
6
+ export { TEST_OWNERSHIP_LABEL, TEST_RUN_ID_LABEL, applyWithOwnership, deleteAllByOwnership, ownershipLabels, waitForResource, } from "./resources.js";
7
+ export { WaitForTimeoutError, waitFor, } from "./wait.js";
@@ -0,0 +1,29 @@
1
+ import { KubeConfig } from "@kubernetes/client-node";
2
+ import { type WaitForOptions } from "./wait.js";
3
+ /** Options accepted by {@link preflight}. */
4
+ export interface PreflightOptions extends Pick<WaitForOptions, "timeoutMs" | "intervalMs" | "signal"> {
5
+ /** An already configured client, primarily for callers with non-default kubeconfig loading. */
6
+ kubeConfig?: KubeConfig;
7
+ }
8
+ /** Successfully verified Kubernetes connection details. */
9
+ export interface PreflightResult {
10
+ /** Name of the active kubeconfig context. */
11
+ contextName: string;
12
+ /** Name of the cluster selected by that context. */
13
+ clusterName: string;
14
+ /** Kubernetes API server URL. */
15
+ server: string;
16
+ /** Version reported by the authenticated API server. */
17
+ gitVersion: string;
18
+ }
19
+ /**
20
+ * Verify kubeconfig selection and authenticated Kubernetes API connectivity.
21
+ *
22
+ * This intentionally calls the non-privileged `/version` endpoint rather than
23
+ * probing a namespaced resource, so it does not impose additional RBAC needs.
24
+ *
25
+ * @param options - Kubeconfig, timing, and cancellation overrides.
26
+ * @returns The selected context, cluster, server, and Kubernetes version.
27
+ */
28
+ export declare function preflight(options?: PreflightOptions): Promise<PreflightResult>;
29
+ //# sourceMappingURL=preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../../src/test/preflight.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAc,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEzD,6CAA6C;AAC7C,MAAM,WAAW,gBAAiB,SAAQ,IAAI,CAC5C,cAAc,EACd,WAAW,GAAG,YAAY,GAAG,QAAQ,CACtC;IACC,+FAA+F;IAC/F,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB;AAED,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;CACpB;AAeD;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAqBxF"}
@@ -0,0 +1,43 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026-Present The Kubernetes Fluent Client Authors
3
+ import { KubeConfig, VersionApi } from "@kubernetes/client-node";
4
+ import { waitFor } from "./wait.js";
5
+ /**
6
+ * Use a supplied kubeconfig or load the process default.
7
+ *
8
+ * @param kubeConfig - Optional caller-configured Kubernetes client.
9
+ * @returns The client used by preflight checks.
10
+ */
11
+ function loadKubeConfig(kubeConfig) {
12
+ if (kubeConfig)
13
+ return kubeConfig;
14
+ const loaded = new KubeConfig();
15
+ loaded.loadFromDefault();
16
+ return loaded;
17
+ }
18
+ /**
19
+ * Verify kubeconfig selection and authenticated Kubernetes API connectivity.
20
+ *
21
+ * This intentionally calls the non-privileged `/version` endpoint rather than
22
+ * probing a namespaced resource, so it does not impose additional RBAC needs.
23
+ *
24
+ * @param options - Kubeconfig, timing, and cancellation overrides.
25
+ * @returns The selected context, cluster, server, and Kubernetes version.
26
+ */
27
+ export async function preflight(options = {}) {
28
+ const kubeConfig = loadKubeConfig(options.kubeConfig);
29
+ const contextName = kubeConfig.getCurrentContext();
30
+ const cluster = kubeConfig.getCurrentCluster();
31
+ if (!contextName)
32
+ throw new Error("Kubernetes preflight failed: no current context is selected");
33
+ if (!cluster)
34
+ throw new Error("Kubernetes preflight failed: the current context has no cluster");
35
+ const versionApi = kubeConfig.makeApiClient(VersionApi);
36
+ const version = await waitFor(`Kubernetes API server ${cluster.server}`, () => versionApi.getCode(), options);
37
+ return {
38
+ contextName,
39
+ clusterName: cluster.name,
40
+ server: cluster.server,
41
+ gitVersion: version.gitVersion,
42
+ };
43
+ }
@@ -0,0 +1,90 @@
1
+ import type { KubernetesObject } from "@kubernetes/client-node";
2
+ import type { PartialDeep } from "../fluent/types.js";
3
+ import type { GenericClass } from "../types.js";
4
+ import { type WaitForOptions } from "./wait.js";
5
+ /** Default label used to identify resources created by integration tests. */
6
+ export declare const TEST_OWNERSHIP_LABEL = "test.defenseunicorns.dev/source";
7
+ /** Default label used to isolate one execution of an integration test suite. */
8
+ export declare const TEST_RUN_ID_LABEL = "test.defenseunicorns.dev/run-id";
9
+ /** Label identity shared by apply and cleanup operations. */
10
+ export interface OwnershipOptions {
11
+ /** Stable value identifying the test suite that owns the resource. */
12
+ owner: string;
13
+ /** Optional value isolating one execution of the test suite. */
14
+ runId?: string;
15
+ /** Label key override for repositories with an existing convention. */
16
+ labelKey?: string;
17
+ /** Run-ID label key override for repositories with an existing convention. */
18
+ runIdLabelKey?: string;
19
+ }
20
+ /** Resolved Kubernetes ownership labels. */
21
+ export interface OwnershipLabels {
22
+ /** Kubernetes metadata label key-value pairs. */
23
+ [key: string]: string;
24
+ }
25
+ /** Options accepted by {@link applyWithOwnership}. */
26
+ export interface ApplyWithOwnershipOptions extends OwnershipOptions {
27
+ /** Forwarded to KFC server-side apply. */
28
+ force?: boolean;
29
+ }
30
+ /** Options accepted by {@link deleteAllByOwnership}. */
31
+ export interface DeleteAllByOwnershipOptions extends OwnershipOptions, Pick<WaitForOptions, "timeoutMs" | "intervalMs" | "signal"> {
32
+ /** Restrict discovery and deletion to one namespace. */
33
+ namespace?: string;
34
+ /** Wait until no resources carry the exact ownership labels. */
35
+ waitForDeletion?: boolean;
36
+ }
37
+ /** Name and optional namespace identifying one Kubernetes resource. */
38
+ export interface ResourceReference {
39
+ /** Resource name. */
40
+ name: string;
41
+ /** Namespace for namespaced resources; omit for cluster-scoped resources. */
42
+ namespace?: string;
43
+ }
44
+ /** Options accepted by {@link waitForResource}. */
45
+ export interface WaitForResourceOptions<TDiagnostics = unknown> extends ResourceReference, WaitForOptions<TDiagnostics> {
46
+ /** Override the human-readable timeout description. */
47
+ description?: string;
48
+ }
49
+ /**
50
+ * Resolve and validate the exact labels used by ownership-aware helpers.
51
+ *
52
+ * Run IDs use a separate label so owner and execution identities cannot
53
+ * produce ambiguous composite values.
54
+ *
55
+ * @param options - Stable owner, optional run ID, and optional label key.
56
+ * @returns Validated Kubernetes label key-value pairs.
57
+ */
58
+ export declare function ownershipLabels(options: OwnershipOptions): OwnershipLabels;
59
+ /**
60
+ * Apply a resource after stamping a stable ownership label.
61
+ *
62
+ * The input object is not mutated. Any existing labels are preserved unless
63
+ * they use a selected ownership key.
64
+ *
65
+ * @param model - KFC model for the Kubernetes resource kind.
66
+ * @param resource - Resource body to server-side apply.
67
+ * @param options - Ownership labels and apply configuration.
68
+ * @returns The resource returned by Kubernetes.
69
+ */
70
+ export declare function applyWithOwnership<T extends GenericClass, K extends KubernetesObject = InstanceType<T>>(model: T, resource: PartialDeep<K>, options: ApplyWithOwnershipOptions): Promise<K>;
71
+ /**
72
+ * Delete every resource carrying the exact ownership labels.
73
+ *
74
+ * Kubernetes evaluates the exact label selectors when collection deletion is
75
+ * supported. Other APIs use UID and resource-version preconditions so a
76
+ * re-labeled or re-created resource cannot be deleted after discovery.
77
+ *
78
+ * @param model - KFC model for the Kubernetes resource kind.
79
+ * @param options - Ownership identity and optional namespace restriction.
80
+ */
81
+ export declare function deleteAllByOwnership<T extends GenericClass, K extends KubernetesObject = InstanceType<T>>(model: T, options: DeleteAllByOwnershipOptions): Promise<void>;
82
+ /**
83
+ * Wait for one named Kubernetes resource to exist.
84
+ *
85
+ * @param model - KFC model for the Kubernetes resource kind.
86
+ * @param options - Resource identity plus standard waiter options.
87
+ * @returns The resource read from Kubernetes.
88
+ */
89
+ export declare function waitForResource<T extends GenericClass, K extends KubernetesObject = InstanceType<T>, TDiagnostics = unknown>(model: T, options: WaitForResourceOptions<TDiagnostics>): Promise<K>;
90
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/test/resources.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAIhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAEzD,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AAEtE,gFAAgF;AAChF,eAAO,MAAM,iBAAiB,oCAAoC,CAAC;AAEnE,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,iDAAiD;IACjD,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED,sDAAsD;AACtD,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE,0CAA0C;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wDAAwD;AACxD,MAAM,WAAW,2BACf,SAAQ,gBAAgB,EAAE,IAAI,CAAC,cAAc,EAAE,WAAW,GAAG,YAAY,GAAG,QAAQ,CAAC;IACrF,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IAChC,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,sBAAsB,CAAC,YAAY,GAAG,OAAO,CAC5D,SAAQ,iBAAiB,EAAE,cAAc,CAAC,YAAY,CAAC;IACvD,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsCD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAoB1E;AAuJD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,gBAAgB,GAAG,YAAY,CAAC,CAAC,CAAC,EAC5C,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,CAAC,CAAC,CAcpF;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,gBAAgB,GAAG,YAAY,CAAC,CAAC,CAAC,EAC5C,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB/D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,gBAAgB,GAAG,YAAY,CAAC,CAAC,CAAC,EAC5C,YAAY,GAAG,OAAO,EACtB,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,sBAAsB,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CASrE"}
@@ -0,0 +1,248 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026-Present The Kubernetes Fluent Client Authors
3
+ import { K8s } from "../fluent/index.js";
4
+ import { FetchMethods } from "../fluent/shared-types.js";
5
+ import { k8sExec } from "../fluent/utils.js";
6
+ import { waitFor } from "./wait.js";
7
+ /** Default label used to identify resources created by integration tests. */
8
+ export const TEST_OWNERSHIP_LABEL = "test.defenseunicorns.dev/source";
9
+ /** Default label used to isolate one execution of an integration test suite. */
10
+ export const TEST_RUN_ID_LABEL = "test.defenseunicorns.dev/run-id";
11
+ const LABEL_NAME_PATTERN = /^[A-Za-z0-9](?:[-_.A-Za-z0-9]{0,61}[A-Za-z0-9])?$/;
12
+ const DNS_LABEL_PATTERN = /^[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?$/;
13
+ /**
14
+ * Determine whether a string is a valid Kubernetes label name.
15
+ *
16
+ * @param value - Label name or label value to inspect.
17
+ * @returns True when the value follows Kubernetes label-name syntax.
18
+ */
19
+ function isLabelName(value) {
20
+ return value.length <= 63 && LABEL_NAME_PATTERN.test(value);
21
+ }
22
+ /**
23
+ * Determine whether a string is a valid optional DNS label-key prefix.
24
+ *
25
+ * @param value - DNS prefix to inspect.
26
+ * @returns True when every segment follows Kubernetes DNS syntax.
27
+ */
28
+ function isDnsPrefix(value) {
29
+ return value.length <= 253 && value.split(".").every(segment => DNS_LABEL_PATTERN.test(segment));
30
+ }
31
+ /**
32
+ * Determine whether a string is a valid Kubernetes label key.
33
+ *
34
+ * @param value - Label key to inspect.
35
+ * @returns True when the key has a valid optional prefix and name.
36
+ */
37
+ function isLabelKey(value) {
38
+ const parts = value.split("/");
39
+ if (parts.length === 1)
40
+ return isLabelName(parts[0]);
41
+ if (parts.length !== 2)
42
+ return false;
43
+ return isDnsPrefix(parts[0]) && isLabelName(parts[1]);
44
+ }
45
+ /**
46
+ * Resolve and validate the exact labels used by ownership-aware helpers.
47
+ *
48
+ * Run IDs use a separate label so owner and execution identities cannot
49
+ * produce ambiguous composite values.
50
+ *
51
+ * @param options - Stable owner, optional run ID, and optional label key.
52
+ * @returns Validated Kubernetes label key-value pairs.
53
+ */
54
+ export function ownershipLabels(options) {
55
+ const key = options.labelKey ?? TEST_OWNERSHIP_LABEL;
56
+ if (!isLabelKey(key))
57
+ throw new Error(`Invalid Kubernetes ownership label key: ${key}`);
58
+ if (!isLabelName(options.owner)) {
59
+ throw new Error(`Invalid Kubernetes ownership label value: ${options.owner}`);
60
+ }
61
+ const labels = { [key]: options.owner };
62
+ if (options.runId !== undefined) {
63
+ const runIdKey = options.runIdLabelKey ?? TEST_RUN_ID_LABEL;
64
+ if (!isLabelKey(runIdKey)) {
65
+ throw new Error(`Invalid Kubernetes run-ID label key: ${runIdKey}`);
66
+ }
67
+ if (runIdKey === key)
68
+ throw new Error("Ownership and run-ID label keys must be different");
69
+ if (!isLabelName(options.runId)) {
70
+ throw new Error(`Invalid Kubernetes run-ID label value: ${options.runId}`);
71
+ }
72
+ labels[runIdKey] = options.runId;
73
+ }
74
+ return labels;
75
+ }
76
+ /**
77
+ * Create a fluent client with an optional namespace filter.
78
+ *
79
+ * @param model - KFC model for the resource kind.
80
+ * @param namespace - Optional namespace for namespaced resources.
81
+ * @returns A fluent resource client.
82
+ */
83
+ function resourceClient(model, namespace) {
84
+ const client = K8s(model);
85
+ return namespace ? client.InNamespace(namespace) : client;
86
+ }
87
+ /**
88
+ * Create a fluent client filtered by every ownership label.
89
+ *
90
+ * @param model - KFC model for the resource kind.
91
+ * @param options - Ownership identity and optional namespace.
92
+ * @returns A fluent resource client with exact label selectors.
93
+ */
94
+ function ownedResourceClient(model, options) {
95
+ let client = resourceClient(model, options.namespace);
96
+ for (const [key, value] of Object.entries(ownershipLabels(options))) {
97
+ client = client.WithLabel(key, value);
98
+ }
99
+ return client;
100
+ }
101
+ /**
102
+ * Validate a resource reference before it can affect a Kubernetes request path.
103
+ *
104
+ * @param reference - Resource name and optional namespace.
105
+ */
106
+ function validateResourceReference(reference) {
107
+ if (!reference.name.trim())
108
+ throw new Error("Resource name must not be blank");
109
+ if (reference.namespace !== undefined && !reference.namespace.trim()) {
110
+ throw new Error("Resource namespace must not be blank when provided");
111
+ }
112
+ }
113
+ /**
114
+ * Determine whether an error carries one HTTP status.
115
+ *
116
+ * @param error - Error raised by a Kubernetes request.
117
+ * @param status - HTTP status to match.
118
+ * @returns True when the error has the requested status.
119
+ */
120
+ function hasStatus(error, status) {
121
+ return (typeof error === "object" && error !== null && "status" in error && error.status === status);
122
+ }
123
+ /**
124
+ * Read the metadata required for a preconditioned deletion.
125
+ *
126
+ * @param model - KFC model for the resource kind.
127
+ * @param resource - Resource returned by an exact-label list operation.
128
+ * @returns The resource identity and version used by the delete request.
129
+ */
130
+ function deletionIdentity(model, resource) {
131
+ const { name, namespace, resourceVersion, uid } = resource.metadata ?? {};
132
+ if (!name || !uid || !resourceVersion) {
133
+ throw new Error(`Cannot safely delete owned ${model.name}: metadata.name, uid, and resourceVersion are required`);
134
+ }
135
+ return { name, namespace, resourceVersion, uid };
136
+ }
137
+ /**
138
+ * Delete one listed resource only if its identity and version have not changed.
139
+ *
140
+ * @param model - KFC model for the Kubernetes resource kind.
141
+ * @param resource - Resource returned by an exact-label list operation.
142
+ * @param namespace - Optional namespace restriction supplied by the caller.
143
+ */
144
+ async function deleteWithPreconditions(model, resource, namespace) {
145
+ const identity = deletionIdentity(model, resource);
146
+ try {
147
+ await k8sExec(model, { name: identity.name, namespace: namespace ?? identity.namespace }, {
148
+ method: FetchMethods.DELETE,
149
+ payload: {
150
+ apiVersion: "v1",
151
+ kind: "DeleteOptions",
152
+ preconditions: {
153
+ resourceVersion: identity.resourceVersion,
154
+ uid: identity.uid,
155
+ },
156
+ },
157
+ });
158
+ }
159
+ catch (error) {
160
+ if (!hasStatus(error, 404))
161
+ throw error;
162
+ }
163
+ }
164
+ /**
165
+ * Safely delete labeled resources for APIs without collection deletion.
166
+ *
167
+ * UID and resource-version preconditions prevent deletion if a listed object
168
+ * is re-created, re-labeled, or otherwise modified before the delete request.
169
+ *
170
+ * @param model - KFC model for the Kubernetes resource kind.
171
+ * @param options - Ownership identity and optional namespace restriction.
172
+ */
173
+ async function deleteWithoutCollectionSupport(model, options) {
174
+ await waitFor(`safe deletion of ${model.name} resources with the requested ownership labels`, async () => {
175
+ const list = await ownedResourceClient(model, options).Get();
176
+ if (list.items.length === 0)
177
+ return true;
178
+ await Promise.all(list.items.map(resource => deleteWithPreconditions(model, resource, options.namespace)));
179
+ return true;
180
+ }, options);
181
+ }
182
+ /**
183
+ * Apply a resource after stamping a stable ownership label.
184
+ *
185
+ * The input object is not mutated. Any existing labels are preserved unless
186
+ * they use a selected ownership key.
187
+ *
188
+ * @param model - KFC model for the Kubernetes resource kind.
189
+ * @param resource - Resource body to server-side apply.
190
+ * @param options - Ownership labels and apply configuration.
191
+ * @returns The resource returned by Kubernetes.
192
+ */
193
+ export function applyWithOwnership(model, resource, options) {
194
+ const labels = ownershipLabels(options);
195
+ const owned = {
196
+ ...resource,
197
+ metadata: {
198
+ ...resource.metadata,
199
+ labels: {
200
+ ...resource.metadata?.labels,
201
+ ...labels,
202
+ },
203
+ },
204
+ };
205
+ return K8s(model).Apply(owned, { force: options.force });
206
+ }
207
+ /**
208
+ * Delete every resource carrying the exact ownership labels.
209
+ *
210
+ * Kubernetes evaluates the exact label selectors when collection deletion is
211
+ * supported. Other APIs use UID and resource-version preconditions so a
212
+ * re-labeled or re-created resource cannot be deleted after discovery.
213
+ *
214
+ * @param model - KFC model for the Kubernetes resource kind.
215
+ * @param options - Ownership identity and optional namespace restriction.
216
+ */
217
+ export async function deleteAllByOwnership(model, options) {
218
+ if (options.namespace !== undefined && !options.namespace.trim()) {
219
+ throw new Error("Resource namespace must not be blank when provided");
220
+ }
221
+ try {
222
+ await ownedResourceClient(model, options).Delete();
223
+ }
224
+ catch (error) {
225
+ if (!hasStatus(error, 405))
226
+ throw error;
227
+ await deleteWithoutCollectionSupport(model, options);
228
+ }
229
+ if (options.waitForDeletion) {
230
+ await waitFor(`${model.name} resources with the requested ownership labels to be deleted`, async () => {
231
+ const remaining = await ownedResourceClient(model, options).Get();
232
+ return remaining.items.length === 0;
233
+ }, options);
234
+ }
235
+ }
236
+ /**
237
+ * Wait for one named Kubernetes resource to exist.
238
+ *
239
+ * @param model - KFC model for the Kubernetes resource kind.
240
+ * @param options - Resource identity plus standard waiter options.
241
+ * @returns The resource read from Kubernetes.
242
+ */
243
+ export function waitForResource(model, options) {
244
+ const { name, namespace, description, ...waitOptions } = options;
245
+ validateResourceReference({ name, namespace });
246
+ const identity = namespace ? `${model.name}/${namespace}/${name}` : `${model.name}/${name}`;
247
+ return waitFor(description ?? identity, () => resourceClient(model, namespace).Get(name), waitOptions);
248
+ }