@alchemy.run/kubernetes 0.0.0 → 2.0.0-beta.85

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 (94) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +41 -4
  3. package/lib/BuiltinAdapters.d.ts +43 -0
  4. package/lib/BuiltinAdapters.d.ts.map +1 -0
  5. package/lib/BuiltinAdapters.js +129 -0
  6. package/lib/BuiltinAdapters.js.map +1 -0
  7. package/lib/Client.d.ts +97 -0
  8. package/lib/Client.d.ts.map +1 -0
  9. package/lib/Client.js +312 -0
  10. package/lib/Client.js.map +1 -0
  11. package/lib/ClusterAdapter.d.ts +324 -0
  12. package/lib/ClusterAdapter.d.ts.map +1 -0
  13. package/lib/ClusterAdapter.js +65 -0
  14. package/lib/ClusterAdapter.js.map +1 -0
  15. package/lib/Connection.d.ts +210 -0
  16. package/lib/Connection.d.ts.map +1 -0
  17. package/lib/Connection.js +58 -0
  18. package/lib/Connection.js.map +1 -0
  19. package/lib/Deployment.d.ts +401 -0
  20. package/lib/Deployment.d.ts.map +1 -0
  21. package/lib/Deployment.js +550 -0
  22. package/lib/Deployment.js.map +1 -0
  23. package/lib/HelmChart.d.ts +147 -0
  24. package/lib/HelmChart.d.ts.map +1 -0
  25. package/lib/HelmChart.js +248 -0
  26. package/lib/HelmChart.js.map +1 -0
  27. package/lib/Job.d.ts +333 -0
  28. package/lib/Job.d.ts.map +1 -0
  29. package/lib/Job.js +443 -0
  30. package/lib/Job.js.map +1 -0
  31. package/lib/LocalCluster.d.ts +243 -0
  32. package/lib/LocalCluster.d.ts.map +1 -0
  33. package/lib/LocalCluster.js +359 -0
  34. package/lib/LocalCluster.js.map +1 -0
  35. package/lib/Manifest.d.ts +157 -0
  36. package/lib/Manifest.d.ts.map +1 -0
  37. package/lib/Manifest.js +205 -0
  38. package/lib/Manifest.js.map +1 -0
  39. package/lib/Objects.d.ts +60 -0
  40. package/lib/Objects.d.ts.map +1 -0
  41. package/lib/Objects.js +132 -0
  42. package/lib/Objects.js.map +1 -0
  43. package/lib/Providers.d.ts +24 -0
  44. package/lib/Providers.d.ts.map +1 -0
  45. package/lib/Providers.js +36 -0
  46. package/lib/Providers.js.map +1 -0
  47. package/lib/index.d.ts +10 -0
  48. package/lib/index.d.ts.map +1 -0
  49. package/lib/index.js +10 -0
  50. package/lib/index.js.map +1 -0
  51. package/lib/internal/declared.d.ts +19 -0
  52. package/lib/internal/declared.d.ts.map +1 -0
  53. package/lib/internal/declared.js +435 -0
  54. package/lib/internal/declared.js.map +1 -0
  55. package/lib/internal/helm.d.ts +65 -0
  56. package/lib/internal/helm.d.ts.map +1 -0
  57. package/lib/internal/helm.js +173 -0
  58. package/lib/internal/helm.js.map +1 -0
  59. package/lib/internal/kind.d.ts +17 -0
  60. package/lib/internal/kind.d.ts.map +1 -0
  61. package/lib/internal/kind.js +31 -0
  62. package/lib/internal/kind.js.map +1 -0
  63. package/lib/internal/kubeconfig.d.ts +79 -0
  64. package/lib/internal/kubeconfig.d.ts.map +1 -0
  65. package/lib/internal/kubeconfig.js +222 -0
  66. package/lib/internal/kubeconfig.js.map +1 -0
  67. package/lib/internal/registry.d.ts +44 -0
  68. package/lib/internal/registry.d.ts.map +1 -0
  69. package/lib/internal/registry.js +72 -0
  70. package/lib/internal/registry.js.map +1 -0
  71. package/lib/internal/workload.d.ts +127 -0
  72. package/lib/internal/workload.d.ts.map +1 -0
  73. package/lib/internal/workload.js +411 -0
  74. package/lib/internal/workload.js.map +1 -0
  75. package/package.json +55 -3
  76. package/src/BuiltinAdapters.ts +202 -0
  77. package/src/Client.ts +496 -0
  78. package/src/ClusterAdapter.ts +373 -0
  79. package/src/Connection.ts +234 -0
  80. package/src/Deployment.ts +872 -0
  81. package/src/HelmChart.ts +388 -0
  82. package/src/Job.ts +709 -0
  83. package/src/LocalCluster.ts +631 -0
  84. package/src/Manifest.ts +301 -0
  85. package/src/Objects.ts +213 -0
  86. package/src/Providers.ts +52 -0
  87. package/src/globals.d.ts +2 -0
  88. package/src/index.ts +9 -0
  89. package/src/internal/declared.ts +471 -0
  90. package/src/internal/helm.ts +245 -0
  91. package/src/internal/kind.ts +43 -0
  92. package/src/internal/kubeconfig.ts +364 -0
  93. package/src/internal/registry.ts +108 -0
  94. package/src/internal/workload.ts +504 -0
