@ankhorage/kubernetes 0.2.0 → 0.4.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 (26) hide show
  1. package/README.md +38 -1
  2. package/dist/features/workload-reconciliation/adapters/createKubectlKubernetesApi.d.ts +12 -0
  3. package/dist/features/workload-reconciliation/adapters/createKubectlKubernetesApi.d.ts.map +1 -0
  4. package/dist/features/workload-reconciliation/adapters/createKubectlKubernetesApi.js +157 -0
  5. package/dist/features/workload-reconciliation/adapters/createKubectlKubernetesApi.js.map +1 -0
  6. package/dist/features/workload-reconciliation/adapters/createSubprocessKubernetesCommandRunner.d.ts +4 -0
  7. package/dist/features/workload-reconciliation/adapters/createSubprocessKubernetesCommandRunner.d.ts.map +1 -0
  8. package/dist/features/workload-reconciliation/adapters/createSubprocessKubernetesCommandRunner.js +27 -0
  9. package/dist/features/workload-reconciliation/adapters/createSubprocessKubernetesCommandRunner.js.map +1 -0
  10. package/dist/features/workload-reconciliation/adapters/kubectlResource.d.ts +12 -0
  11. package/dist/features/workload-reconciliation/adapters/kubectlResource.d.ts.map +1 -0
  12. package/dist/features/workload-reconciliation/adapters/kubectlResource.js +198 -0
  13. package/dist/features/workload-reconciliation/adapters/kubectlResource.js.map +1 -0
  14. package/dist/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.d.ts +2 -0
  15. package/dist/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.d.ts.map +1 -1
  16. package/dist/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.js +2 -0
  17. package/dist/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.js.map +1 -1
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +2 -0
  21. package/dist/index.js.map +1 -1
  22. package/dist/types/kubectl.d.ts +25 -0
  23. package/dist/types/kubectl.d.ts.map +1 -0
  24. package/dist/types/kubectl.js +2 -0
  25. package/dist/types/kubectl.js.map +1 -0
  26. package/package.json +2 -2
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # @ankhorage/kubernetes
5
5
 
