@konfig.ts/k8s 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/dist/.generated/k8s-types/index.d.ts +6 -0
- package/dist/backend.d.ts +26 -0
- package/dist/container.d.ts +190 -0
- package/dist/env.d.ts +110 -0
- package/dist/environmentBind.d.ts +133 -0
- package/dist/identity.d.ts +58 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.mjs +1008 -0
- package/dist/index.mjs.map +1 -0
- package/dist/nativeSecret.d.ts +9 -0
- package/dist/network.d.ts +99 -0
- package/dist/podHash.d.ts +12 -0
- package/dist/podSet.d.ts +36 -0
- package/dist/policy.d.ts +150 -0
- package/dist/ports.d.ts +101 -0
- package/dist/refs.d.ts +26 -0
- package/dist/secretBind.d.ts +36 -0
- package/dist/selector.d.ts +30 -0
- package/dist/volume.d.ts +108 -0
- package/dist/workload.d.ts +117 -0
- package/dist/workloadHelpers.d.ts +97 -0
- package/package.json +69 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1008 @@
|
|
|
1
|
+
import { Dep, Manifest, RenderError, brand, unsafeCoerce } from "@konfig.ts/core";
|
|
2
|
+
import { Data, Effect, Layer, Match, Redacted } from "effect";
|
|
3
|
+
import { Environment as Environment$1, Secret as Secret$1, runtime } from "@konfig.ts/env";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __exportAll = (all, no_symbols) => {
|
|
8
|
+
let target = {};
|
|
9
|
+
for (var name in all) __defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true
|
|
12
|
+
});
|
|
13
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
14
|
+
return target;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/.generated/k8s-types/index.ts
|
|
18
|
+
var k8s_types_exports = /* @__PURE__ */ __exportAll({});
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/container.ts
|
|
21
|
+
/**
|
|
22
|
+
* `Container` value namespace.
|
|
23
|
+
*
|
|
24
|
+
* const apiContainer = Container.define({
|
|
25
|
+
* name: "api",
|
|
26
|
+
* image: apiImage,
|
|
27
|
+
* ports: [Port.make({ name: "http", containerPort: 8080 })],
|
|
28
|
+
* readinessProbe: { httpGet: { port: Port.ref("http") } },
|
|
29
|
+
* env: [...],
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* `Container.define` captures the union of named ports (from
|
|
33
|
+
* `Port.make`) as `Ports`, the union of mounted volume names (from
|
|
34
|
+
* `Volume.mountRef`) as `Mounts`, and validates that the `env` list
|
|
35
|
+
* has no duplicate env-var names. The first two phantoms travel on
|
|
36
|
+
* `ContainerSpec`; `Pod.define` checks the container's Mounts against
|
|
37
|
+
* the pod's declared volume names.
|
|
38
|
+
*
|
|
39
|
+
* Duplicate env names are caught at the call site via a template-literal
|
|
40
|
+
* error message — see `EnvDupCheck`. With no volumeMounts and no env,
|
|
41
|
+
* all phantoms collapse to `never` so the container slots into any pod.
|
|
42
|
+
*/
|
|
43
|
+
const Container = { define: (input) => {
|
|
44
|
+
return {
|
|
45
|
+
...input,
|
|
46
|
+
ports: unsafeCoerce(input.ports, "Ports tuple's element brands are the same PortName<N>; widening Ports → readonly ContainerPort<P>[] only changes the static shape, not the runtime values"),
|
|
47
|
+
readinessProbe: input.readinessProbe,
|
|
48
|
+
livenessProbe: input.livenessProbe,
|
|
49
|
+
startupProbe: input.startupProbe,
|
|
50
|
+
volumeMounts: unsafeCoerce(input.volumeMounts, "Mounts tuple's element brands are the same VolumeMount<N>; widening Mounts → readonly VolumeMount<M>[] preserves runtime shape"),
|
|
51
|
+
env: unsafeCoerce(input.env, "EnvDupCheck<Envs> intersection vanishes at runtime; the runtime value is the original EnvVar[]")
|
|
52
|
+
};
|
|
53
|
+
} };
|
|
54
|
+
/**
|
|
55
|
+
* `Pod` value namespace.
|
|
56
|
+
*
|
|
57
|
+
* const pod = Pod.define({
|
|
58
|
+
* volumes: [Volume.empty({ name: "config" })],
|
|
59
|
+
* containers: [Container.define({ ..., volumeMounts: [...] })],
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* imagePullSecrets: [Pod.imagePullSecret(ghcrRef)],
|
|
63
|
+
*
|
|
64
|
+
* - `Pod.define(input)` ties container `volumeMounts[i].name` to the
|
|
65
|
+
* pod's declared volume names via `NoInfer`.
|
|
66
|
+
* - `Pod.imagePullSecret(ref)` returns the `{ name: SecretRef }` entry
|
|
67
|
+
* consumed by Deployment / StatefulSet / Job / CronJob workloads.
|
|
68
|
+
*/
|
|
69
|
+
const Pod = {
|
|
70
|
+
define: (input) => ({
|
|
71
|
+
volumes: unsafeCoerce(input.volumes, "V tuple's elements are Volume<N>; widening V → readonly Volume<VolumeNamesOf<V>>[] is a structural relaxation, runtime value unchanged"),
|
|
72
|
+
containers: input.containers,
|
|
73
|
+
initContainers: input.initContainers
|
|
74
|
+
}),
|
|
75
|
+
imagePullSecret: (ref) => ({ name: ref })
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/ports.ts
|
|
79
|
+
const _portName = (name) => brand(name);
|
|
80
|
+
/**
|
|
81
|
+
* `Port` value namespace.
|
|
82
|
+
*
|
|
83
|
+
* ports: [Port.make({ name: "http", containerPort: 8080 })],
|
|
84
|
+
* readinessProbe: { httpGet: { port: Port.ref("http") } },
|
|
85
|
+
*
|
|
86
|
+
* - `Port.make(input)` constructs a named container port; the literal
|
|
87
|
+
* `name` is captured in the returned `ContainerPort<N>` brand so
|
|
88
|
+
* `Container` can infer the port-name union and constrain
|
|
89
|
+
* cross-references (probes, Service.targetPort).
|
|
90
|
+
* - `Port.ref(name)` returns the brand alone, for probe targets and
|
|
91
|
+
* `targetPort` references that need to name an existing declared port.
|
|
92
|
+
*/
|
|
93
|
+
const Port = {
|
|
94
|
+
make: (input) => ({
|
|
95
|
+
containerPort: input.containerPort,
|
|
96
|
+
name: _portName(input.name),
|
|
97
|
+
protocol: input.protocol,
|
|
98
|
+
hostPort: input.hostPort,
|
|
99
|
+
hostIP: input.hostIP
|
|
100
|
+
}),
|
|
101
|
+
ref: (name) => _portName(name)
|
|
102
|
+
};
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/env.ts
|
|
105
|
+
/**
|
|
106
|
+
* `EnvVar` value namespace.
|
|
107
|
+
*
|
|
108
|
+
* env: [
|
|
109
|
+
* EnvVar.value({ name: "PORT", value: "8080" }),
|
|
110
|
+
* EnvVar.fromSecret({ name: "DB_URL", ref: dbCreds.ref, key: "url" }),
|
|
111
|
+
* EnvVar.fromSecretForPod({
|
|
112
|
+
* name: "DB_URL_PRIMARY", ref: dbCreds.ref, key: "url",
|
|
113
|
+
* podNamespace: "app",
|
|
114
|
+
* }),
|
|
115
|
+
* EnvVar.fromConfigMap({ name: "NEW_UI", ref: flags.ref, key: "NEW_UI" }),
|
|
116
|
+
* EnvVar.raw({ name: "POD_NAME", valueFrom: { fieldRef: ... } }),
|
|
117
|
+
* ]
|
|
118
|
+
*
|
|
119
|
+
* Each constructor captures the literal env-var name (`N`) so
|
|
120
|
+
* `Container` can detect duplicate entries across the env list.
|
|
121
|
+
*/
|
|
122
|
+
const EnvVar = {
|
|
123
|
+
value: (input) => ({
|
|
124
|
+
name: input.name,
|
|
125
|
+
value: input.value
|
|
126
|
+
}),
|
|
127
|
+
fromSecret: (input) => ({
|
|
128
|
+
name: input.name,
|
|
129
|
+
valueFrom: { secretKeyRef: {
|
|
130
|
+
name: input.ref,
|
|
131
|
+
key: input.key,
|
|
132
|
+
optional: input.optional
|
|
133
|
+
} }
|
|
134
|
+
}),
|
|
135
|
+
/**
|
|
136
|
+
* Namespace-checked variant. The ref's namespace slot (`Ns`) must
|
|
137
|
+
* match `podNamespace`. Catches "pod in namespace A references a
|
|
138
|
+
* Secret in namespace B" at compile time — kube-apiserver only
|
|
139
|
+
* resolves `valueFrom.secretKeyRef` against the pod's own namespace,
|
|
140
|
+
* so cross-namespace refs are runtime errors.
|
|
141
|
+
*/
|
|
142
|
+
fromSecretForPod: (input) => ({
|
|
143
|
+
name: input.name,
|
|
144
|
+
valueFrom: { secretKeyRef: {
|
|
145
|
+
name: input.ref,
|
|
146
|
+
key: input.key,
|
|
147
|
+
optional: input.optional
|
|
148
|
+
} }
|
|
149
|
+
}),
|
|
150
|
+
fromConfigMap: (input) => ({
|
|
151
|
+
name: input.name,
|
|
152
|
+
valueFrom: { configMapKeyRef: {
|
|
153
|
+
name: input.ref,
|
|
154
|
+
key: input.key,
|
|
155
|
+
optional: input.optional
|
|
156
|
+
} }
|
|
157
|
+
}),
|
|
158
|
+
raw: (input) => input
|
|
159
|
+
};
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/refs.ts
|
|
162
|
+
const SecretRef = {
|
|
163
|
+
of: (name) => brand(name),
|
|
164
|
+
/**
|
|
165
|
+
* Escape hatch — widen a typed ref's namespace slot to `any`, making
|
|
166
|
+
* it usable across any pod context. Use sparingly (legitimate
|
|
167
|
+
* cross-namespace cases: ExternalSecret reflection, in-cluster
|
|
168
|
+
* shared infra). The cast is grep-able so PR reviewers see opt-ins.
|
|
169
|
+
*/
|
|
170
|
+
unsafeReNamespace: (ref) => ref
|
|
171
|
+
};
|
|
172
|
+
const ConfigMapRef = { of: (name) => brand(name) };
|
|
173
|
+
const ServiceAccountRef = { of: (name) => brand(name) };
|
|
174
|
+
const PvcRef = { of: (name) => brand(name) };
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/identity.ts
|
|
177
|
+
const Namespace = { make: (input) => {
|
|
178
|
+
const resource = {
|
|
179
|
+
apiVersion: "v1",
|
|
180
|
+
kind: "Namespace",
|
|
181
|
+
metadata: {
|
|
182
|
+
name: input.name,
|
|
183
|
+
labels: input.labels,
|
|
184
|
+
annotations: input.annotations
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const m = Manifest.make(() => Effect.succeed(resource));
|
|
188
|
+
return Object.assign(m, { ref: input.name });
|
|
189
|
+
} };
|
|
190
|
+
const ServiceAccount = { make: (input) => {
|
|
191
|
+
const resource = {
|
|
192
|
+
apiVersion: "v1",
|
|
193
|
+
kind: "ServiceAccount",
|
|
194
|
+
metadata: {
|
|
195
|
+
name: input.name,
|
|
196
|
+
namespace: input.namespace,
|
|
197
|
+
labels: input.labels,
|
|
198
|
+
annotations: input.annotations
|
|
199
|
+
},
|
|
200
|
+
automountServiceAccountToken: input.automountServiceAccountToken,
|
|
201
|
+
imagePullSecrets: input.imagePullSecrets?.map((s) => ({ name: s.name }))
|
|
202
|
+
};
|
|
203
|
+
const m = Manifest.make(() => Effect.succeed(resource));
|
|
204
|
+
return Object.assign(m, { ref: ServiceAccountRef.of(input.name) });
|
|
205
|
+
} };
|
|
206
|
+
const ConfigMap = { make: (input) => {
|
|
207
|
+
const resource = {
|
|
208
|
+
apiVersion: "v1",
|
|
209
|
+
kind: "ConfigMap",
|
|
210
|
+
metadata: {
|
|
211
|
+
name: input.name,
|
|
212
|
+
namespace: input.namespace,
|
|
213
|
+
labels: input.labels,
|
|
214
|
+
annotations: input.annotations
|
|
215
|
+
},
|
|
216
|
+
data: input.data,
|
|
217
|
+
binaryData: input.binaryData,
|
|
218
|
+
immutable: input.immutable
|
|
219
|
+
};
|
|
220
|
+
const m = Manifest.make(() => Effect.succeed(resource));
|
|
221
|
+
return Object.assign(m, { ref: ConfigMapRef.of(input.name) });
|
|
222
|
+
} };
|
|
223
|
+
const Secret$2 = { make: (input) => {
|
|
224
|
+
const resource = {
|
|
225
|
+
apiVersion: "v1",
|
|
226
|
+
kind: "Secret",
|
|
227
|
+
metadata: {
|
|
228
|
+
name: input.name,
|
|
229
|
+
namespace: input.namespace,
|
|
230
|
+
labels: input.labels,
|
|
231
|
+
annotations: input.annotations
|
|
232
|
+
},
|
|
233
|
+
type: input.type,
|
|
234
|
+
data: input.data,
|
|
235
|
+
stringData: input.stringData,
|
|
236
|
+
immutable: input.immutable
|
|
237
|
+
};
|
|
238
|
+
const m = Manifest.make(() => Effect.succeed(resource));
|
|
239
|
+
return Object.assign(m, { ref: SecretRef.of(input.name) });
|
|
240
|
+
} };
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/secretBind.ts
|
|
243
|
+
const bindSecret = (input) => {
|
|
244
|
+
const { secret } = input;
|
|
245
|
+
const namespace = unsafeCoerce(input.namespace ?? secret.namespace, "Ns defaults to `string`; the override (if present) is `Ns`, the secret's own namespace is `string` — runtime value either way is a string");
|
|
246
|
+
const ref = SecretRef.of(secret.name);
|
|
247
|
+
const envVars = secret.keys.map((key) => EnvVar.fromSecret({
|
|
248
|
+
name: secret.env[key],
|
|
249
|
+
ref,
|
|
250
|
+
key
|
|
251
|
+
}));
|
|
252
|
+
const refLayer = Dep.provideSecret(secret.name);
|
|
253
|
+
const manifest = input.backend === void 0 ? void 0 : input.backend.emit({
|
|
254
|
+
name: secret.name,
|
|
255
|
+
namespace,
|
|
256
|
+
keys: secret.keys,
|
|
257
|
+
labels: input.labels,
|
|
258
|
+
annotations: input.annotations,
|
|
259
|
+
source: input.source
|
|
260
|
+
});
|
|
261
|
+
const out = {
|
|
262
|
+
ref,
|
|
263
|
+
name: secret.name,
|
|
264
|
+
namespace,
|
|
265
|
+
keys: secret.keys,
|
|
266
|
+
envVars,
|
|
267
|
+
manifest,
|
|
268
|
+
refLayer
|
|
269
|
+
};
|
|
270
|
+
if (input.source === void 0) return out;
|
|
271
|
+
const source = input.source;
|
|
272
|
+
const valuesTag = Dep.SecretValues(secret.name);
|
|
273
|
+
const layer = Layer.effect(valuesTag, source.resolve.pipe(Effect.mapError((cause) => new RenderError({
|
|
274
|
+
message: `SecretValues(${namespace}/${secret.name}): source failed for key "${cause.key}"`,
|
|
275
|
+
cause
|
|
276
|
+
}))));
|
|
277
|
+
return {
|
|
278
|
+
...out,
|
|
279
|
+
values: valuesTag,
|
|
280
|
+
layer
|
|
281
|
+
};
|
|
282
|
+
};
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/environmentBind.ts
|
|
285
|
+
const _bindLiteral = (input) => {
|
|
286
|
+
const hasOverride = input.override !== void 0;
|
|
287
|
+
const value = hasOverride ? input.override : input.entry.value;
|
|
288
|
+
const serialized = hasOverride ? input.entry.serialize(input.override) : input.entry.serialized;
|
|
289
|
+
return {
|
|
290
|
+
envName: input.entry.envName,
|
|
291
|
+
value,
|
|
292
|
+
envVar: {
|
|
293
|
+
name: input.entry.envName,
|
|
294
|
+
value: serialized
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
const _bindDownward = (input) => ({
|
|
299
|
+
envName: input.entry.envName,
|
|
300
|
+
fieldPath: input.entry.fieldPath,
|
|
301
|
+
envVar: {
|
|
302
|
+
name: input.entry.envName,
|
|
303
|
+
valueFrom: { fieldRef: { fieldPath: input.entry.fieldPath } }
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
const _handleSecret = (input) => {
|
|
307
|
+
const memberOpts = unsafeCoerce(input.secretsOpts?.[input.memberKey], "SecretMembersOpts<M> shape — runtime key lookup against the typed input");
|
|
308
|
+
const d = bindSecret({
|
|
309
|
+
secret: unsafeCoerce(input.entry, "Match.tag('Secret') narrowed entry to SecretEntry; the helper-level signature is the general SecretEntry shape"),
|
|
310
|
+
backend: memberOpts?.backend,
|
|
311
|
+
source: memberOpts?.source,
|
|
312
|
+
labels: memberOpts?.labels,
|
|
313
|
+
annotations: memberOpts?.annotations,
|
|
314
|
+
namespace: input.namespace
|
|
315
|
+
});
|
|
316
|
+
input.acc.declared[input.memberKey] = d;
|
|
317
|
+
input.acc.envVars.push(...d.envVars);
|
|
318
|
+
if (d.manifest !== void 0) input.acc.manifests.push(d.manifest);
|
|
319
|
+
if (d.layer !== void 0) input.acc.valuesLayers.push(unsafeCoerce(d.layer, "DeclaredSecret.layer is Layer<Provide<SecretValues, N>, ...>; widen to unknown for the heterogeneous Layer.mergeAll"));
|
|
320
|
+
};
|
|
321
|
+
const _handleLiteral = (input) => {
|
|
322
|
+
const d = _bindLiteral({
|
|
323
|
+
entry: unsafeCoerce(input.entry, "Match.tag('Literal') narrowed entry to LiteralEntry<string, unknown>"),
|
|
324
|
+
override: input.literalsOpts?.[input.memberKey]
|
|
325
|
+
});
|
|
326
|
+
input.acc.declared[input.memberKey] = d;
|
|
327
|
+
input.acc.envVars.push(d.envVar);
|
|
328
|
+
};
|
|
329
|
+
const _handleDownward = (input) => {
|
|
330
|
+
const d = _bindDownward({ entry: unsafeCoerce(input.entry, "Match.tag('Downward') narrowed entry to DownwardEntry<string>") });
|
|
331
|
+
input.acc.declared[input.memberKey] = d;
|
|
332
|
+
input.acc.envVars.push(d.envVar);
|
|
333
|
+
};
|
|
334
|
+
const _handleEnvironment = (input) => {
|
|
335
|
+
const sub = bindEnvironment({
|
|
336
|
+
env: unsafeCoerce(input.entry, "Match.tag('Environment') narrowed entry to a nested Environment"),
|
|
337
|
+
secrets: unsafeCoerce(input.secretsOpts?.[input.memberKey] ?? {}, "sub-record from SecretMembersOpts<M> — type is checked at the outer call site"),
|
|
338
|
+
literals: unsafeCoerce(input.literalsOpts?.[input.memberKey] ?? {}, "sub-record from LiteralMembersOpts<M> — type is checked at the outer call site"),
|
|
339
|
+
namespace: input.namespace
|
|
340
|
+
});
|
|
341
|
+
input.acc.declared[input.memberKey] = sub.members;
|
|
342
|
+
input.acc.envVars.push(...sub.envVars);
|
|
343
|
+
input.acc.manifests.push(...sub.manifests);
|
|
344
|
+
input.acc.valuesLayers.push(unsafeCoerce(sub.valuesLayer, "valuesLayer aggregate over the recursive merge"));
|
|
345
|
+
};
|
|
346
|
+
const _dispatch = (input) => Match.value(input.entry._kind).pipe(Match.when("Secret", () => _handleSecret(input)), Match.when("Literal", () => _handleLiteral(input)), Match.when("Downward", () => _handleDownward(input)), Match.when("Environment", () => _handleEnvironment(input)), Match.exhaustive);
|
|
347
|
+
const bindEnvironment = (input) => {
|
|
348
|
+
const { env } = input;
|
|
349
|
+
const acc = {
|
|
350
|
+
declared: {},
|
|
351
|
+
envVars: [],
|
|
352
|
+
manifests: [],
|
|
353
|
+
valuesLayers: []
|
|
354
|
+
};
|
|
355
|
+
const secretsOpts = unsafeCoerce(input.secrets, "discriminated union from BindEnvironmentInput; iterate keys at runtime");
|
|
356
|
+
const literalsOpts = unsafeCoerce(input.literals, "discriminated union from BindEnvironmentInput; iterate keys at runtime");
|
|
357
|
+
for (const memberKey of Object.keys(env.members)) _dispatch({
|
|
358
|
+
memberKey,
|
|
359
|
+
entry: unsafeCoerce(env.members[memberKey], "env.members values are EnvMember by construction"),
|
|
360
|
+
secretsOpts,
|
|
361
|
+
literalsOpts,
|
|
362
|
+
namespace: input.namespace,
|
|
363
|
+
acc
|
|
364
|
+
});
|
|
365
|
+
const valuesLayer = unsafeCoerce(acc.valuesLayers.length === 0 ? Layer.empty : Layer.mergeAll(acc.valuesLayers[0], ...acc.valuesLayers.slice(1)), "merged Layer over a heterogeneous list of per-secret value layers");
|
|
366
|
+
return {
|
|
367
|
+
envVars: acc.envVars,
|
|
368
|
+
manifests: acc.manifests,
|
|
369
|
+
members: unsafeCoerce(acc.declared, "declared populated by iterating env.members; each key maps to its DeclaredMember<M[K], Ns>"),
|
|
370
|
+
valuesLayer
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/backend.ts
|
|
375
|
+
var BackendSourceMissing = class extends Data.TaggedError("BackendSourceMissing") {
|
|
376
|
+
get message() {
|
|
377
|
+
return `backend "${this.backend}" requires a source but none was provided for secret "${this.secret}"`;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
//#endregion
|
|
381
|
+
//#region src/nativeSecret.ts
|
|
382
|
+
const _emit = (input) => Manifest.make((_ctx) => Effect.gen(function* () {
|
|
383
|
+
if (input.opts.silenceWarning !== true) yield* Effect.logWarning(`NativeSecret backend emits a plaintext Secret with stringData on disk for "${input.base.namespace}/${input.base.name}". Pass { silenceWarning: true } to suppress, or switch to ExternalSecrets / SealedSecrets / Sops for production.`);
|
|
384
|
+
const resolved = yield* input.source.resolve.pipe(Effect.mapError((cause) => new RenderError({
|
|
385
|
+
message: `NativeSecret(${input.base.namespace}/${input.base.name}): source failed for key "${cause.key}"`,
|
|
386
|
+
cause
|
|
387
|
+
})));
|
|
388
|
+
const stringData = {};
|
|
389
|
+
for (const key of input.base.keys) stringData[key] = Redacted.value(resolved[key]);
|
|
390
|
+
return {
|
|
391
|
+
apiVersion: "v1",
|
|
392
|
+
kind: "Secret",
|
|
393
|
+
metadata: {
|
|
394
|
+
name: input.base.name,
|
|
395
|
+
namespace: input.base.namespace,
|
|
396
|
+
labels: input.base.labels,
|
|
397
|
+
annotations: input.base.annotations
|
|
398
|
+
},
|
|
399
|
+
type: input.opts.type,
|
|
400
|
+
stringData,
|
|
401
|
+
immutable: input.opts.immutable
|
|
402
|
+
};
|
|
403
|
+
}));
|
|
404
|
+
const NativeSecret = { backend: (opts) => {
|
|
405
|
+
const resolvedOpts = opts ?? {};
|
|
406
|
+
return {
|
|
407
|
+
_tag: "NativeSecret",
|
|
408
|
+
requiresSource: true,
|
|
409
|
+
emit: (input) => {
|
|
410
|
+
if (input.source === void 0) throw new BackendSourceMissing({
|
|
411
|
+
backend: "NativeSecret",
|
|
412
|
+
secret: input.name
|
|
413
|
+
});
|
|
414
|
+
return _emit({
|
|
415
|
+
base: input,
|
|
416
|
+
opts: resolvedOpts,
|
|
417
|
+
source: input.source
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
} };
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/podHash.ts
|
|
424
|
+
const _frame = (hasher, value) => {
|
|
425
|
+
const bytes = Buffer.from(value, "utf8");
|
|
426
|
+
hasher.update(`${bytes.length}:`);
|
|
427
|
+
hasher.update(bytes);
|
|
428
|
+
hasher.update(",");
|
|
429
|
+
};
|
|
430
|
+
const hashSecretValues = (input) => {
|
|
431
|
+
const hasher = createHash("sha256");
|
|
432
|
+
_frame(hasher, "konfig/secret-values-hash/v1");
|
|
433
|
+
_frame(hasher, input.salt);
|
|
434
|
+
const keys = Object.keys(input.values).sort();
|
|
435
|
+
for (const key of keys) {
|
|
436
|
+
_frame(hasher, key);
|
|
437
|
+
_frame(hasher, Redacted.value(input.values[key]));
|
|
438
|
+
}
|
|
439
|
+
return hasher.digest("hex");
|
|
440
|
+
};
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/network.ts
|
|
443
|
+
const Service = {
|
|
444
|
+
make: (input) => {
|
|
445
|
+
const resource = {
|
|
446
|
+
apiVersion: "v1",
|
|
447
|
+
kind: "Service",
|
|
448
|
+
metadata: {
|
|
449
|
+
name: input.name,
|
|
450
|
+
namespace: input.namespace,
|
|
451
|
+
labels: input.labels,
|
|
452
|
+
annotations: input.annotations
|
|
453
|
+
},
|
|
454
|
+
spec: {
|
|
455
|
+
selector: input.selector,
|
|
456
|
+
type: input.type,
|
|
457
|
+
ports: unsafeCoerce(input.ports, "input.ports is the user-typed Service spec; K8s ServicePort allows the same fields"),
|
|
458
|
+
clusterIP: input.clusterIP,
|
|
459
|
+
sessionAffinity: input.sessionAffinity,
|
|
460
|
+
publishNotReadyAddresses: input.publishNotReadyAddresses,
|
|
461
|
+
externalTrafficPolicy: input.externalTrafficPolicy,
|
|
462
|
+
internalTrafficPolicy: input.internalTrafficPolicy
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
return Manifest.make(() => Effect.succeed(resource));
|
|
466
|
+
},
|
|
467
|
+
fromContainer: (input) => Service.make({
|
|
468
|
+
name: input.name,
|
|
469
|
+
namespace: input.namespace,
|
|
470
|
+
labels: input.labels,
|
|
471
|
+
annotations: input.annotations,
|
|
472
|
+
selector: input.selector,
|
|
473
|
+
ports: unsafeCoerce(input.ports, "ServicePortSpec<Ports> structurally matches K8sServicePort; targetPort's PortName<Ports> brand is a phantom — runtime value is the underlying string"),
|
|
474
|
+
type: input.type,
|
|
475
|
+
clusterIP: input.clusterIP,
|
|
476
|
+
sessionAffinity: input.sessionAffinity,
|
|
477
|
+
publishNotReadyAddresses: input.publishNotReadyAddresses,
|
|
478
|
+
externalTrafficPolicy: input.externalTrafficPolicy,
|
|
479
|
+
internalTrafficPolicy: input.internalTrafficPolicy
|
|
480
|
+
}),
|
|
481
|
+
fromPodSet: (input) => Service.make({
|
|
482
|
+
name: input.name,
|
|
483
|
+
namespace: input.namespace,
|
|
484
|
+
labels: input.labels,
|
|
485
|
+
annotations: input.annotations,
|
|
486
|
+
selector: input.podSet.labels,
|
|
487
|
+
ports: input.ports,
|
|
488
|
+
type: input.type,
|
|
489
|
+
clusterIP: input.clusterIP,
|
|
490
|
+
sessionAffinity: input.sessionAffinity,
|
|
491
|
+
publishNotReadyAddresses: input.publishNotReadyAddresses,
|
|
492
|
+
externalTrafficPolicy: input.externalTrafficPolicy,
|
|
493
|
+
internalTrafficPolicy: input.internalTrafficPolicy
|
|
494
|
+
})
|
|
495
|
+
};
|
|
496
|
+
const Ingress = {
|
|
497
|
+
make: (input) => {
|
|
498
|
+
const resource = {
|
|
499
|
+
apiVersion: "networking.k8s.io/v1",
|
|
500
|
+
kind: "Ingress",
|
|
501
|
+
metadata: {
|
|
502
|
+
name: input.name,
|
|
503
|
+
namespace: input.namespace,
|
|
504
|
+
labels: input.labels,
|
|
505
|
+
annotations: input.annotations
|
|
506
|
+
},
|
|
507
|
+
spec: {
|
|
508
|
+
ingressClassName: input.ingressClassName,
|
|
509
|
+
rules: unsafeCoerce(input.rules, "user-supplied Ingress rules; widening from our convenience type to the K8s type"),
|
|
510
|
+
tls: unsafeCoerce(input.tls, "Ingress.tls produces K8sIngressTLS with branded secretName"),
|
|
511
|
+
defaultBackend: unsafeCoerce(input.defaultBackend, "user-supplied IngressBackend; structural match to K8s type")
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
return Manifest.make(() => Effect.succeed(resource));
|
|
515
|
+
},
|
|
516
|
+
/**
|
|
517
|
+
* TLS entry constructor for `Ingress.make({ tls: [...] })`. Takes a
|
|
518
|
+
* branded `SecretRef` so the secret's name can't widen to a raw
|
|
519
|
+
* string at the call site.
|
|
520
|
+
*/
|
|
521
|
+
tls: (input) => ({
|
|
522
|
+
secretName: input.secretName,
|
|
523
|
+
hosts: input.hosts
|
|
524
|
+
})
|
|
525
|
+
};
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region src/policy.ts
|
|
528
|
+
const PersistentVolume = { make: (input) => Manifest.make(() => Effect.succeed({
|
|
529
|
+
apiVersion: "v1",
|
|
530
|
+
kind: "PersistentVolume",
|
|
531
|
+
metadata: {
|
|
532
|
+
name: input.name,
|
|
533
|
+
labels: input.labels,
|
|
534
|
+
annotations: input.annotations
|
|
535
|
+
},
|
|
536
|
+
spec: unsafeCoerce(input.spec, "user-supplied PV spec; structural match to the K8s type")
|
|
537
|
+
})) };
|
|
538
|
+
const PersistentVolumeClaim = { make: (input) => Manifest.make(() => Effect.succeed({
|
|
539
|
+
apiVersion: "v1",
|
|
540
|
+
kind: "PersistentVolumeClaim",
|
|
541
|
+
metadata: {
|
|
542
|
+
name: input.name,
|
|
543
|
+
namespace: input.namespace,
|
|
544
|
+
labels: input.labels,
|
|
545
|
+
annotations: input.annotations
|
|
546
|
+
},
|
|
547
|
+
spec: unsafeCoerce(input.spec, "user-supplied PVC spec; structural match to the K8s type")
|
|
548
|
+
})) };
|
|
549
|
+
const _lowerPeer = (peer) => ({
|
|
550
|
+
...peer.podSet !== void 0 ? { podSelector: { matchLabels: peer.podSet.labels } } : {},
|
|
551
|
+
...peer.namespaceSelector !== void 0 ? { namespaceSelector: peer.namespaceSelector } : {},
|
|
552
|
+
...peer.ipBlock !== void 0 ? { ipBlock: peer.ipBlock } : {}
|
|
553
|
+
});
|
|
554
|
+
const NetworkPolicy = {
|
|
555
|
+
make: (input) => Manifest.make(() => Effect.succeed({
|
|
556
|
+
apiVersion: "networking.k8s.io/v1",
|
|
557
|
+
kind: "NetworkPolicy",
|
|
558
|
+
metadata: {
|
|
559
|
+
name: input.name,
|
|
560
|
+
namespace: input.namespace,
|
|
561
|
+
labels: input.labels,
|
|
562
|
+
annotations: input.annotations
|
|
563
|
+
},
|
|
564
|
+
spec: input.spec
|
|
565
|
+
})),
|
|
566
|
+
fromPodSet: (input) => {
|
|
567
|
+
const ingress = input.ingress?.map((rule) => ({
|
|
568
|
+
from: rule.from?.map(_lowerPeer),
|
|
569
|
+
ports: rule.ports
|
|
570
|
+
}));
|
|
571
|
+
const egress = input.egress?.map((rule) => ({
|
|
572
|
+
to: rule.to?.map(_lowerPeer),
|
|
573
|
+
ports: rule.ports
|
|
574
|
+
}));
|
|
575
|
+
return NetworkPolicy.make({
|
|
576
|
+
name: input.name,
|
|
577
|
+
namespace: input.namespace,
|
|
578
|
+
labels: input.labels,
|
|
579
|
+
annotations: input.annotations,
|
|
580
|
+
spec: unsafeCoerce({
|
|
581
|
+
podSelector: { matchLabels: input.podSet.labels },
|
|
582
|
+
policyTypes: input.policyTypes,
|
|
583
|
+
ingress,
|
|
584
|
+
egress
|
|
585
|
+
}, "konfig peers carry readonly arrays; upstream NetworkPolicySpec is mutable but the runtime shape matches")
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
const ClusterRole = { make: (input) => Manifest.make(() => Effect.succeed({
|
|
590
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
591
|
+
kind: "ClusterRole",
|
|
592
|
+
metadata: {
|
|
593
|
+
name: input.name,
|
|
594
|
+
labels: input.labels,
|
|
595
|
+
annotations: input.annotations
|
|
596
|
+
},
|
|
597
|
+
rules: input.rules,
|
|
598
|
+
aggregationRule: input.aggregationRule
|
|
599
|
+
})) };
|
|
600
|
+
const ClusterRoleBinding = { make: (input) => Manifest.make(() => Effect.succeed({
|
|
601
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
602
|
+
kind: "ClusterRoleBinding",
|
|
603
|
+
metadata: {
|
|
604
|
+
name: input.name,
|
|
605
|
+
labels: input.labels,
|
|
606
|
+
annotations: input.annotations
|
|
607
|
+
},
|
|
608
|
+
roleRef: input.roleRef,
|
|
609
|
+
subjects: input.subjects
|
|
610
|
+
})) };
|
|
611
|
+
const Role = { make: (input) => Manifest.make(() => Effect.succeed({
|
|
612
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
613
|
+
kind: "Role",
|
|
614
|
+
metadata: {
|
|
615
|
+
name: input.name,
|
|
616
|
+
namespace: input.namespace,
|
|
617
|
+
labels: input.labels,
|
|
618
|
+
annotations: input.annotations
|
|
619
|
+
},
|
|
620
|
+
rules: input.rules
|
|
621
|
+
})) };
|
|
622
|
+
const RoleBinding = { make: (input) => Manifest.make(() => Effect.succeed({
|
|
623
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
624
|
+
kind: "RoleBinding",
|
|
625
|
+
metadata: {
|
|
626
|
+
name: input.name,
|
|
627
|
+
namespace: input.namespace,
|
|
628
|
+
labels: input.labels,
|
|
629
|
+
annotations: input.annotations
|
|
630
|
+
},
|
|
631
|
+
roleRef: input.roleRef,
|
|
632
|
+
subjects: input.subjects
|
|
633
|
+
})) };
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/volume.ts
|
|
636
|
+
const _volumeName = (name) => brand(name);
|
|
637
|
+
/**
|
|
638
|
+
* `Volume` value namespace.
|
|
639
|
+
*
|
|
640
|
+
* volumes: [
|
|
641
|
+
* Volume.empty({ name: "config" }),
|
|
642
|
+
* Volume.fromSecret({ name: "tls", ref: tlsSecret.ref }),
|
|
643
|
+
* Volume.fromConfigMap({ name: "settings", ref: cfg.ref }),
|
|
644
|
+
* Volume.fromPvc({ name: "data", claim: pvc.ref }),
|
|
645
|
+
* ],
|
|
646
|
+
* volumeMounts: [
|
|
647
|
+
* { name: Volume.mountRef("config"), mountPath: "/etc/conf" },
|
|
648
|
+
* ],
|
|
649
|
+
*
|
|
650
|
+
* All four constructors capture the literal `name` in the returned
|
|
651
|
+
* `Volume<N>` brand; `Pod` infers the union and constrains each
|
|
652
|
+
* container's `volumeMounts[i].name` to it.
|
|
653
|
+
*/
|
|
654
|
+
const Volume = {
|
|
655
|
+
empty: (input) => ({
|
|
656
|
+
name: _volumeName(input.name),
|
|
657
|
+
emptyDir: {
|
|
658
|
+
medium: input.medium,
|
|
659
|
+
sizeLimit: input.sizeLimit
|
|
660
|
+
}
|
|
661
|
+
}),
|
|
662
|
+
fromSecret: (input) => ({
|
|
663
|
+
name: _volumeName(input.name),
|
|
664
|
+
secret: {
|
|
665
|
+
secretName: input.ref,
|
|
666
|
+
optional: input.optional,
|
|
667
|
+
defaultMode: input.defaultMode
|
|
668
|
+
}
|
|
669
|
+
}),
|
|
670
|
+
fromConfigMap: (input) => ({
|
|
671
|
+
name: _volumeName(input.name),
|
|
672
|
+
configMap: {
|
|
673
|
+
name: input.ref,
|
|
674
|
+
optional: input.optional,
|
|
675
|
+
defaultMode: input.defaultMode,
|
|
676
|
+
items: input.items
|
|
677
|
+
}
|
|
678
|
+
}),
|
|
679
|
+
fromPvc: (input) => ({
|
|
680
|
+
name: _volumeName(input.name),
|
|
681
|
+
persistentVolumeClaim: {
|
|
682
|
+
claimName: input.claim,
|
|
683
|
+
readOnly: input.readOnly
|
|
684
|
+
}
|
|
685
|
+
}),
|
|
686
|
+
mountRef: (name) => _volumeName(name)
|
|
687
|
+
};
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region src/workload.ts
|
|
690
|
+
const Deployment = {
|
|
691
|
+
make: (input) => {
|
|
692
|
+
const resource = {
|
|
693
|
+
apiVersion: "apps/v1",
|
|
694
|
+
kind: "Deployment",
|
|
695
|
+
metadata: {
|
|
696
|
+
name: input.name,
|
|
697
|
+
namespace: input.namespace,
|
|
698
|
+
labels: input.labels,
|
|
699
|
+
annotations: input.annotations
|
|
700
|
+
},
|
|
701
|
+
spec: {
|
|
702
|
+
replicas: input.replicas,
|
|
703
|
+
selector: input.selector,
|
|
704
|
+
template: unsafeCoerce(input.template, "konfig PodSpecInput is structurally a K8s PodTemplateSpec body; brand-checked fields lower at construction"),
|
|
705
|
+
strategy: unsafeCoerce(input.strategy, "DeploymentStrategy is escape-hatch unknown; lift to K8s type for serialization"),
|
|
706
|
+
revisionHistoryLimit: input.revisionHistoryLimit,
|
|
707
|
+
progressDeadlineSeconds: input.progressDeadlineSeconds,
|
|
708
|
+
minReadySeconds: input.minReadySeconds
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
return Manifest.make(() => Effect.succeed(resource));
|
|
712
|
+
},
|
|
713
|
+
fromPodSet: (input) => Deployment.make({
|
|
714
|
+
name: input.name,
|
|
715
|
+
namespace: input.namespace,
|
|
716
|
+
labels: input.labels,
|
|
717
|
+
annotations: input.annotations,
|
|
718
|
+
replicas: input.replicas,
|
|
719
|
+
selector: { matchLabels: input.podSet.labels },
|
|
720
|
+
template: {
|
|
721
|
+
metadata: {
|
|
722
|
+
labels: {
|
|
723
|
+
...input.template.metadata?.labels,
|
|
724
|
+
...input.podSet.labels
|
|
725
|
+
},
|
|
726
|
+
annotations: input.template.metadata?.annotations
|
|
727
|
+
},
|
|
728
|
+
spec: input.template.spec
|
|
729
|
+
},
|
|
730
|
+
strategy: input.strategy,
|
|
731
|
+
revisionHistoryLimit: input.revisionHistoryLimit,
|
|
732
|
+
progressDeadlineSeconds: input.progressDeadlineSeconds,
|
|
733
|
+
minReadySeconds: input.minReadySeconds
|
|
734
|
+
})
|
|
735
|
+
};
|
|
736
|
+
const StatefulSet = { make: (input) => {
|
|
737
|
+
const resource = {
|
|
738
|
+
apiVersion: "apps/v1",
|
|
739
|
+
kind: "StatefulSet",
|
|
740
|
+
metadata: {
|
|
741
|
+
name: input.name,
|
|
742
|
+
namespace: input.namespace,
|
|
743
|
+
labels: input.labels,
|
|
744
|
+
annotations: input.annotations
|
|
745
|
+
},
|
|
746
|
+
spec: {
|
|
747
|
+
replicas: input.replicas,
|
|
748
|
+
selector: input.selector,
|
|
749
|
+
template: unsafeCoerce(input.template, "konfig PodSpecInput is structurally a K8s PodTemplateSpec body; brand-checked fields lower at construction"),
|
|
750
|
+
serviceName: input.serviceName,
|
|
751
|
+
volumeClaimTemplates: unsafeCoerce(input.volumeClaimTemplates, "StatefulSet volumeClaimTemplates is escape-hatch unknown; lift to K8s type"),
|
|
752
|
+
podManagementPolicy: input.podManagementPolicy,
|
|
753
|
+
updateStrategy: unsafeCoerce(input.updateStrategy, "StatefulSetUpdateStrategy is escape-hatch unknown")
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
return Manifest.make(() => Effect.succeed(resource));
|
|
757
|
+
} };
|
|
758
|
+
const Job = { make: (input) => {
|
|
759
|
+
const resource = {
|
|
760
|
+
apiVersion: "batch/v1",
|
|
761
|
+
kind: "Job",
|
|
762
|
+
metadata: {
|
|
763
|
+
name: input.name,
|
|
764
|
+
namespace: input.namespace,
|
|
765
|
+
labels: input.labels,
|
|
766
|
+
annotations: input.annotations
|
|
767
|
+
},
|
|
768
|
+
spec: {
|
|
769
|
+
parallelism: input.parallelism,
|
|
770
|
+
completions: input.completions,
|
|
771
|
+
backoffLimit: input.backoffLimit,
|
|
772
|
+
activeDeadlineSeconds: input.activeDeadlineSeconds,
|
|
773
|
+
ttlSecondsAfterFinished: input.ttlSecondsAfterFinished,
|
|
774
|
+
suspend: input.suspend,
|
|
775
|
+
template: unsafeCoerce(input.template, "konfig PodSpecInput is structurally a K8s PodTemplateSpec body; brand-checked fields lower at construction")
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
return Manifest.make(() => Effect.succeed(resource));
|
|
779
|
+
} };
|
|
780
|
+
const CronJob = { make: (input) => {
|
|
781
|
+
const resource = {
|
|
782
|
+
apiVersion: "batch/v1",
|
|
783
|
+
kind: "CronJob",
|
|
784
|
+
metadata: {
|
|
785
|
+
name: input.name,
|
|
786
|
+
namespace: input.namespace,
|
|
787
|
+
labels: input.labels,
|
|
788
|
+
annotations: input.annotations
|
|
789
|
+
},
|
|
790
|
+
spec: {
|
|
791
|
+
schedule: input.schedule,
|
|
792
|
+
concurrencyPolicy: input.concurrencyPolicy,
|
|
793
|
+
successfulJobsHistoryLimit: input.successfulJobsHistoryLimit,
|
|
794
|
+
failedJobsHistoryLimit: input.failedJobsHistoryLimit,
|
|
795
|
+
startingDeadlineSeconds: input.startingDeadlineSeconds,
|
|
796
|
+
suspend: input.suspend,
|
|
797
|
+
jobTemplate: unsafeCoerce(input.jobTemplate, "konfig CronJob jobTemplate is structurally a K8s JobTemplateSpec body")
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
return Manifest.make(() => Effect.succeed(resource));
|
|
801
|
+
} };
|
|
802
|
+
//#endregion
|
|
803
|
+
//#region src/workloadHelpers.ts
|
|
804
|
+
var workloadHelpers_exports = /* @__PURE__ */ __exportAll({
|
|
805
|
+
cron: () => cron,
|
|
806
|
+
web: () => web
|
|
807
|
+
});
|
|
808
|
+
const _reloaderAnnotations = (opt) => {
|
|
809
|
+
if (opt === void 0 || opt === "off") return {};
|
|
810
|
+
if (opt === "stakater") return { "reloader.stakater.com/auto": "true" };
|
|
811
|
+
if (opt === "stakater-strict") return {
|
|
812
|
+
"reloader.stakater.com/auto": "true",
|
|
813
|
+
"reloader.stakater.com/match": "true"
|
|
814
|
+
};
|
|
815
|
+
const out = {};
|
|
816
|
+
if (opt.secrets && opt.secrets.length > 0) out["secret.reloader.stakater.com/reload"] = opt.secrets.join(",");
|
|
817
|
+
if (opt.configMaps && opt.configMaps.length > 0) out["configmap.reloader.stakater.com/reload"] = opt.configMaps.join(",");
|
|
818
|
+
return out;
|
|
819
|
+
};
|
|
820
|
+
const web = (input) => {
|
|
821
|
+
const selectorLabels = { app: input.name };
|
|
822
|
+
const podLabels = {
|
|
823
|
+
...input.deployment.podLabels,
|
|
824
|
+
...selectorLabels
|
|
825
|
+
};
|
|
826
|
+
const reloaderAnns = _reloaderAnnotations(input.reloader);
|
|
827
|
+
const deployment = Deployment.make({
|
|
828
|
+
name: input.name,
|
|
829
|
+
namespace: input.namespace,
|
|
830
|
+
labels: {
|
|
831
|
+
...selectorLabels,
|
|
832
|
+
...input.labels
|
|
833
|
+
},
|
|
834
|
+
annotations: {
|
|
835
|
+
...input.annotations,
|
|
836
|
+
...reloaderAnns
|
|
837
|
+
},
|
|
838
|
+
replicas: input.deployment.replicas,
|
|
839
|
+
selector: { matchLabels: selectorLabels },
|
|
840
|
+
template: {
|
|
841
|
+
metadata: {
|
|
842
|
+
labels: podLabels,
|
|
843
|
+
annotations: input.deployment.podAnnotations
|
|
844
|
+
},
|
|
845
|
+
spec: {
|
|
846
|
+
containers: input.deployment.containers,
|
|
847
|
+
volumes: input.deployment.volumes,
|
|
848
|
+
imagePullSecrets: input.deployment.imagePullSecrets,
|
|
849
|
+
serviceAccountName: input.deployment.serviceAccountName
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
const service = Service.make({
|
|
854
|
+
name: input.name,
|
|
855
|
+
namespace: input.namespace,
|
|
856
|
+
labels: {
|
|
857
|
+
...selectorLabels,
|
|
858
|
+
...input.labels
|
|
859
|
+
},
|
|
860
|
+
annotations: input.annotations,
|
|
861
|
+
selector: selectorLabels,
|
|
862
|
+
type: input.service.type ?? "ClusterIP",
|
|
863
|
+
ports: unsafeCoerce(input.service.ports, "ServicePortSpec<Ports> structurally matches K8sServicePort; the PortName<Ports> brand on targetPort is a phantom whose runtime value is the underlying string")
|
|
864
|
+
});
|
|
865
|
+
if (input.ingress === void 0) return Manifest.make((ctx) => Effect.all([deployment.render(ctx), service.render(ctx)], { concurrency: "unbounded" }));
|
|
866
|
+
const ingress = Ingress.make({
|
|
867
|
+
name: input.name,
|
|
868
|
+
namespace: input.namespace,
|
|
869
|
+
labels: {
|
|
870
|
+
...selectorLabels,
|
|
871
|
+
...input.labels
|
|
872
|
+
},
|
|
873
|
+
annotations: {
|
|
874
|
+
...input.annotations,
|
|
875
|
+
...input.ingress.annotations
|
|
876
|
+
},
|
|
877
|
+
ingressClassName: input.ingress.ingressClassName,
|
|
878
|
+
rules: input.ingress.rules,
|
|
879
|
+
tls: input.ingress.tls
|
|
880
|
+
});
|
|
881
|
+
return Manifest.make((ctx) => Effect.all([
|
|
882
|
+
deployment.render(ctx),
|
|
883
|
+
service.render(ctx),
|
|
884
|
+
ingress.render(ctx)
|
|
885
|
+
], { concurrency: "unbounded" }));
|
|
886
|
+
};
|
|
887
|
+
const cron = (input) => {
|
|
888
|
+
const selectorLabels = { app: input.name };
|
|
889
|
+
const sa = Manifest.make(() => Effect.succeed({
|
|
890
|
+
apiVersion: "v1",
|
|
891
|
+
kind: "ServiceAccount",
|
|
892
|
+
metadata: {
|
|
893
|
+
name: input.name,
|
|
894
|
+
namespace: input.namespace,
|
|
895
|
+
labels: {
|
|
896
|
+
...selectorLabels,
|
|
897
|
+
...input.labels
|
|
898
|
+
},
|
|
899
|
+
annotations: input.annotations
|
|
900
|
+
}
|
|
901
|
+
}));
|
|
902
|
+
const cronJob = CronJob.make({
|
|
903
|
+
name: input.name,
|
|
904
|
+
namespace: input.namespace,
|
|
905
|
+
labels: {
|
|
906
|
+
...selectorLabels,
|
|
907
|
+
...input.labels
|
|
908
|
+
},
|
|
909
|
+
annotations: input.annotations,
|
|
910
|
+
schedule: input.schedule,
|
|
911
|
+
concurrencyPolicy: input.concurrencyPolicy,
|
|
912
|
+
successfulJobsHistoryLimit: input.successfulJobsHistoryLimit,
|
|
913
|
+
failedJobsHistoryLimit: input.failedJobsHistoryLimit,
|
|
914
|
+
jobTemplate: { spec: { template: {
|
|
915
|
+
metadata: { labels: selectorLabels },
|
|
916
|
+
spec: {
|
|
917
|
+
containers: input.containers,
|
|
918
|
+
volumes: input.volumes,
|
|
919
|
+
imagePullSecrets: input.imagePullSecrets,
|
|
920
|
+
serviceAccountName: input.name,
|
|
921
|
+
restartPolicy: input.restartPolicy ?? "OnFailure"
|
|
922
|
+
}
|
|
923
|
+
} } }
|
|
924
|
+
});
|
|
925
|
+
return Manifest.make((ctx) => Effect.all([sa.render(ctx), cronJob.render(ctx)], { concurrency: "unbounded" }));
|
|
926
|
+
};
|
|
927
|
+
//#endregion
|
|
928
|
+
//#region src/selector.ts
|
|
929
|
+
/**
|
|
930
|
+
* `Selector` value namespace.
|
|
931
|
+
*
|
|
932
|
+
* const apiPods = Selector.make({ app: "api", tier: "web" });
|
|
933
|
+
*/
|
|
934
|
+
const Selector = { make: (labels) => unsafeCoerce({ labels }, "SelectorBrand is a unique-symbol phantom — no runtime value; runtime shape is { labels }") };
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region src/podSet.ts
|
|
937
|
+
/**
|
|
938
|
+
* `PodSet` value namespace.
|
|
939
|
+
*
|
|
940
|
+
* const trio = PodSet.define({
|
|
941
|
+
* podSet: redisCachePods,
|
|
942
|
+
* deployment: { ... },
|
|
943
|
+
* service: { ... },
|
|
944
|
+
* netPol: { ... },
|
|
945
|
+
* });
|
|
946
|
+
*
|
|
947
|
+
* Umbrella over `Deployment.fromPodSet`, `Service.fromPodSet`, and
|
|
948
|
+
* `NetworkPolicy.fromPodSet` — emits whichever subset of the trio the
|
|
949
|
+
* input specifies, all rooted at one `Selector`.
|
|
950
|
+
*/
|
|
951
|
+
const PodSet = { define: (input) => {
|
|
952
|
+
const deployment = Deployment.fromPodSet({
|
|
953
|
+
podSet: input.podSet,
|
|
954
|
+
...input.deployment
|
|
955
|
+
});
|
|
956
|
+
const service = input.service !== void 0 ? Service.fromPodSet({
|
|
957
|
+
podSet: input.podSet,
|
|
958
|
+
...input.service
|
|
959
|
+
}) : void 0;
|
|
960
|
+
const netPol = input.netPol !== void 0 ? NetworkPolicy.fromPodSet({
|
|
961
|
+
podSet: input.podSet,
|
|
962
|
+
...input.netPol
|
|
963
|
+
}) : void 0;
|
|
964
|
+
return Manifest.make((ctx) => Effect.gen(function* () {
|
|
965
|
+
const d = yield* deployment.render(ctx);
|
|
966
|
+
const s = service !== void 0 ? yield* service.render(ctx) : void 0;
|
|
967
|
+
const n = netPol !== void 0 ? yield* netPol.render(ctx) : void 0;
|
|
968
|
+
const reason = "tuple element types are statically known per branch; the array literal is widened by TS, the brand-free runtime shape matches the typed tuple";
|
|
969
|
+
if (s !== void 0 && n !== void 0) return unsafeCoerce([
|
|
970
|
+
d,
|
|
971
|
+
s,
|
|
972
|
+
n
|
|
973
|
+
], reason);
|
|
974
|
+
if (s !== void 0) return unsafeCoerce([d, s], reason);
|
|
975
|
+
if (n !== void 0) return unsafeCoerce([d, n], reason);
|
|
976
|
+
return unsafeCoerce([d], reason);
|
|
977
|
+
}));
|
|
978
|
+
} };
|
|
979
|
+
//#endregion
|
|
980
|
+
//#region src/index.ts
|
|
981
|
+
/**
|
|
982
|
+
* `Secret` value namespace — merges the env-contracts side (`define`)
|
|
983
|
+
* with the K8s side (`make`, `bind`, identity helpers). Importing
|
|
984
|
+
* `Secret` from `@konfig.ts/k8s` gives you the full surface:
|
|
985
|
+
*
|
|
986
|
+
* Secret.define({ name, namespace, env }) — env contract (from @konfig.ts/env)
|
|
987
|
+
* Secret.make({ name, namespace, stringData }) — K8s Secret manifest
|
|
988
|
+
* Secret.bind({ secret, backend, source }) — env-to-manifest binder
|
|
989
|
+
*/
|
|
990
|
+
const Secret = {
|
|
991
|
+
...Secret$1,
|
|
992
|
+
...Secret$2,
|
|
993
|
+
bind: bindSecret
|
|
994
|
+
};
|
|
995
|
+
/**
|
|
996
|
+
* `Environment` value namespace — merges `define` (env-contracts) with
|
|
997
|
+
* `bind` + `runtime` (K8s). The same `Environment` symbol carries the
|
|
998
|
+
* declaration, the manifest binder, and the runtime decoder.
|
|
999
|
+
*/
|
|
1000
|
+
const Environment = {
|
|
1001
|
+
...Environment$1,
|
|
1002
|
+
bind: bindEnvironment,
|
|
1003
|
+
runtime
|
|
1004
|
+
};
|
|
1005
|
+
//#endregion
|
|
1006
|
+
export { BackendSourceMissing, ClusterRole, ClusterRoleBinding, ConfigMap, ConfigMapRef, Container, CronJob, Deployment, EnvVar, Environment, Ingress, Job, k8s_types_exports as K8s, Namespace, NativeSecret, NetworkPolicy, PersistentVolume, PersistentVolumeClaim, Pod, PodSet, Port, PvcRef, Role, RoleBinding, Secret, SecretRef, Selector, Service, ServiceAccount, ServiceAccountRef, StatefulSet, Volume, workloadHelpers_exports as Workload, hashSecretValues };
|
|
1007
|
+
|
|
1008
|
+
//# sourceMappingURL=index.mjs.map
|