@@ -0,0 +1,373 @@
1
+ import type { InlineDockerfile } from "@alchemy.run/docker/Dockerfile";
2
+ import type { DockerfileBuild } from "@alchemy.run/docker/ImageSource";
3
+ import type * as Bundle from "alchemy/Bundle/Bundle";
4
+ import type { ResourceBinding } from "alchemy/Resource";
5
+ import type { ResourceContext } from "alchemy/ResourceContext";
6
+ import type { Stack } from "alchemy/Stack";
7
+ import type { Stage } from "alchemy/Stage";
8
+ /**
9
+ * The extension seam between the cluster-agnostic `Kubernetes.*` workloads
10
+ * and the platform a cluster runs on.
11
+ *
12
+ * A {@link ClusterAdapterService} is registered under a keyed Context tag —
13
+ * `Kubernetes.ClusterAdapter/<auth kind>` — and resolved dynamically from
14
+ * the ambient provider context by {@link findClusterAdapter}, mirroring how
15
+ * resource providers are resolved by type. `Kubernetes.providers()` ships
16
+ * the built-in adapters (`kubeconfig`, `token`, `client-cert`, `exec`);
17
+ * cloud provider layers contribute theirs (`AWS.providers()` registers
18
+ * `aws-eks`).
19
+ *
20
+ * An adapter owns everything platform-specific:
21
+ *
22
+ * - **connect** (required) — resolve the API server endpoint/CA and mint
23
+ * per-request auth headers.
24
+ * - **identity** (optional) — provision workload identity for a namespace +
25
+ * service account and translate host bindings into cloud credentials
26
+ * (EKS Pod Identity, GKE Workload Identity Federation).
27
+ * - **registry** (optional) — build/mirror container images into a managed
28
+ * registry the cluster can pull from (ECR on EKS, Artifact Registry on GKE).
29
+ * - **bootstrap** (optional) — platform-specific generated container
30
+ * entries for Effect-native workloads (e.g. wiring the AWS credential
31
+ * chain for Pod Identity).
32
+ * - **loadBalancerDefaults** (optional) — platform defaults for
33
+ * `LoadBalancer` Services (EKS Auto Mode's `loadBalancerClass` +
34
+ * internet-facing scheme).
35
+ */
36
+ import * as Context from "effect/Context";
37
+ import * as Data from "effect/Data";
38
+ import * as Effect from "effect/Effect";
39
+ import * as Option from "effect/Option";
40
+ import type { Connection } from "./Connection.ts";
41
+
42
+ /**
43
+ * Per-resource engine services ambient inside every provider lifecycle
44
+ * operation. Adapter methods run inside the workload providers' lifecycle
45
+ * ops, so they may require these (physical-name generation needs the
46
+ * resource's ResourceContext + Stack + Stage) — everything else an adapter
47
+ * needs is captured at its layer build.
48
+ */
49
+ export type AdapterLifecycleServices = ResourceContext | Stack | Stage;
50
+
51
+ /**
52
+ * The target cluster no longer exists (definitively — e.g. the managed
53
+ * control plane is deleted or deleting). Distinct from transient
54
+ * unreachability: `read`/`delete` treat this as "everything in-cluster is
55
+ * already gone".
56
+ */
57
+ export class ClusterNotFoundError extends Data.TaggedError("Kubernetes.ClusterNotFoundError")<{
58
+ message: string;
59
+ }> {}
60
+
61
+ /**
62
+ * A resolved transport to a cluster's API server. `headers` is an Effect
63
+ * so short-lived tokens (EKS SigV4 presigns, exec plugin credentials) are
64
+ * minted per request.
65
+ */
66
+ export interface ClusterTransport {
67
+ /** The API server endpoint URL. */
68
+ endpoint: string;
69
+ /** Base64-encoded PEM certificate authority bundle. */
70
+ certificateAuthorityData?: string;
71
+ /** Skip TLS verification (self-signed local clusters). */
72
+ insecureSkipTlsVerify?: boolean;
73
+ /** Mint the auth headers for one request. */
74
+ headers: Effect.Effect<Record<string, string>, Error>;
75
+ /** PEM client certificate credentials for mutual TLS. */
76
+ clientCert?: { certificate: string; key: string };
77
+ }
78
+
79
+ /**
80
+ * Workload identity state persisted on a workload's attributes, keyed by
81
+ * adapter kind. Cloud providers extend it via module augmentation — AWS
82
+ * registers:
83
+ *
84
+ * ```ts
85
+ * declare module "@alchemy.run/kubernetes/ClusterAdapter" {
86
+ * interface IdentityStateRegistry {
87
+ * "aws-pod-identity": {
88
+ * roleArn: string;
89
+ * roleName: string;
90
+ * associationArn: string;
91
+ * associationId: string;
92
+ * };
93
+ * }
94
+ * }
95
+ * ```
96
+ */
97
+ export interface IdentityStateRegistry {}
98
+
99
+ /** The discriminated identity-state union across all registered adapters. */
100
+ export type IdentityState = {
101
+ [K in keyof IdentityStateRegistry]: {
102
+ readonly kind: K;
103
+ } & IdentityStateRegistry[K];
104
+ }[keyof IdentityStateRegistry];
105
+
106
+ /**
107
+ * Image-registry state persisted on a workload's attributes, keyed by
108
+ * adapter kind (AWS registers `"aws-ecr"` with the repository name/URI).
109
+ * The built-in `"registry"` entry records images pushed to a connection's
110
+ * {@link Connection.registry}.
111
+ */
112
+ export interface RegistryStateRegistry {
113
+ registry: {
114
+ /** The registry `server` the image was pushed to. */
115
+ server: string;
116
+ /** The repository the image was pushed to (`<server>/<name>`). */
117
+ repository: string;
118
+ };
119
+ }
120
+
121
+ /** The discriminated registry-state union across all registered adapters. */
122
+ export type RegistryState = {
123
+ [K in keyof RegistryStateRegistry]: {
124
+ readonly kind: K;
125
+ } & RegistryStateRegistry[K];
126
+ }[keyof RegistryStateRegistry];
127
+
128
+ /**
129
+ * Cloud-specific workload identity options, extended via module
130
+ * augmentation (AWS adds `managedPolicyArns`, GCP `gcpServiceAccount`).
131
+ */
132
+ export interface WorkloadIdentityOptions {}
133
+
134
+ /**
135
+ * The binding contract of `Kubernetes.Deployment` / `Kubernetes.Job`
136
+ * hosts. The core contract is environment variables; cloud providers
137
+ * augment it with their credential-grant channels (AWS adds
138
+ * `policyStatements`, GCP adds `iam`), which the matching {@link ClusterAdapterService}'s
139
+ * identity adapter materializes at deploy time.
140
+ */
141
+ export interface WorkloadBindingContract {
142
+ env?: Record<string, any>;
143
+ }
144
+
145
+ /**
146
+ * Ambient runtime services the workload platforms assume inside the
147
+ * deployed container, keyed by contributor and extended via module
148
+ * augmentation — AWS registers its credential-chain services so inline
149
+ * impls can use AWS bindings without providing them:
150
+ *
151
+ * ```ts
152
+ * declare module "@alchemy.run/kubernetes/ClusterAdapter" {
153
+ * interface WorkloadServicesRegistry {
154
+ * aws: Credentials | Region | AWSEnvironment;
155
+ * }
156
+ * }
157
+ * ```
158
+ */
159
+ export interface WorkloadServicesRegistry {}
160
+
161
+ /** The union of all registered ambient workload services. */
162
+ export type WorkloadServices = WorkloadServicesRegistry[keyof WorkloadServicesRegistry];
163
+
164
+ /**
165
+ * The image-source shape shared by every workload: exactly one of `main`
166
+ * (bundle an inline Effect program), `context`/`dockerfile` (build the
167
+ * user's Dockerfile), or `image` (a pre-built registry reference).
168
+ * Structurally mirrors the AWS container platforms' source props.
169
+ */
170
+ export interface WorkloadImageSource {
171
+ main?: string;
172
+ handler?: string;
173
+ /**
174
+ * Bundler configuration for `main`, or Docker build args and secrets for
175
+ * a `context`/`dockerfile` build. Unused bundled code is tree-shaken.
176
+ * `effect`, alchemy, and `@distilled.cloud` are marked pure so unused
177
+ * parts prune more aggressively. List extra packages with
178
+ * `pure.packages`, or disable with `pure: false`.
179
+ */
180
+ build?: Bundle.BundleConfig & DockerfileBuild;
181
+ context?: string;
182
+ dockerfile?: string | InlineDockerfile;
183
+ image?: string;
184
+ }
185
+
186
+ export interface WorkloadIdentityReconcileOptions {
187
+ /** Logical resource id. */
188
+ id: string;
189
+ /** The resolved cluster connection. */
190
+ connection: Connection;
191
+ /** The namespace the workload runs in. */
192
+ namespace: string;
193
+ /** The service account name the workload's pods run as. */
194
+ serviceAccount: string;
195
+ /** Host bindings attached to the workload. */
196
+ bindings: ResourceBinding<WorkloadBindingContract>[];
197
+ /** Cloud-specific identity options from the workload's `identity` prop. */
198
+ options: WorkloadIdentityOptions | undefined;
199
+ /**
200
+ * Previously persisted identity state, if any — untyped hints because
201
+ * legacy pre-rename rows carry the old flat attribute shape; adapters
202
+ * narrow structurally and re-observe cloud state as the authority.
203
+ */
204
+ state: Record<string, unknown> | undefined;
205
+ /** Internal + user tags for identity-owned cloud resources. */
206
+ tags: Record<string, string>;
207
+ }
208
+
209
+ export interface WorkloadIdentityResult {
210
+ /** Extra container environment collected from bindings. */
211
+ env: Record<string, any>;
212
+ /** Annotations to stamp on the workload's ServiceAccount object. */
213
+ serviceAccountAnnotations?: Record<string, string>;
214
+ /** Serializable state persisted on the workload's attributes. */
215
+ // oxlint-disable-next-line typescript/no-redundant-type-constituents -- registry type is `never` until cloud packages augment it
216
+ state: IdentityState | undefined;
217
+ }
218
+
219
+ export interface WorkloadIdentityDeleteOptions {
220
+ /** The persisted cluster connection (may reference a gone cluster). */
221
+ connection: Connection | undefined;
222
+ /**
223
+ * The persisted identity state. Untyped because legacy pre-rename rows
224
+ * carry the old flat attribute shape — adapters narrow structurally.
225
+ */
226
+ state: Record<string, unknown> | undefined;
227
+ }
228
+
229
+ export interface ImageRegistryResolveOptions {
230
+ /** Logical resource id — keys generated repository names. */
231
+ id: string;
232
+ /** The image-source props bag. */
233
+ source: WorkloadImageSource;
234
+ /** Target image platform (`linux/amd64` / `linux/arm64`). */
235
+ platform: string;
236
+ /** Port the generated Dockerfile exposes (`main` sources only). */
237
+ port?: number;
238
+ /** True when `main` bundles as-is (no Effect bootstrap entry). */
239
+ isExternal?: boolean;
240
+ /** The generated-entry bootstrap wrapped around `main`. */
241
+ bootstrap: (importPath: string) => string;
242
+ /** Tags for registry-owned cloud resources. */
243
+ tags: Record<string, string>;
244
+ /**
245
+ * Previously persisted registry state, if any — untyped hints because
246
+ * legacy pre-rename rows carry the old flat attribute shape; adapters
247
+ * narrow structurally.
248
+ */
249
+ state: Record<string, unknown> | undefined;
250
+ /** Plan-status session for build/push progress notes. */
251
+ session: { note: (message: string) => Effect.Effect<void> };
252
+ }
253
+
254
+ export interface ImageRegistryResult {
255
+ /** Full image reference the pod spec should run. */
256
+ imageUri: string;
257
+ /** Content hash identifying the image. */
258
+ codeHash: string;
259
+ /** Serializable state persisted on the workload's attributes. */
260
+ state: RegistryState | undefined;
261
+ }
262
+
263
+ export interface ImageRegistryHashOptions {
264
+ source: WorkloadImageSource;
265
+ platform: string;
266
+ port?: number;
267
+ isExternal?: boolean;
268
+ bootstrap: (importPath: string) => string;
269
+ }
270
+
271
+ export interface ImageRegistryDeleteOptions {
272
+ /**
273
+ * The persisted registry state. Untyped because legacy pre-rename rows
274
+ * carry the old flat attribute shape — adapters narrow structurally.
275
+ */
276
+ state: Record<string, unknown> | undefined;
277
+ }
278
+
279
+ export interface ClusterAdapterService {
280
+ readonly kind: "Kubernetes.ClusterAdapter";
281
+ /**
282
+ * Resolve the transport for a connection: discover the endpoint/CA when
283
+ * the connection doesn't carry them, and return a per-request auth
284
+ * header mint. Fails with {@link ClusterNotFoundError} when the cluster
285
+ * definitively no longer exists.
286
+ */
287
+ readonly connect: (
288
+ connection: Connection,
289
+ ) => Effect.Effect<ClusterTransport, ClusterNotFoundError | Error>;
290
+ /** Workload identity provisioning (Pod Identity on EKS, Workload Identity Federation on GKE). */
291
+ readonly identity?: {
292
+ readonly reconcile: (
293
+ options: WorkloadIdentityReconcileOptions,
294
+ ) => Effect.Effect<WorkloadIdentityResult, any, AdapterLifecycleServices>;
295
+ readonly delete: (
296
+ options: WorkloadIdentityDeleteOptions,
297
+ ) => Effect.Effect<void, any, AdapterLifecycleServices>;
298
+ };
299
+ /** Managed container-image registry (ECR on EKS, Artifact Registry on GKE). */
300
+ readonly registry?: {
301
+ readonly resolve: (
302
+ options: ImageRegistryResolveOptions,
303
+ ) => Effect.Effect<ImageRegistryResult, any, AdapterLifecycleServices>;
304
+ /** Plan-time content hash used by `diff` to surface source drift. */
305
+ readonly hash: (
306
+ options: ImageRegistryHashOptions,
307
+ ) => Effect.Effect<string | undefined, any, AdapterLifecycleServices>;
308
+ readonly delete: (
309
+ options: ImageRegistryDeleteOptions,
310
+ ) => Effect.Effect<void, any, AdapterLifecycleServices>;
311
+ };
312
+ /**
313
+ * Platform-specific generated container entries for Effect-native
314
+ * workloads. Defaults to the platform-neutral bootstraps when omitted.
315
+ */
316
+ readonly bootstrap?: {
317
+ readonly server?: (handler: string) => (importPath: string) => string;
318
+ readonly job?: (handler: string) => (importPath: string) => string;
319
+ };
320
+ /**
321
+ * Platform defaults applied to a `LoadBalancer` Service (EKS Auto Mode
322
+ * sets `loadBalancerClass: eks.amazonaws.com/nlb` and defaults the
323
+ * scheme to internet-facing). User `serviceAnnotations` always win.
324
+ */
325
+ readonly loadBalancerDefaults?: (options: { connection: Connection }) => Effect.Effect<
326
+ {
327
+ loadBalancerClass?: string | undefined;
328
+ annotations?: Record<string, string>;
329
+ },
330
+ any,
331
+ AdapterLifecycleServices
332
+ >;
333
+ }
334
+
335
+ const adapterKey = (authKind: string) => `Kubernetes.ClusterAdapter/${authKind}`;
336
+
337
+ /**
338
+ * The keyed Context tag for an adapter. Same auth kind → same tag, so a
339
+ * layer built with `ClusterAdapter("aws-eks")` is found by any dynamic
340
+ * lookup for that kind.
341
+ */
342
+ export const ClusterAdapter = (
343
+ authKind: string,
344
+ ): Context.Service<ClusterAdapterService, ClusterAdapterService> =>
345
+ Context.Service<ClusterAdapterService, ClusterAdapterService>()(adapterKey(authKind)) as any;
346
+
347
+ /**
348
+ * Resolve the {@link ClusterAdapterService} for a connection's auth kind
349
+ * from the ambient context (the stack's composed provider layers). Dies
350
+ * with setup guidance when no adapter is registered — that means the
351
+ * provider layer contributing it (e.g. `AWS.providers()` for `aws-eks`)
352
+ * is missing from the stack.
353
+ */
354
+ export const findClusterAdapter = (authKind: string): Effect.Effect<ClusterAdapterService> =>
355
+ Effect.serviceOption(ClusterAdapter(authKind)).pipe(
356
+ Effect.flatMap(
357
+ Option.match({
358
+ onSome: (adapter) => Effect.succeed(adapter),
359
+ onNone: () =>
360
+ Effect.die(
361
+ new Error(
362
+ `No Kubernetes cluster adapter is registered for auth kind ` +
363
+ `'${authKind}'. Add the provider layer that contributes it ` +
364
+ `to the stack's providers — e.g. 'aws-eks' ships with ` +
365
+ "`AWS.providers()`; the built-in kinds (kubeconfig, token, " +
366
+ "client-cert, exec) ship with `Kubernetes.providers()`. " +
367
+ "Compose multiple provider layers with " +
368
+ "`Layer.mergeAll(AWS.providers(), Kubernetes.providers())`.",
369
+ ),
370
+ ),
371
+ }),
372
+ ),
373
+ );
@@ -0,0 +1,234 @@
1
+ import type * as Redacted from "effect/Redacted";
2
+
3
+ /**
4
+ * The cluster-agnostic Kubernetes connection model.
5
+ *
6
+ * Every `Kubernetes.*` workload (`Deployment`, `Job`, `Manifest`,
7
+ * `HelmChart`) targets a cluster through a serializable {@link Connection}:
8
+ * the API server endpoint (or enough information to discover it) plus an
9
+ * {@link ConnectionAuth} descriptor whose `kind` selects the
10
+ * {@link ClusterAdapter} that knows how to authenticate requests — and,
11
+ * for managed clouds, how to provision workload identity and container
12
+ * images.
13
+ *
14
+ * A `Connection` is plain data on purpose: it is resolved from resource
15
+ * attributes at reconcile time and persisted on workload attributes so
16
+ * `delete` can reconnect without the original resource graph.
17
+ */
18
+
19
+ /**
20
+ * The auth descriptor registry, keyed by adapter kind. Cloud providers
21
+ * extend it via module augmentation — e.g. AWS registers:
22
+ *
23
+ * ```ts
24
+ * declare module "@alchemy.run/kubernetes/Connection" {
25
+ * interface AuthRegistry {
26
+ * "aws-eks": { clusterName: string; region?: string };
27
+ * }
28
+ * }
29
+ * ```
30
+ *
31
+ * The built-in kinds cover anything `kubectl` can reach: a kubeconfig
32
+ * context (including `exec` credential plugins — `aws eks get-token`,
33
+ * `kubelogin`, `gke-gcloud-auth-plugin`), a static bearer token, a client
34
+ * certificate, or a raw exec credential plugin invocation.
35
+ */
36
+ export interface AuthRegistry {
37
+ /**
38
+ * Resolve the cluster, user, and credentials from a kubeconfig file —
39
+ * the "anything kubectl can reach" adapter. Honors client certificates,
40
+ * static tokens, and `exec` credential plugins declared in the file.
41
+ */
42
+ kubeconfig: {
43
+ /**
44
+ * Path to the kubeconfig file.
45
+ * @default `$KUBECONFIG` or `~/.kube/config`
46
+ */
47
+ path?: string;
48
+ /**
49
+ * The context to use.
50
+ * @default the file's `current-context`
51
+ */
52
+ context?: string;
53
+ };
54
+ /** Authenticate with a static bearer token. */
55
+ token: {
56
+ /** The bearer token sent as `Authorization: Bearer <token>`. */
57
+ token: string;
58
+ };
59
+ /** Authenticate with a client certificate (mutual TLS). */
60
+ "client-cert": {
61
+ /** PEM-encoded client certificate. */
62
+ certificate: string;
63
+ /** PEM-encoded client private key. */
64
+ key: string;
65
+ };
66
+ /**
67
+ * Mint credentials with a kubeconfig-style exec credential plugin
68
+ * (`client.authentication.k8s.io` ExecCredential protocol).
69
+ */
70
+ exec: {
71
+ /** The command to run (e.g. `aws`). */
72
+ command: string;
73
+ /** Arguments (e.g. `["eks", "get-token", "--cluster-name", "x"]`). */
74
+ args?: string[];
75
+ /** Extra environment variables for the plugin process. */
76
+ env?: Record<string, string>;
77
+ };
78
+ }
79
+
80
+ /**
81
+ * The discriminated auth union — one member per {@link AuthRegistry} entry,
82
+ * each tagged with its registry key as `kind`.
83
+ */
84
+ export type ConnectionAuth = {
85
+ [K in keyof AuthRegistry]: { readonly kind: K } & AuthRegistry[K];
86
+ }[keyof AuthRegistry];
87
+
88
+ /**
89
+ * A container registry the cluster's nodes can pull from. `Deployment` and
90
+ * `Job` workloads with a `main` program or a `context` Dockerfile are built
91
+ * on the deploying machine and pushed here as `<server>/<name>:<hash>`.
92
+ *
93
+ * Clusters whose platform adapter provides its own registry (EKS pushes to
94
+ * ECR) ignore this setting.
95
+ */
96
+ export interface ContainerRegistry {
97
+ /**
98
+ * Registry host, optionally followed by the namespace images are pushed
99
+ * under — e.g. `localhost:5001`, `ghcr.io/acme`, or
100
+ * `us-docker.pkg.dev/my-project/apps`.
101
+ */
102
+ server: string;
103
+ /**
104
+ * Username for pushing. Omit `username` and `password` to push with the
105
+ * deploying machine's own Docker login (`docker login`, credential
106
+ * helpers).
107
+ */
108
+ username?: string;
109
+ /** Password or access token for pushing. */
110
+ password?: string | Redacted.Redacted<string>;
111
+ }
112
+
113
+ /**
114
+ * A serializable description of how to reach and authenticate against a
115
+ * Kubernetes API server.
116
+ */
117
+ export interface Connection {
118
+ /**
119
+ * The API server endpoint URL. Optional when the auth adapter can
120
+ * discover it (a kubeconfig file carries the server; the EKS adapter
121
+ * re-describes the cluster).
122
+ */
123
+ endpoint?: string;
124
+ /** Base64-encoded PEM certificate authority bundle for the endpoint. */
125
+ certificateAuthorityData?: string;
126
+ /** Skip TLS verification (self-signed local clusters). */
127
+ insecureSkipTlsVerify?: boolean;
128
+ /** How to authenticate — selects the {@link ClusterAdapter} by `kind`. */
129
+ auth: ConnectionAuth;
130
+ /**
131
+ * Where `main` and `context` workloads push their images. Required for
132
+ * those sources unless the cluster's platform adapter provides a
133
+ * registry (EKS → ECR).
134
+ */
135
+ registry?: ContainerRegistry;
136
+ /**
137
+ * CPU architecture of the cluster's nodes — the default target of
138
+ * workload image builds when a workload doesn't set `architecture`.
139
+ * @default "amd64"
140
+ */
141
+ architecture?: "amd64" | "arm64";
142
+ }
143
+
144
+ /**
145
+ * Anything a `Kubernetes.*` workload accepts as its `cluster` prop: a raw
146
+ * {@link Connection}, or any value exposing one as a `connection` field —
147
+ * which is how managed cluster resources (`AWS.EKS.Cluster`) participate:
148
+ * their attributes carry a `connection`, so the whole resource can be
149
+ * passed directly.
150
+ */
151
+ export type ClusterLike = Connection | { connection: Connection };
152
+
153
+ const isConnection = (value: ClusterLike): value is Connection =>
154
+ "auth" in value && value.auth !== undefined;
155
+
156
+ /**
157
+ * Normalize a {@link ClusterLike} to its {@link Connection}. Throws when
158
+ * the value carries neither shape — e.g. a managed cluster resource whose
159
+ * attributes predate the `connection` field.
160
+ */
161
+ export const toConnection = (cluster: ClusterLike): Connection => {
162
+ if (isConnection(cluster)) return cluster;
163
+ if (cluster.connection !== undefined) return cluster.connection;
164
+ throw new Error(
165
+ "The `cluster` value carries no Kubernetes connection. Pass a cluster " +
166
+ "resource whose attributes expose `connection` (e.g. `AWS.EKS.Cluster`" +
167
+ " — redeploy the cluster if it was created before the `connection` " +
168
+ "attribute existed), a `Kubernetes.KubeConfig(...)`, or a raw " +
169
+ "`Kubernetes.Connection`.",
170
+ );
171
+ };
172
+
173
+ /**
174
+ * Connect `Kubernetes.*` workloads to any cluster your local kubeconfig can
175
+ * reach — k3s, kind, on-prem, AKS, GKE, anything `kubectl` works against.
176
+ * Exec credential plugins declared in the file (`aws eks get-token`,
177
+ * `kubelogin`, `gke-gcloud-auth-plugin`) are honored, so cloud-CLI-managed
178
+ * contexts work as-is.
179
+ *
180
+ * This is a plain helper, not a resource: nothing is provisioned and the
181
+ * cluster appears in no plan — it only describes how to reach the API
182
+ * server.
183
+ *
184
+ * ```ts
185
+ * const cluster = Kubernetes.KubeConfig({ context: "prod-east" });
186
+ *
187
+ * const api = yield* Kubernetes.Deployment("Api", {
188
+ * cluster,
189
+ * image: "ghcr.io/acme/api:v3",
190
+ * port: 8080,
191
+ * });
192
+ * ```
193
+ *
194
+ * Add a `registry` to build `main` programs and `context` Dockerfiles for
195
+ * the cluster:
196
+ *
197
+ * ```ts
198
+ * const cluster = Kubernetes.KubeConfig({
199
+ * context: "prod-east",
200
+ * registry: { server: "ghcr.io/acme" },
201
+ * });
202
+ * ```
203
+ */
204
+ export const KubeConfig = (options?: {
205
+ /**
206
+ * Path to the kubeconfig file.
207
+ * @default `$KUBECONFIG` or `~/.kube/config`
208
+ */
209
+ path?: string;
210
+ /**
211
+ * The context to use.
212
+ * @default the file's `current-context`
213
+ */
214
+ context?: string;
215
+ /**
216
+ * Where `main` and `context` workloads push their images. The cluster's
217
+ * nodes must be able to pull from it.
218
+ */
219
+ registry?: ContainerRegistry;
220
+ /**
221
+ * CPU architecture of the cluster's nodes, used as the default image
222
+ * build target.
223
+ * @default "amd64"
224
+ */
225
+ architecture?: "amd64" | "arm64";
226
+ }): Connection => ({
227
+ auth: {
228
+ kind: "kubeconfig",
229
+ path: options?.path,
230
+ context: options?.context,
231
+ },
232
+ ...(options?.registry !== undefined ? { registry: options.registry } : {}),
233
+ ...(options?.architecture !== undefined ? { architecture: options.architecture } : {}),
234
+ });