6
- ![license: MIT](././paradox/badges/license.svg) ![npm: v0.1.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v0.3.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![docs: paradox](././paradox/badges/docs.svg)
7
7
 
8
8
  Shared Kubernetes workload driver for provider-neutral Ankhorage infrastructure.
9
9
 
@@ -15,11 +15,30 @@ Shared Kubernetes workload driver for provider-neutral Ankhorage infrastructure.
15
15
  - [Architecture overview](././paradox/diagrams/architecture-overview.mmd)
16
16
  - [Module relationships](././paradox/diagrams/module-relationships.mmd)
17
17
  - [Export graph](././paradox/diagrams/export-graph.mmd)
18
+ - [createKubectlKubernetesApi sequence](././paradox/diagrams/sequences/create-kubectl-kubernetes-api.mmd)
18
19
 
19
20
  ## Public API
20
21
 
21
22
  ### Utilities
22
23
 
24
+ <details>
25
+ <summary>createKubectlKubernetesApi</summary>
26
+
27
+ ```ts
28
+ createKubectlKubernetesApi(options: KubectlKubernetesApiOptions) => KubernetesApi
29
+ ```
30
+
31
+ Create a concrete Kubernetes API backed by an authenticated kubectl context.
32
+
33
+ Commands are executed as argv arrays without a shell. Secret manifests are supplied only on
34
+ standard input and are never included in errors or observations.
35
+
36
+ Module: `src/features/workload-reconciliation/adapters/createKubectlKubernetesApi.ts`
37
+ Source: `src/features/workload-reconciliation/adapters/createKubectlKubernetesApi.ts:39:1`
38
+ Related symbols: `KubectlKubernetesApiOptions`, `KubernetesApi`
39
+
40
+ </details>
41
+
23
42
  <details>
24
43
  <summary>createKubernetesDriver</summary>
25
44
 
@@ -38,3 +57,21 @@ Source: `src/features/workload-reconciliation/composition/createKubernetesDriver
38
57
  Related symbols: `KubernetesDriver`, `KubernetesDriverOptions`
39
58
 
40
59
  </details>
60
+
61
+ <details>
62
+ <summary>projectKubernetesResourcesAsync</summary>
63
+
64
+ ```ts
65
+ projectKubernetesResourcesAsync(request: KubernetesDriverRequest) => Promise<InfraResult<KubernetesProjection>>
66
+ ```
67
+
68
+ Project portable workload desired state into deterministic standard Kubernetes resources.
69
+
70
+ Resolved secret values are deliberately absent. The projection carries only references for
71
+ runtime materialization immediately before apply.
72
+
73
+ Module: `src/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.ts`
74
+ Source: `src/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.ts:23:1`
75
+ Related symbols: `KubernetesDriverRequest`, `KubernetesProjection`
76
+
77
+ </details>
@@ -0,0 +1,12 @@
1
+ import type { KubectlKubernetesApiOptions } from '../../../types/kubectl';
2
+ import type { KubernetesApi } from '../../../types/kubernetesResources';
3
+ /***
4
+ * Create a concrete Kubernetes API backed by an authenticated kubectl context.
5
+ *
6
+ * Commands are executed as argv arrays without a shell. Secret manifests are supplied only on
7
+ * standard input and are never included in errors or observations.
8
+ *
9
+ * @readme
10
+ */
11
+ export declare function createKubectlKubernetesApi(options: KubectlKubernetesApiOptions): KubernetesApi;
12
+ //# sourceMappingURL=createKubectlKubernetesApi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createKubectlKubernetesApi.d.ts","sourceRoot":"","sources":["../../../../src/features/workload-reconciliation/adapters/createKubectlKubernetesApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,2BAA2B,EAG5B,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EACV,aAAa,EAKd,MAAM,oCAAoC,CAAC;AAmB5C;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,GAAG,aAAa,CAmB9F"}
@@ -0,0 +1,157 @@
1
+ import { createSubprocessKubernetesCommandRunner } from './createSubprocessKubernetesCommandRunner';
2
+ import { labelsInclude, observeKubectlResource, parseKubectlJsonRecord, parseKubectlResourceList, parseOptionalKubectlResource, } from './kubectlResource';
3
+ const NAMESPACED_RESOURCE_TYPES = [
4
+ 'configmaps',
5
+ 'secrets',
6
+ 'persistentvolumeclaims',
7
+ 'deployments.apps',
8
+ 'services',
9
+ 'ingresses.networking.k8s.io',
10
+ ].join(',');
11
+ /***
12
+ * Create a concrete Kubernetes API backed by an authenticated kubectl context.
13
+ *
14
+ * Commands are executed as argv arrays without a shell. Secret manifests are supplied only on
15
+ * standard input and are never included in errors or observations.
16
+ *
17
+ * @readme
18
+ */
19
+ export function createKubectlKubernetesApi(options) {
20
+ if (options.context.trim().length === 0) {
21
+ throw new TypeError('kubectl requires a non-empty authenticated context.');
22
+ }
23
+ const runner = options.runner ?? createSubprocessKubernetesCommandRunner();
24
+ const executable = options.executable ?? 'kubectl';
25
+ const pollIntervalMs = options.pollIntervalMs ?? 250;
26
+ if (!Number.isInteger(pollIntervalMs) || pollIntervalMs < 0) {
27
+ throw new TypeError('kubectl pollIntervalMs must be a non-negative integer.');
28
+ }
29
+ const command = createCommand(runner, executable, options.context);
30
+ return {
31
+ listOwnedAsync: (query) => listOwnedAsync(command, query),
32
+ applyAsync: (resource, signal) => applyAsync(command, resource, signal),
33
+ deleteAsync: (resource, signal) => deleteAsync(command, resource, signal),
34
+ observeAsync: (resource, signal) => observeAsync(command, resource, signal),
35
+ waitUntilReadyAsync: (resource, waitOptions) => waitUntilReadyAsync(command, resource, waitOptions, pollIntervalMs),
36
+ };
37
+ }
38
+ /*** Bind an executable and exact kubeconfig context to every kubectl invocation. */
39
+ function createCommand(runner, executable, context) {
40
+ return {
41
+ runAsync: (arguments_, options = {}) => runner.runAsync({
42
+ executable,
43
+ arguments: ['--context', context, ...arguments_],
44
+ ...(options.stdin === undefined ? {} : { stdin: options.stdin }),
45
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
46
+ }),
47
+ };
48
+ }
49
+ /*** List only resources selected by the driver's namespace and ownership labels. */
50
+ async function listOwnedAsync(command, query) {
51
+ const selector = Object.entries(query.labels)
52
+ .sort(([left], [right]) => left.localeCompare(right))
53
+ .map(([key, value]) => `${key}=${value}`)
54
+ .join(',');
55
+ const namespace = await command.runAsync(['get', 'namespace', query.namespace, '--ignore-not-found=true', '-o', 'json'], signalOption(query.signal));
56
+ ensureSuccess(namespace);
57
+ if (namespace.stdout.trim().length === 0)
58
+ return [];
59
+ const namespaced = await command.runAsync([
60
+ 'get',
61
+ NAMESPACED_RESOURCE_TYPES,
62
+ '--namespace',
63
+ query.namespace,
64
+ '-l',
65
+ selector,
66
+ '-o',
67
+ 'json',
68
+ ], signalOption(query.signal));
69
+ ensureSuccess(namespaced);
70
+ return [
71
+ ...parseOptionalKubectlResource(namespace.stdout).filter((resource) => labelsInclude(resource.metadata.labels, query.labels)),
72
+ ...parseKubectlResourceList(namespaced.stdout),
73
+ ];
74
+ }
75
+ /*** Apply one exact resource through standard input so values never enter argv. */
76
+ async function applyAsync(command, resource, signal) {
77
+ const result = await command.runAsync(['apply', '-f', '-'], {
78
+ stdin: JSON.stringify(resource),
79
+ ...(signal === undefined ? {} : { signal }),
80
+ });
81
+ ensureSuccess(result);
82
+ }
83
+ /*** Delete one exact resource identity and tolerate an already absent resource. */
84
+ async function deleteAsync(command, resource, signal) {
85
+ const result = await command.runAsync([
86
+ 'delete',
87
+ `${resource.kind}/${resource.name}`,
88
+ '--ignore-not-found=true',
89
+ ...(resource.namespace === undefined ? [] : ['--namespace', resource.namespace]),
90
+ ], signalOption(signal));
91
+ ensureSuccess(result);
92
+ }
93
+ /*** Read one live resource and map its standard readiness and public endpoint state. */
94
+ async function observeAsync(command, resource, signal) {
95
+ const result = await command.runAsync([
96
+ 'get',
97
+ `${resource.kind}/${resource.name}`,
98
+ ...(resource.namespace === undefined ? [] : ['--namespace', resource.namespace]),
99
+ '--ignore-not-found=true',
100
+ '-o',
101
+ 'json',
102
+ ], signalOption(signal));
103
+ ensureSuccess(result);
104
+ if (result.stdout.trim().length === 0)
105
+ return { state: 'absent' };
106
+ return observeKubectlResource(parseKubectlJsonRecord(result.stdout));
107
+ }
108
+ /*** Poll live resource state until ready, terminal failure or timeout. */
109
+ async function waitUntilReadyAsync(command, resource, options, pollIntervalMs) {
110
+ const deadline = Date.now() + options.timeoutSeconds * 1_000;
111
+ for (;;) {
112
+ const observation = await observeAsync(command, resource, options.signal);
113
+ if (observation.state === 'ready' || observation.state === 'failed')
114
+ return observation;
115
+ if (Date.now() >= deadline) {
116
+ return {
117
+ state: 'degraded',
118
+ detail: `Timed out waiting for ${resource.kind}/${resource.name}.`,
119
+ };
120
+ }
121
+ await delayAsync(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())), options.signal);
122
+ }
123
+ }
124
+ /*** Throw a sanitized failure that never includes stdin, stdout or stderr content. */
125
+ function ensureSuccess(result) {
126
+ if (result.exitCode !== 0)
127
+ throw new Error('kubectl command failed.');
128
+ }
129
+ /*** Build an optional signal bag without assigning undefined. */
130
+ function signalOption(signal) {
131
+ return signal === undefined ? {} : { signal };
132
+ }
133
+ /*** Wait between API observations and abort promptly when requested. */
134
+ function delayAsync(milliseconds, signal) {
135
+ if (signal === undefined)
136
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
137
+ return new Promise((resolve, reject) => {
138
+ if (signal.aborted) {
139
+ reject(abortError(signal));
140
+ return;
141
+ }
142
+ const onAbort = () => {
143
+ clearTimeout(timeout);
144
+ reject(abortError(signal));
145
+ };
146
+ const timeout = setTimeout(() => {
147
+ signal.removeEventListener('abort', onAbort);
148
+ resolve();
149
+ }, milliseconds);
150
+ signal.addEventListener('abort', onAbort, { once: true });
151
+ });
152
+ }
153
+ /*** Normalize an AbortSignal reason to an Error for predictable rejection handling. */
154
+ function abortError(signal) {
155
+ return signal.reason instanceof Error ? signal.reason : new Error('kubectl operation aborted.');
156
+ }
157
+ //# sourceMappingURL=createKubectlKubernetesApi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createKubectlKubernetesApi.js","sourceRoot":"","sources":["../../../../src/features/workload-reconciliation/adapters/createKubectlKubernetesApi.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n KubectlKubernetesApiOptions,\n KubernetesCommandResult,\n KubernetesCommandRunner,\n} from '../../../types/kubectl';\nimport type {\n KubernetesApi,\n KubernetesOwnershipQuery,\n KubernetesResource,\n KubernetesResourceObservation,\n KubernetesResourceReference,\n} from '../../../types/kubernetesResources';\nimport { createSubprocessKubernetesCommandRunner } from './createSubprocessKubernetesCommandRunner';\nimport {\n labelsInclude,\n observeKubectlResource,\n parseKubectlJsonRecord,\n parseKubectlResourceList,\n parseOptionalKubectlResource,\n} from './kubectlResource';\n\nconst NAMESPACED_RESOURCE_TYPES = [\n 'configmaps',\n 'secrets',\n 'persistentvolumeclaims',\n 'deployments.apps',\n 'services',\n 'ingresses.networking.k8s.io',\n].join(',');\n\n/***\n * Create a concrete Kubernetes API backed by an authenticated kubectl context.\n *\n * Commands are executed as argv arrays without a shell. Secret manifests are supplied only on\n * standard input and are never included in errors or observations.\n *\n * @readme\n */\nexport function createKubectlKubernetesApi(options: KubectlKubernetesApiOptions): KubernetesApi {\n if (options.context.trim().length === 0) {\n throw new TypeError('kubectl requires a non-empty authenticated context.');\n }\n const runner = options.runner ?? createSubprocessKubernetesCommandRunner();\n const executable = options.executable ?? 'kubectl';\n const pollIntervalMs = options.pollIntervalMs ?? 250;\n if (!Number.isInteger(pollIntervalMs) || pollIntervalMs < 0) {\n throw new TypeError('kubectl pollIntervalMs must be a non-negative integer.');\n }\n const command = createCommand(runner, executable, options.context);\n return {\n listOwnedAsync: (query) => listOwnedAsync(command, query),\n applyAsync: (resource, signal) => applyAsync(command, resource, signal),\n deleteAsync: (resource, signal) => deleteAsync(command, resource, signal),\n observeAsync: (resource, signal) => observeAsync(command, resource, signal),\n waitUntilReadyAsync: (resource, waitOptions) =>\n waitUntilReadyAsync(command, resource, waitOptions, pollIntervalMs),\n };\n}\n\ninterface KubectlCommand {\n runAsync(\n arguments_: readonly string[],\n options?: { readonly stdin?: string; readonly signal?: AbortSignal },\n ): Promise<KubernetesCommandResult>;\n}\n\n/*** Bind an executable and exact kubeconfig context to every kubectl invocation. */\nfunction createCommand(\n runner: KubernetesCommandRunner,\n executable: string,\n context: string,\n): KubectlCommand {\n return {\n runAsync: (arguments_, options = {}) =>\n runner.runAsync({\n executable,\n arguments: ['--context', context, ...arguments_],\n ...(options.stdin === undefined ? {} : { stdin: options.stdin }),\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n }),\n };\n}\n\n/*** List only resources selected by the driver's namespace and ownership labels. */\nasync function listOwnedAsync(\n command: KubectlCommand,\n query: KubernetesOwnershipQuery,\n): Promise<readonly KubernetesResource[]> {\n const selector = Object.entries(query.labels)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, value]) => `${key}=${value}`)\n .join(',');\n const namespace = await command.runAsync(\n ['get', 'namespace', query.namespace, '--ignore-not-found=true', '-o', 'json'],\n signalOption(query.signal),\n );\n ensureSuccess(namespace);\n if (namespace.stdout.trim().length === 0) return [];\n const namespaced = await command.runAsync(\n [\n 'get',\n NAMESPACED_RESOURCE_TYPES,\n '--namespace',\n query.namespace,\n '-l',\n selector,\n '-o',\n 'json',\n ],\n signalOption(query.signal),\n );\n ensureSuccess(namespaced);\n return [\n ...parseOptionalKubectlResource(namespace.stdout).filter((resource) =>\n labelsInclude(resource.metadata.labels, query.labels),\n ),\n ...parseKubectlResourceList(namespaced.stdout),\n ];\n}\n\n/*** Apply one exact resource through standard input so values never enter argv. */\nasync function applyAsync(\n command: KubectlCommand,\n resource: KubernetesResource,\n signal?: AbortSignal,\n): Promise<void> {\n const result = await command.runAsync(['apply', '-f', '-'], {\n stdin: JSON.stringify(resource),\n ...(signal === undefined ? {} : { signal }),\n });\n ensureSuccess(result);\n}\n\n/*** Delete one exact resource identity and tolerate an already absent resource. */\nasync function deleteAsync(\n command: KubectlCommand,\n resource: KubernetesResourceReference,\n signal?: AbortSignal,\n): Promise<void> {\n const result = await command.runAsync(\n [\n 'delete',\n `${resource.kind}/${resource.name}`,\n '--ignore-not-found=true',\n ...(resource.namespace === undefined ? [] : ['--namespace', resource.namespace]),\n ],\n signalOption(signal),\n );\n ensureSuccess(result);\n}\n\n/*** Read one live resource and map its standard readiness and public endpoint state. */\nasync function observeAsync(\n command: KubectlCommand,\n resource: KubernetesResourceReference,\n signal?: AbortSignal,\n): Promise<KubernetesResourceObservation> {\n const result = await command.runAsync(\n [\n 'get',\n `${resource.kind}/${resource.name}`,\n ...(resource.namespace === undefined ? [] : ['--namespace', resource.namespace]),\n '--ignore-not-found=true',\n '-o',\n 'json',\n ],\n signalOption(signal),\n );\n ensureSuccess(result);\n if (result.stdout.trim().length === 0) return { state: 'absent' };\n return observeKubectlResource(parseKubectlJsonRecord(result.stdout));\n}\n\n/*** Poll live resource state until ready, terminal failure or timeout. */\nasync function waitUntilReadyAsync(\n command: KubectlCommand,\n resource: KubernetesResourceReference,\n options: { readonly timeoutSeconds: number; readonly signal?: AbortSignal },\n pollIntervalMs: number,\n): Promise<KubernetesResourceObservation> {\n const deadline = Date.now() + options.timeoutSeconds * 1_000;\n for (;;) {\n const observation = await observeAsync(command, resource, options.signal);\n if (observation.state === 'ready' || observation.state === 'failed') return observation;\n if (Date.now() >= deadline) {\n return {\n state: 'degraded',\n detail: `Timed out waiting for ${resource.kind}/${resource.name}.`,\n };\n }\n await delayAsync(Math.min(pollIntervalMs, Math.max(0, deadline - Date.now())), options.signal);\n }\n}\n\n/*** Throw a sanitized failure that never includes stdin, stdout or stderr content. */\nfunction ensureSuccess(result: KubernetesCommandResult): void {\n if (result.exitCode !== 0) throw new Error('kubectl command failed.');\n}\n\n/*** Build an optional signal bag without assigning undefined. */\nfunction signalOption(signal?: AbortSignal): { readonly signal?: AbortSignal } {\n return signal === undefined ? {} : { signal };\n}\n\n/*** Wait between API observations and abort promptly when requested. */\nfunction delayAsync(milliseconds: number, signal?: AbortSignal): Promise<void> {\n if (signal === undefined) return new Promise((resolve) => setTimeout(resolve, milliseconds));\n return new Promise((resolve, reject) => {\n if (signal.aborted) {\n reject(abortError(signal));\n return;\n }\n const onAbort = () => {\n clearTimeout(timeout);\n reject(abortError(signal));\n };\n const timeout = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, milliseconds);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/*** Normalize an AbortSignal reason to an Error for predictable rejection handling. */\nfunction abortError(signal: AbortSignal): Error {\n return signal.reason instanceof Error ? signal.reason : new Error('kubectl operation aborted.');\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { KubernetesCommandRunner } from '../../../types/kubectl';
2
+ /*** Create the concrete shell-free subprocess boundary used by kubectl operations. */
3
+ export declare function createSubprocessKubernetesCommandRunner(): KubernetesCommandRunner;
4
+ //# sourceMappingURL=createSubprocessKubernetesCommandRunner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createSubprocessKubernetesCommandRunner.d.ts","sourceRoot":"","sources":["../../../../src/features/workload-reconciliation/adapters/createSubprocessKubernetesCommandRunner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,uBAAuB,EACxB,MAAM,wBAAwB,CAAC;AAEhC,sFAAsF;AACtF,wBAAgB,uCAAuC,IAAI,uBAAuB,CAEjF"}
@@ -0,0 +1,27 @@
1
+ import { spawn } from 'node:child_process';
2
+ /*** Create the concrete shell-free subprocess boundary used by kubectl operations. */
3
+ export function createSubprocessKubernetesCommandRunner() {
4
+ return { runAsync };
5
+ }
6
+ /*** Execute one argv-safe command while keeping standard input out of diagnostics. */
7
+ function runAsync(request) {
8
+ return new Promise((resolve, reject) => {
9
+ const child = spawn(request.executable, [...request.arguments], {
10
+ shell: false,
11
+ stdio: ['pipe', 'pipe', 'pipe'],
12
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
13
+ });
14
+ const stdout = [];
15
+ const stderr = [];
16
+ child.stdout.on('data', (chunk) => stdout.push(chunk));
17
+ child.stderr.on('data', (chunk) => stderr.push(chunk));
18
+ child.once('error', reject);
19
+ child.once('close', (exitCode) => resolve({
20
+ exitCode: exitCode ?? 1,
21
+ stdout: Buffer.concat(stdout).toString('utf8'),
22
+ stderr: Buffer.concat(stderr).toString('utf8'),
23
+ }));
24
+ child.stdin.end(request.stdin);
25
+ });
26
+ }
27
+ //# sourceMappingURL=createSubprocessKubernetesCommandRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createSubprocessKubernetesCommandRunner.js","sourceRoot":"","sources":["../../../../src/features/workload-reconciliation/adapters/createSubprocessKubernetesCommandRunner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAQ3C,sFAAsF;AACtF,MAAM,UAAU,uCAAuC;IACrD,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAED,sFAAsF;AACtF,SAAS,QAAQ,CAAC,OAAiC;IACjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,EAAE;YAC9D,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAC/B,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;SACpE,CAAC,CAAC;QACH,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/D,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/D,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE,CAC/B,OAAO,CAAC;YACN,QAAQ,EAAE,QAAQ,IAAI,CAAC;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;SAC/C,CAAC,CACH,CAAC;QACF,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { spawn } from 'node:child_process';\n\nimport type {\n KubernetesCommandRequest,\n KubernetesCommandResult,\n KubernetesCommandRunner,\n} from '../../../types/kubectl';\n\n/*** Create the concrete shell-free subprocess boundary used by kubectl operations. */\nexport function createSubprocessKubernetesCommandRunner(): KubernetesCommandRunner {\n return { runAsync };\n}\n\n/*** Execute one argv-safe command while keeping standard input out of diagnostics. */\nfunction runAsync(request: KubernetesCommandRequest): Promise<KubernetesCommandResult> {\n return new Promise((resolve, reject) => {\n const child = spawn(request.executable, [...request.arguments], {\n shell: false,\n stdio: ['pipe', 'pipe', 'pipe'],\n ...(request.signal === undefined ? {} : { signal: request.signal }),\n });\n const stdout: Buffer[] = [];\n const stderr: Buffer[] = [];\n child.stdout.on('data', (chunk: Buffer) => stdout.push(chunk));\n child.stderr.on('data', (chunk: Buffer) => stderr.push(chunk));\n child.once('error', reject);\n child.once('close', (exitCode) =>\n resolve({\n exitCode: exitCode ?? 1,\n stdout: Buffer.concat(stdout).toString('utf8'),\n stderr: Buffer.concat(stderr).toString('utf8'),\n }),\n );\n child.stdin.end(request.stdin);\n });\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import type { KubernetesResource, KubernetesResourceObservation } from '../../../types/kubernetesResources';
2
+ /*** Parse a kubectl list response into comparison-safe desired resource shapes. */
3
+ export declare function parseKubectlResourceList(value: string): readonly KubernetesResource[];
4
+ /*** Parse an optional single-resource response. */
5
+ export declare function parseOptionalKubectlResource(value: string): readonly KubernetesResource[];
6
+ /*** Parse one JSON object returned by kubectl. */
7
+ export declare function parseKubectlJsonRecord(value: string): Readonly<Record<string, unknown>>;
8
+ /*** Map standard Kubernetes status fields without exposing Secret data. */
9
+ export declare function observeKubectlResource(resource: Readonly<Record<string, unknown>>): KubernetesResourceObservation;
10
+ /*** Match a required subset of labels without dynamic object-property access. */
11
+ export declare function labelsInclude(actual: Readonly<Record<string, string>>, expected: Readonly<Record<string, string>>): boolean;
12
+ //# sourceMappingURL=kubectlResource.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kubectlResource.d.ts","sourceRoot":"","sources":["../../../../src/features/workload-reconciliation/adapters/kubectlResource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,6BAA6B,EAC9B,MAAM,oCAAoC,CAAC;AAI5C,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,kBAAkB,EAAE,CAKrF;AAED,mDAAmD;AACnD,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,kBAAkB,EAAE,CAEzF;AAED,kDAAkD;AAClD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAQvF;AAED,2EAA2E;AAC3E,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,6BAA6B,CA2B/B;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACxC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACzC,OAAO,CAMT"}
@@ -0,0 +1,198 @@
1
+ const LAST_APPLIED_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration';
2
+ /*** Parse a kubectl list response into comparison-safe desired resource shapes. */
3
+ export function parseKubectlResourceList(value) {
4
+ const parsed = parseKubectlJsonRecord(value);
5
+ const items = getValue(parsed, 'items');
6
+ if (!Array.isArray(items))
7
+ throw new Error('kubectl returned an invalid resource list.');
8
+ return items.map(parseComparisonResource);
9
+ }
10
+ /*** Parse an optional single-resource response. */
11
+ export function parseOptionalKubectlResource(value) {
12
+ return value.trim().length === 0 ? [] : [parseComparisonResource(parseKubectlJsonRecord(value))];
13
+ }
14
+ /*** Parse one JSON object returned by kubectl. */
15
+ export function parseKubectlJsonRecord(value) {
16
+ try {
17
+ const parsed = JSON.parse(value);
18
+ if (isRecord(parsed))
19
+ return parsed;
20
+ }
21
+ catch {
22
+ // Use the canonical sanitized error below.
23
+ }
24
+ throw new Error('kubectl returned invalid JSON.');
25
+ }
26
+ /*** Map standard Kubernetes status fields without exposing Secret data. */
27
+ export function observeKubectlResource(resource) {
28
+ const kind = readString(resource, 'kind');
29
+ const metadata = readRecord(resource, 'metadata');
30
+ const spec = optionalRecord(resource, 'spec');
31
+ const status = optionalRecord(resource, 'status');
32
+ const publicOutputs = readPublicOutputs(kind, spec, status);
33
+ if (kind === 'Deployment') {
34
+ const desired = readNumber(spec, 'replicas') ?? 1;
35
+ const available = readNumber(status, 'availableReplicas') ?? 0;
36
+ const generation = readNumber(metadata, 'generation') ?? 0;
37
+ const observedGeneration = readNumber(status, 'observedGeneration') ?? 0;
38
+ return withOutputs(available >= desired && observedGeneration >= generation ? 'ready' : 'pending', publicOutputs);
39
+ }
40
+ if (kind === 'PersistentVolumeClaim') {
41
+ const phase = optionalString(status, 'phase');
42
+ return withOutputs(phase === 'Bound' ? 'ready' : phase === 'Lost' ? 'failed' : 'pending');
43
+ }
44
+ if (kind === 'Namespace') {
45
+ const phase = optionalString(status, 'phase');
46
+ return withOutputs(phase === 'Active' ? 'ready' : phase === 'Terminating' ? 'failed' : 'pending');
47
+ }
48
+ return withOutputs('ready', publicOutputs);
49
+ }
50
+ /*** Match a required subset of labels without dynamic object-property access. */
51
+ export function labelsInclude(actual, expected) {
52
+ return Object.entries(expected).every(([expectedKey, expectedValue]) => Object.entries(actual).some(([actualKey, actualValue]) => actualKey === expectedKey && actualValue === expectedValue));
53
+ }
54
+ /*** Prefer kubectl's exact last-applied shape to avoid server-default drift during planning. */
55
+ function parseComparisonResource(value) {
56
+ const applied = readLastApplied(value);
57
+ if (applied === undefined)
58
+ return parseResource(value);
59
+ try {
60
+ return parseResource(JSON.parse(applied));
61
+ }
62
+ catch {
63
+ throw new Error('kubectl returned an invalid last-applied resource.');
64
+ }
65
+ }
66
+ /*** Read kubectl's comparison annotation before stripping bookkeeping metadata. */
67
+ function readLastApplied(value) {
68
+ if (!isRecord(value))
69
+ return undefined;
70
+ const metadata = optionalRecord(value, 'metadata');
71
+ const annotations = optionalStringRecord(metadata, 'annotations');
72
+ return annotations === undefined
73
+ ? undefined
74
+ : getStringValue(annotations, LAST_APPLIED_ANNOTATION);
75
+ }
76
+ /*** Parse the generic resource fields owned by the shared Kubernetes driver. */
77
+ function parseResource(value) {
78
+ if (!isRecord(value))
79
+ throw new Error('kubectl returned an invalid resource.');
80
+ const metadata = readRecord(value, 'metadata');
81
+ const namespace = optionalString(metadata, 'namespace');
82
+ const data = optionalStringRecord(value, 'data');
83
+ const stringData = optionalStringRecord(value, 'stringData');
84
+ const spec = optionalRecord(value, 'spec');
85
+ return {
86
+ apiVersion: readString(value, 'apiVersion'),
87
+ kind: readString(value, 'kind'),
88
+ metadata: {
89
+ name: readString(metadata, 'name'),
90
+ ...(namespace === undefined ? {} : { namespace }),
91
+ labels: optionalStringRecord(metadata, 'labels') ?? {},
92
+ annotations: withoutLastApplied(optionalStringRecord(metadata, 'annotations') ?? {}),
93
+ },
94
+ ...(data === undefined ? {} : { data }),
95
+ ...(stringData === undefined ? {} : { stringData }),
96
+ ...(spec === undefined ? {} : { spec }),
97
+ };
98
+ }
99
+ /*** Read only endpoint values that Kubernetes explicitly exposes as public routing state. */
100
+ function readPublicOutputs(kind, spec, status) {
101
+ if (kind === 'Ingress') {
102
+ const rules = getValue(spec, 'rules');
103
+ const firstRule = isUnknownArray(rules) ? rules.at(0) : undefined;
104
+ const host = isRecord(firstRule) ? optionalString(firstRule, 'host') : undefined;
105
+ return host === undefined ? undefined : { endpoint: `https://${host}` };
106
+ }
107
+ if (kind !== 'Service')
108
+ return undefined;
109
+ const loadBalancer = optionalRecord(status, 'loadBalancer');
110
+ const ingress = getValue(loadBalancer, 'ingress');
111
+ const firstIngress = isUnknownArray(ingress) ? ingress.at(0) : undefined;
112
+ if (!isRecord(firstIngress))
113
+ return undefined;
114
+ const endpoint = optionalString(firstIngress, 'hostname') ?? optionalString(firstIngress, 'ip');
115
+ return endpoint === undefined ? undefined : { endpoint };
116
+ }
117
+ /*** Add optional public outputs to a resource observation. */
118
+ function withOutputs(state, publicOutputs) {
119
+ return { state, ...(publicOutputs === undefined ? {} : { publicOutputs }) };
120
+ }
121
+ /*** Remove kubectl's bookkeeping annotation from driver-owned comparison metadata. */
122
+ function withoutLastApplied(annotations) {
123
+ return Object.fromEntries(Object.entries(annotations).filter(([key]) => key !== LAST_APPLIED_ANNOTATION));
124
+ }
125
+ /*** Narrow an unknown value to a plain record. */
126
+ function isRecord(value) {
127
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
128
+ }
129
+ /*** Narrow an unknown value to an array whose entries remain unknown. */
130
+ function isUnknownArray(value) {
131
+ return Array.isArray(value);
132
+ }
133
+ /*** Read a property through entry iteration to avoid trusting dynamic object prototypes. */
134
+ function getValue(value, key) {
135
+ const entry = value === undefined ? undefined : Object.entries(value).find(([name]) => name === key);
136
+ return entry?.at(1);
137
+ }
138
+ /*** Read one required string field. */
139
+ function readString(value, key) {
140
+ const item = getValue(value, key);
141
+ if (typeof item !== 'string' || item.length === 0) {
142
+ throw new Error('kubectl returned an invalid resource field.');
143
+ }
144
+ return item;
145
+ }
146
+ /*** Read one optional string field. */
147
+ function optionalString(value, key) {
148
+ const item = getValue(value, key);
149
+ if (item === undefined)
150
+ return undefined;
151
+ if (typeof item !== 'string')
152
+ throw new Error('kubectl returned an invalid resource field.');
153
+ return item;
154
+ }
155
+ /*** Read one required record field. */
156
+ function readRecord(value, key) {
157
+ const item = getValue(value, key);
158
+ if (!isRecord(item))
159
+ throw new Error('kubectl returned an invalid resource field.');
160
+ return item;
161
+ }
162
+ /*** Read one optional record field. */
163
+ function optionalRecord(value, key) {
164
+ const item = getValue(value, key);
165
+ if (item === undefined)
166
+ return undefined;
167
+ if (!isRecord(item))
168
+ throw new Error('kubectl returned an invalid resource field.');
169
+ return item;
170
+ }
171
+ /*** Read one optional finite number field. */
172
+ function readNumber(value, key) {
173
+ const item = getValue(value, key);
174
+ return typeof item === 'number' && Number.isFinite(item) ? item : undefined;
175
+ }
176
+ /*** Read one optional string record field. */
177
+ function optionalStringRecord(value, key) {
178
+ const item = getValue(value, key);
179
+ if (item === undefined)
180
+ return undefined;
181
+ if (!isRecord(item))
182
+ throw new Error('kubectl returned an invalid string record.');
183
+ const entries = [];
184
+ for (const [entryKey, entryValue] of Object.entries(item)) {
185
+ if (typeof entryValue !== 'string') {
186
+ throw new Error('kubectl returned an invalid string record.');
187
+ }
188
+ entries.push([entryKey, entryValue]);
189
+ }
190
+ return Object.fromEntries(entries);
191
+ }
192
+ /*** Read a string value from a validated string record. */
193
+ function getStringValue(value, key) {
194
+ return Object.entries(value)
195
+ .find(([entryKey]) => entryKey === key)
196
+ ?.at(1);
197
+ }
198
+ //# sourceMappingURL=kubectlResource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kubectlResource.js","sourceRoot":"","sources":["../../../../src/features/workload-reconciliation/adapters/kubectlResource.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n KubernetesResource,\n KubernetesResourceObservation,\n} from '../../../types/kubernetesResources';\n\nconst LAST_APPLIED_ANNOTATION = 'kubectl.kubernetes.io/last-applied-configuration';\n\n/*** Parse a kubectl list response into comparison-safe desired resource shapes. */\nexport function parseKubectlResourceList(value: string): readonly KubernetesResource[] {\n const parsed = parseKubectlJsonRecord(value);\n const items = getValue(parsed, 'items');\n if (!Array.isArray(items)) throw new Error('kubectl returned an invalid resource list.');\n return items.map(parseComparisonResource);\n}\n\n/*** Parse an optional single-resource response. */\nexport function parseOptionalKubectlResource(value: string): readonly KubernetesResource[] {\n return value.trim().length === 0 ? [] : [parseComparisonResource(parseKubectlJsonRecord(value))];\n}\n\n/*** Parse one JSON object returned by kubectl. */\nexport function parseKubectlJsonRecord(value: string): Readonly<Record<string, unknown>> {\n try {\n const parsed: unknown = JSON.parse(value);\n if (isRecord(parsed)) return parsed;\n } catch {\n // Use the canonical sanitized error below.\n }\n throw new Error('kubectl returned invalid JSON.');\n}\n\n/*** Map standard Kubernetes status fields without exposing Secret data. */\nexport function observeKubectlResource(\n resource: Readonly<Record<string, unknown>>,\n): KubernetesResourceObservation {\n const kind = readString(resource, 'kind');\n const metadata = readRecord(resource, 'metadata');\n const spec = optionalRecord(resource, 'spec');\n const status = optionalRecord(resource, 'status');\n const publicOutputs = readPublicOutputs(kind, spec, status);\n if (kind === 'Deployment') {\n const desired = readNumber(spec, 'replicas') ?? 1;\n const available = readNumber(status, 'availableReplicas') ?? 0;\n const generation = readNumber(metadata, 'generation') ?? 0;\n const observedGeneration = readNumber(status, 'observedGeneration') ?? 0;\n return withOutputs(\n available >= desired && observedGeneration >= generation ? 'ready' : 'pending',\n publicOutputs,\n );\n }\n if (kind === 'PersistentVolumeClaim') {\n const phase = optionalString(status, 'phase');\n return withOutputs(phase === 'Bound' ? 'ready' : phase === 'Lost' ? 'failed' : 'pending');\n }\n if (kind === 'Namespace') {\n const phase = optionalString(status, 'phase');\n return withOutputs(\n phase === 'Active' ? 'ready' : phase === 'Terminating' ? 'failed' : 'pending',\n );\n }\n return withOutputs('ready', publicOutputs);\n}\n\n/*** Match a required subset of labels without dynamic object-property access. */\nexport function labelsInclude(\n actual: Readonly<Record<string, string>>,\n expected: Readonly<Record<string, string>>,\n): boolean {\n return Object.entries(expected).every(([expectedKey, expectedValue]) =>\n Object.entries(actual).some(\n ([actualKey, actualValue]) => actualKey === expectedKey && actualValue === expectedValue,\n ),\n );\n}\n\n/*** Prefer kubectl's exact last-applied shape to avoid server-default drift during planning. */\nfunction parseComparisonResource(value: unknown): KubernetesResource {\n const applied = readLastApplied(value);\n if (applied === undefined) return parseResource(value);\n try {\n return parseResource(JSON.parse(applied));\n } catch {\n throw new Error('kubectl returned an invalid last-applied resource.');\n }\n}\n\n/*** Read kubectl's comparison annotation before stripping bookkeeping metadata. */\nfunction readLastApplied(value: unknown): string | undefined {\n if (!isRecord(value)) return undefined;\n const metadata = optionalRecord(value, 'metadata');\n const annotations = optionalStringRecord(metadata, 'annotations');\n return annotations === undefined\n ? undefined\n : getStringValue(annotations, LAST_APPLIED_ANNOTATION);\n}\n\n/*** Parse the generic resource fields owned by the shared Kubernetes driver. */\nfunction parseResource(value: unknown): KubernetesResource {\n if (!isRecord(value)) throw new Error('kubectl returned an invalid resource.');\n const metadata = readRecord(value, 'metadata');\n const namespace = optionalString(metadata, 'namespace');\n const data = optionalStringRecord(value, 'data');\n const stringData = optionalStringRecord(value, 'stringData');\n const spec = optionalRecord(value, 'spec');\n return {\n apiVersion: readString(value, 'apiVersion'),\n kind: readString(value, 'kind'),\n metadata: {\n name: readString(metadata, 'name'),\n ...(namespace === undefined ? {} : { namespace }),\n labels: optionalStringRecord(metadata, 'labels') ?? {},\n annotations: withoutLastApplied(optionalStringRecord(metadata, 'annotations') ?? {}),\n },\n ...(data === undefined ? {} : { data }),\n ...(stringData === undefined ? {} : { stringData }),\n ...(spec === undefined ? {} : { spec }),\n };\n}\n\n/*** Read only endpoint values that Kubernetes explicitly exposes as public routing state. */\nfunction readPublicOutputs(\n kind: string,\n spec: Readonly<Record<string, unknown>> | undefined,\n status: Readonly<Record<string, unknown>> | undefined,\n): Readonly<Record<string, string>> | undefined {\n if (kind === 'Ingress') {\n const rules = getValue(spec, 'rules');\n const firstRule = isUnknownArray(rules) ? rules.at(0) : undefined;\n const host = isRecord(firstRule) ? optionalString(firstRule, 'host') : undefined;\n return host === undefined ? undefined : { endpoint: `https://${host}` };\n }\n if (kind !== 'Service') return undefined;\n const loadBalancer = optionalRecord(status, 'loadBalancer');\n const ingress = getValue(loadBalancer, 'ingress');\n const firstIngress = isUnknownArray(ingress) ? ingress.at(0) : undefined;\n if (!isRecord(firstIngress)) return undefined;\n const endpoint = optionalString(firstIngress, 'hostname') ?? optionalString(firstIngress, 'ip');\n return endpoint === undefined ? undefined : { endpoint };\n}\n\n/*** Add optional public outputs to a resource observation. */\nfunction withOutputs(\n state: KubernetesResourceObservation['state'],\n publicOutputs?: Readonly<Record<string, string>>,\n): KubernetesResourceObservation {\n return { state, ...(publicOutputs === undefined ? {} : { publicOutputs }) };\n}\n\n/*** Remove kubectl's bookkeeping annotation from driver-owned comparison metadata. */\nfunction withoutLastApplied(\n annotations: Readonly<Record<string, string>>,\n): Readonly<Record<string, string>> {\n return Object.fromEntries(\n Object.entries(annotations).filter(([key]) => key !== LAST_APPLIED_ANNOTATION),\n );\n}\n\n/*** Narrow an unknown value to a plain record. */\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/*** Narrow an unknown value to an array whose entries remain unknown. */\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n return Array.isArray(value);\n}\n\n/*** Read a property through entry iteration to avoid trusting dynamic object prototypes. */\nfunction getValue(value: Readonly<Record<string, unknown>> | undefined, key: string): unknown {\n const entry =\n value === undefined ? undefined : Object.entries(value).find(([name]) => name === key);\n return entry?.at(1);\n}\n\n/*** Read one required string field. */\nfunction readString(value: Readonly<Record<string, unknown>>, key: string): string {\n const item = getValue(value, key);\n if (typeof item !== 'string' || item.length === 0) {\n throw new Error('kubectl returned an invalid resource field.');\n }\n return item;\n}\n\n/*** Read one optional string field. */\nfunction optionalString(\n value: Readonly<Record<string, unknown>> | undefined,\n key: string,\n): string | undefined {\n const item = getValue(value, key);\n if (item === undefined) return undefined;\n if (typeof item !== 'string') throw new Error('kubectl returned an invalid resource field.');\n return item;\n}\n\n/*** Read one required record field. */\nfunction readRecord(\n value: Readonly<Record<string, unknown>>,\n key: string,\n): Readonly<Record<string, unknown>> {\n const item = getValue(value, key);\n if (!isRecord(item)) throw new Error('kubectl returned an invalid resource field.');\n return item;\n}\n\n/*** Read one optional record field. */\nfunction optionalRecord(\n value: Readonly<Record<string, unknown>> | undefined,\n key: string,\n): Readonly<Record<string, unknown>> | undefined {\n const item = getValue(value, key);\n if (item === undefined) return undefined;\n if (!isRecord(item)) throw new Error('kubectl returned an invalid resource field.');\n return item;\n}\n\n/*** Read one optional finite number field. */\nfunction readNumber(\n value: Readonly<Record<string, unknown>> | undefined,\n key: string,\n): number | undefined {\n const item = getValue(value, key);\n return typeof item === 'number' && Number.isFinite(item) ? item : undefined;\n}\n\n/*** Read one optional string record field. */\nfunction optionalStringRecord(\n value: Readonly<Record<string, unknown>> | undefined,\n key: string,\n): Readonly<Record<string, string>> | undefined {\n const item = getValue(value, key);\n if (item === undefined) return undefined;\n if (!isRecord(item)) throw new Error('kubectl returned an invalid string record.');\n const entries: [string, string][] = [];\n for (const [entryKey, entryValue] of Object.entries(item)) {\n if (typeof entryValue !== 'string') {\n throw new Error('kubectl returned an invalid string record.');\n }\n entries.push([entryKey, entryValue]);\n }\n return Object.fromEntries(entries);\n}\n\n/*** Read a string value from a validated string record. */\nfunction getStringValue(value: Readonly<Record<string, string>>, key: string): string | undefined {\n return Object.entries(value)\n .find(([entryKey]) => entryKey === key)\n ?.at(1);\n}\n"]}
@@ -6,6 +6,8 @@ import type { KubernetesProjection } from '../../../../types/kubernetesResources
6
6
  *
7
7
  * Resolved secret values are deliberately absent. The projection carries only references for
8
8
  * runtime materialization immediately before apply.
9
+ *
10
+ * @readme
9
11
  */
10
12
  export declare function projectKubernetesResourcesAsync(request: KubernetesDriverRequest): Promise<InfraResult<KubernetesProjection>>;
11
13
  //# sourceMappingURL=projectKubernetesResourcesAsync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"projectKubernetesResourcesAsync.d.ts","sourceRoot":"","sources":["../../../../../src/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,KAAK,EAEV,oBAAoB,EAErB,MAAM,uCAAuC,CAAC;AAO/C;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,CA8B5C"}
1
+ {"version":3,"file":"projectKubernetesResourcesAsync.d.ts","sourceRoot":"","sources":["../../../../../src/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,KAAK,EAEV,oBAAoB,EAErB,MAAM,uCAAuC,CAAC;AAO/C;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,WAAW,CAAC,oBAAoB,CAAC,CAAC,CA8B5C"}
@@ -8,6 +8,8 @@ import { validateKubernetesWorkloads } from './validateKubernetesWorkloads';
8
8
  *
9
9
  * Resolved secret values are deliberately absent. The projection carries only references for
10
10
  * runtime materialization immediately before apply.
11
+ *
12
+ * @readme
11
13
  */
12
14
  export function projectKubernetesResourcesAsync(request) {
13
15
  const validation = validateKubernetesWorkloads(request.workloads);
@@ -1 +1 @@
1
- {"version":3,"file":"projectKubernetesResourcesAsync.js","sourceRoot":"","sources":["../../../../../src/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,+BAA+B,EAAE,MAAM,6CAA6C,CAAC;AAC9F,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,2BAA2B,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,UAAU,+BAA+B,CAC7C,OAAgC;IAEhC,MAAM,UAAU,GAAG,2BAA2B,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClE,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;IAE1F,MAAM,EAAE,SAAS,EAAE,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IAC3D,MAAM,iBAAiB,GAAG,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC9D,MAAM,cAAc,GAAG,IAAI,GAAG,CAC5B,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;QAClC,QAAQ,CAAC,EAAE;QACX,wBAAwB,CAAC,OAAO,EAAE,YAAY,QAAQ,CAAC,EAAE,EAAE,CAAC;KAC7D,CAAC,CACH,CAAC;IACF,MAAM,SAAS,GAAgC,CAAC,iBAAiB,CAAC,CAAC;IACnE,MAAM,cAAc,GAA8B,EAAE,CAAC;IACrD,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,yBAAyB,CAAC,OAAO,EAAE;YACnD,SAAS;YACT,cAAc,EAAE,iBAAiB,CAAC,KAAK,CAAC,QAAQ;YAChD,cAAc;YACd,QAAQ;SACT,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,EAAE;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACrD,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7C,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,OAAO,CAAC,OAAO,CAAC;QACrB,EAAE,EAAE,IAAI;QACR,KAAK,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE;QACpC,WAAW,EAAE,EAAE;KAChB,CAAC,CAAC;AACL,CAAC;AAED,yEAAyE;AACzE,SAAS,eAAe,CACtB,OAAgC,EAChC,SAAiB;IAEjB,OAAO,+BAA+B,CAAC,OAAO,EAAE;QAC9C,UAAU,EAAE,aAAa,SAAS,EAAE;QACpC,QAAQ,EAAE;YACR,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,WAAW;YACjB,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;SAC3D;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { InfraResult } from '@ankhorage/contracts/infra';\n\nimport type { KubernetesDriverRequest } from '../../../../types/kubernetesDriver';\nimport type {\n KubernetesDesiredResource,\n KubernetesProjection,\n KubernetesSecretBinding,\n} from '../../../../types/kubernetesResources';\nimport { createKubernetesDesiredResource } from '../../utils/createKubernetesDesiredResource';\nimport { createKubernetesIdentity } from '../../utils/createKubernetesIdentity';\nimport { getKubernetesOwnershipQuery } from '../../utils/getKubernetesOwnershipQuery';\nimport { projectKubernetesWorkload } from './projectKubernetesWorkload';\nimport { validateKubernetesWorkloads } from './validateKubernetesWorkloads';\n\n/***\n * Project portable workload desired state into deterministic standard Kubernetes resources.\n *\n * Resolved secret values are deliberately absent. The projection carries only references for\n * runtime materialization immediately before apply.\n */\nexport function projectKubernetesResourcesAsync(\n request: KubernetesDriverRequest,\n): Promise<InfraResult<KubernetesProjection>> {\n const validation = validateKubernetesWorkloads(request.workloads);\n if (validation.length > 0) return Promise.resolve({ ok: false, diagnostics: validation });\n\n const { namespace } = getKubernetesOwnershipQuery(request);\n const namespaceResource = createNamespace(request, namespace);\n const workloadOwners = new Map(\n request.workloads.map((workload) => [\n workload.id,\n createKubernetesIdentity(request, `workload:${workload.id}`),\n ]),\n );\n const resources: KubernetesDesiredResource[] = [namespaceResource];\n const secretBindings: KubernetesSecretBinding[] = [];\n for (const workload of request.workloads) {\n const projected = projectKubernetesWorkload(request, {\n namespace,\n namespaceOwner: namespaceResource.owner.identity,\n workloadOwners,\n workload,\n });\n if (!projected.ok) return Promise.resolve(projected);\n resources.push(...projected.value.resources);\n secretBindings.push(...projected.value.secretBindings);\n }\n return Promise.resolve({\n ok: true,\n value: { resources, secretBindings },\n diagnostics: [],\n });\n}\n\n/*** Create the environment namespace before all namespaced resources. */\nfunction createNamespace(\n request: KubernetesDriverRequest,\n namespace: string,\n): KubernetesDesiredResource {\n return createKubernetesDesiredResource(request, {\n resourceId: `namespace:${namespace}`,\n resource: {\n apiVersion: 'v1',\n kind: 'Namespace',\n metadata: { name: namespace, labels: {}, annotations: {} },\n },\n });\n}\n"]}
1
+ {"version":3,"file":"projectKubernetesResourcesAsync.js","sourceRoot":"","sources":["../../../../../src/features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,+BAA+B,EAAE,MAAM,6CAA6C,CAAC;AAC9F,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAChF,OAAO,EAAE,2BAA2B,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAE5E;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAC7C,OAAgC;IAEhC,MAAM,UAAU,GAAG,2BAA2B,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAClE,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;IAE1F,MAAM,EAAE,SAAS,EAAE,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IAC3D,MAAM,iBAAiB,GAAG,eAAe,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC9D,MAAM,cAAc,GAAG,IAAI,GAAG,CAC5B,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC;QAClC,QAAQ,CAAC,EAAE;QACX,wBAAwB,CAAC,OAAO,EAAE,YAAY,QAAQ,CAAC,EAAE,EAAE,CAAC;KAC7D,CAAC,CACH,CAAC;IACF,MAAM,SAAS,GAAgC,CAAC,iBAAiB,CAAC,CAAC;IACnE,MAAM,cAAc,GAA8B,EAAE,CAAC;IACrD,KAAK,MAAM,QAAQ,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,yBAAyB,CAAC,OAAO,EAAE;YACnD,SAAS;YACT,cAAc,EAAE,iBAAiB,CAAC,KAAK,CAAC,QAAQ;YAChD,cAAc;YACd,QAAQ;SACT,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,EAAE;YAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACrD,SAAS,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7C,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,OAAO,CAAC,OAAO,CAAC;QACrB,EAAE,EAAE,IAAI;QACR,KAAK,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE;QACpC,WAAW,EAAE,EAAE;KAChB,CAAC,CAAC;AACL,CAAC;AAED,yEAAyE;AACzE,SAAS,eAAe,CACtB,OAAgC,EAChC,SAAiB;IAEjB,OAAO,+BAA+B,CAAC,OAAO,EAAE;QAC9C,UAAU,EAAE,aAAa,SAAS,EAAE;QACpC,QAAQ,EAAE;YACR,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,WAAW;YACjB,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;SAC3D;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { InfraResult } from '@ankhorage/contracts/infra';\n\nimport type { KubernetesDriverRequest } from '../../../../types/kubernetesDriver';\nimport type {\n KubernetesDesiredResource,\n KubernetesProjection,\n KubernetesSecretBinding,\n} from '../../../../types/kubernetesResources';\nimport { createKubernetesDesiredResource } from '../../utils/createKubernetesDesiredResource';\nimport { createKubernetesIdentity } from '../../utils/createKubernetesIdentity';\nimport { getKubernetesOwnershipQuery } from '../../utils/getKubernetesOwnershipQuery';\nimport { projectKubernetesWorkload } from './projectKubernetesWorkload';\nimport { validateKubernetesWorkloads } from './validateKubernetesWorkloads';\n\n/***\n * Project portable workload desired state into deterministic standard Kubernetes resources.\n *\n * Resolved secret values are deliberately absent. The projection carries only references for\n * runtime materialization immediately before apply.\n *\n * @readme\n */\nexport function projectKubernetesResourcesAsync(\n request: KubernetesDriverRequest,\n): Promise<InfraResult<KubernetesProjection>> {\n const validation = validateKubernetesWorkloads(request.workloads);\n if (validation.length > 0) return Promise.resolve({ ok: false, diagnostics: validation });\n\n const { namespace } = getKubernetesOwnershipQuery(request);\n const namespaceResource = createNamespace(request, namespace);\n const workloadOwners = new Map(\n request.workloads.map((workload) => [\n workload.id,\n createKubernetesIdentity(request, `workload:${workload.id}`),\n ]),\n );\n const resources: KubernetesDesiredResource[] = [namespaceResource];\n const secretBindings: KubernetesSecretBinding[] = [];\n for (const workload of request.workloads) {\n const projected = projectKubernetesWorkload(request, {\n namespace,\n namespaceOwner: namespaceResource.owner.identity,\n workloadOwners,\n workload,\n });\n if (!projected.ok) return Promise.resolve(projected);\n resources.push(...projected.value.resources);\n secretBindings.push(...projected.value.secretBindings);\n }\n return Promise.resolve({\n ok: true,\n value: { resources, secretBindings },\n diagnostics: [],\n });\n}\n\n/*** Create the environment namespace before all namespaced resources. */\nfunction createNamespace(\n request: KubernetesDriverRequest,\n namespace: string,\n): KubernetesDesiredResource {\n return createKubernetesDesiredResource(request, {\n resourceId: `namespace:${namespace}`,\n resource: {\n apiVersion: 'v1',\n kind: 'Namespace',\n metadata: { name: namespace, labels: {}, annotations: {} },\n },\n });\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  /** Public shared Kubernetes workload-driver boundary. */
2
+ export { createKubectlKubernetesApi } from './features/workload-reconciliation/adapters/createKubectlKubernetesApi';
3
+ export { projectKubernetesResourcesAsync } from './features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync';
2
4
  export { createKubernetesDriver } from './features/workload-reconciliation/composition/createKubernetesDriver';
5
+ export type { KubectlKubernetesApi, KubectlKubernetesApiOptions, KubernetesCommandRequest, KubernetesCommandResult, KubernetesCommandRunner, } from './types/kubectl';
3
6
  export type { KubernetesDriver, KubernetesDriverOptions, KubernetesDriverRequest, } from './types/kubernetesDriver';
4
7
  export type { KubernetesApi, KubernetesDesiredResource, KubernetesOwnershipQuery, KubernetesProjection, KubernetesResource, KubernetesResourceMetadata, KubernetesResourceObservation, KubernetesResourceReference, KubernetesSecretBinding, } from './types/kubernetesResources';
5
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uEAAuE,CAAC;AAC/G,YAAY,EACV,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,aAAa,EACb,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,0BAA0B,EAC1B,6BAA6B,EAC7B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,wEAAwE,CAAC;AACpH,OAAO,EAAE,+BAA+B,EAAE,MAAM,0FAA0F,CAAC;AAC3I,OAAO,EAAE,sBAAsB,EAAE,MAAM,uEAAuE,CAAC;AAC/G,YAAY,EACV,oBAAoB,EACpB,2BAA2B,EAC3B,wBAAwB,EACxB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,aAAa,EACb,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,0BAA0B,EAC1B,6BAA6B,EAC7B,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,6BAA6B,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  /** Public shared Kubernetes workload-driver boundary. */
2
+ export { createKubectlKubernetesApi } from './features/workload-reconciliation/adapters/createKubectlKubernetesApi';
3
+ export { projectKubernetesResourcesAsync } from './features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync';
2
4
  export { createKubernetesDriver } from './features/workload-reconciliation/composition/createKubernetesDriver';
3
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uEAAuE,CAAC","sourcesContent":["/** Public shared Kubernetes workload-driver boundary. */\nexport { createKubernetesDriver } from './features/workload-reconciliation/composition/createKubernetesDriver';\nexport type {\n KubernetesDriver,\n KubernetesDriverOptions,\n KubernetesDriverRequest,\n} from './types/kubernetesDriver';\nexport type {\n KubernetesApi,\n KubernetesDesiredResource,\n KubernetesOwnershipQuery,\n KubernetesProjection,\n KubernetesResource,\n KubernetesResourceMetadata,\n KubernetesResourceObservation,\n KubernetesResourceReference,\n KubernetesSecretBinding,\n} from './types/kubernetesResources';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,OAAO,EAAE,0BAA0B,EAAE,MAAM,wEAAwE,CAAC;AACpH,OAAO,EAAE,+BAA+B,EAAE,MAAM,0FAA0F,CAAC;AAC3I,OAAO,EAAE,sBAAsB,EAAE,MAAM,uEAAuE,CAAC","sourcesContent":["/** Public shared Kubernetes workload-driver boundary. */\nexport { createKubectlKubernetesApi } from './features/workload-reconciliation/adapters/createKubectlKubernetesApi';\nexport { projectKubernetesResourcesAsync } from './features/workload-reconciliation/application/use-cases/projectKubernetesResourcesAsync';\nexport { createKubernetesDriver } from './features/workload-reconciliation/composition/createKubernetesDriver';\nexport type {\n KubectlKubernetesApi,\n KubectlKubernetesApiOptions,\n KubernetesCommandRequest,\n KubernetesCommandResult,\n KubernetesCommandRunner,\n} from './types/kubectl';\nexport type {\n KubernetesDriver,\n KubernetesDriverOptions,\n KubernetesDriverRequest,\n} from './types/kubernetesDriver';\nexport type {\n KubernetesApi,\n KubernetesDesiredResource,\n KubernetesOwnershipQuery,\n KubernetesProjection,\n KubernetesResource,\n KubernetesResourceMetadata,\n KubernetesResourceObservation,\n KubernetesResourceReference,\n KubernetesSecretBinding,\n} from './types/kubernetesResources';\n"]}
@@ -0,0 +1,25 @@
1
+ import type { KubernetesApi } from './kubernetesResources';
2
+ export interface KubernetesCommandRequest {
3
+ readonly executable: string;
4
+ readonly arguments: readonly string[];
5
+ readonly stdin?: string;
6
+ readonly signal?: AbortSignal;
7
+ }
8
+ export interface KubernetesCommandResult {
9
+ readonly exitCode: number;
10
+ readonly stdout: string;
11
+ readonly stderr: string;
12
+ }
13
+ /** Process boundary used by the concrete kubectl adapter without invoking a shell. */
14
+ export interface KubernetesCommandRunner {
15
+ runAsync(request: KubernetesCommandRequest): Promise<KubernetesCommandResult>;
16
+ }
17
+ export interface KubectlKubernetesApiOptions {
18
+ /** Exact authenticated kubeconfig context selected by the owning runtime adapter. */
19
+ readonly context: string;
20
+ readonly executable?: string;
21
+ readonly runner?: KubernetesCommandRunner;
22
+ readonly pollIntervalMs?: number;
23
+ }
24
+ export type KubectlKubernetesApi = KubernetesApi;
25
+ //# sourceMappingURL=kubectl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../src/types/kubectl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,sFAAsF;AACtF,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,2BAA2B;IAC1C,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,MAAM,oBAAoB,GAAG,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=kubectl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kubectl.js","sourceRoot":"","sources":["../../src/types/kubectl.ts"],"names":[],"mappings":"","sourcesContent":["import type { KubernetesApi } from './kubernetesResources';\n\nexport interface KubernetesCommandRequest {\n readonly executable: string;\n readonly arguments: readonly string[];\n readonly stdin?: string;\n readonly signal?: AbortSignal;\n}\n\nexport interface KubernetesCommandResult {\n readonly exitCode: number;\n readonly stdout: string;\n readonly stderr: string;\n}\n\n/** Process boundary used by the concrete kubectl adapter without invoking a shell. */\nexport interface KubernetesCommandRunner {\n runAsync(request: KubernetesCommandRequest): Promise<KubernetesCommandResult>;\n}\n\nexport interface KubectlKubernetesApiOptions {\n /** Exact authenticated kubeconfig context selected by the owning runtime adapter. */\n readonly context: string;\n readonly executable?: string;\n readonly runner?: KubernetesCommandRunner;\n readonly pollIntervalMs?: number;\n}\n\nexport type KubectlKubernetesApi = KubernetesApi;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/kubernetes",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Shared Kubernetes workload driver for provider-neutral Ankhorage infrastructure.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,7 +55,7 @@
55
55
  "version-packages": "ankhorage-changeset version"
56
56
  },
57
57
  "dependencies": {
58
- "@ankhorage/contracts": "^15.0.0"
58
+ "@ankhorage/contracts": "^16.0.0"
59
59
  },
60
60
  "devDependencies": {
61
61
  "@ankhorage/devtools": "^1.14.6",