src/core.bend
bend-kube-runtime@0.2.0.0 · 408 lines · 81 definitions
1import Base
2import bend-kube-runtime@0.2.0.0/src/json.bend as J
3import bend-kube-runtime@0.2.0.0/src/http.bend as H
4
5
6
7
8
9
10
11
12
13
14type Res is Data:
15 Res{group: String, version: String, kind: String, plural: String, namespaced: Bool}
16
17
18type Scope is Data:
19 AllNs{}
20 InNs{ns: String}
21 Cluster{}
22
23type Api is Data:
24 Api{res: Res, scope: Scope}
25
26type ListParams is Data:
27 ListParams{label: String, field: String, limit: U32, cont: String, rv: String}
28
29type WatchParams is Data:
30 WatchParams{label: String, field: String, timeout: U32, bookmarks: Bool}
31
32
33type PatchKind is Data:
34 PMerge{}
35 PJson{}
36 PStrategic{}
37 PApply{manager: String, force: Bool}
38
39
40
41
42def Res.core(kind: String, plural: String, ns: Bool) -> Res:
43 Res{"", "v1", kind, plural, ns}
44
45def Res.pods() -> Res:
46 Res.core("Pod", "pods", True{})
47
48def Res.config_maps() -> Res:
49 Res.core("ConfigMap", "configmaps", True{})
50
51def Res.secrets() -> Res:
52 Res.core("Secret", "secrets", True{})
53
54def Res.services() -> Res:
55 Res.core("Service", "services", True{})
56
57def Res.namespaces() -> Res:
58 Res.core("Namespace", "namespaces", False{})
59
60def Res.events() -> Res:
61 Res{"events.k8s.io", "v1", "Event", "events", True{}}
62
63def Res.deployments() -> Res:
64 Res{"apps", "v1", "Deployment", "deployments", True{}}
65
66def Res.leases() -> Res:
67 Res{"coordination.k8s.io", "v1", "Lease", "leases", True{}}
68
69def Res.crds() -> Res:
70 Res{"apiextensions.k8s.io", "v1", "CustomResourceDefinition", "customresourcedefinitions", False{}}
71
72def Res.api_version.go(g: String, v: String, core: Bool) -> String:
73 match core:
74 case True{}:
75 v
76 case False{}:
77 g ++ "/" ++ v
78
79def Res.api_version(r: Res) -> String:
80 match r:
81 case Res{+g, v, k, p, n}:
82 Res.api_version.go(g, v, String.is_empty(g))
83
84
85
86
87def Api.prefix(g: String, v: String, core: Bool) -> String:
88 match core:
89 case True{}:
90 "/api/" ++ v
91 case False{}:
92 "/apis/" ++ g ++ "/" ++ v
93
94def Api.ns_part(s: Scope, namespaced: Bool) -> String:
95 match s namespaced:
96 case InNs{ns} True{}:
97 "/namespaces/" ++ H.Http.encode(ns)
98 case _ _:
99 ""
100
101
102def Api.path(a: Api) -> String:
103 match a:
104 case Api{Res{+g, v, k, p, n}, s}:
105 Api.prefix(g, v, String.is_empty(g)) ++ Api.ns_part(s, n) ++ "/" ++ p
106
107def Api.item(a: Api, name: String) -> String:
108 Api.path(a) ++ "/" ++ H.Http.encode(name)
109
110
111
112
113type Q is Data:
114 Q{key: String, val: String}
115
116def Query.go.put(k: String, v: String, rest: List<&2, String>, empty: Bool) -> List<&2, String>:
117 match empty:
118 case True{}:
119 rest
120 case False{}:
121 (H.Http.encode(k) ++ "=" ++ H.Http.encode(v)) <> rest
122
123
124def Query.go(qs: List<&2, Q>) -> List<&2, String>:
125 match qs:
126 case Nil{}:
127 Nil{}
128 case Con{Q{k, +v}, t}:
129 Query.go.put(k, v, Query.go(t), String.is_empty(v))
130
131def Query.render.go(ps: List<&2, String>) -> String:
132 match ps:
133 case Nil{}:
134 ""
135 case +xs:
136 "?" ++ String.join(xs, "&")
137
138def Query.render(qs: List<&2, Q>) -> String:
139 Query.render.go(Query.go(qs))
140
141def Query.u32.go(+n: U32, zero: Bool) -> String:
142 match zero:
143 case True{}:
144 ""
145 case False{}:
146 U32.show(n)
147
148def Query.u32(+n: U32) -> String:
149 Query.u32.go(n, U32.is_eq(n, 0))
150
151def Query.bool(b: Bool) -> String:
152 match b:
153 case True{}:
154 "true"
155 case False{}:
156 ""
157
158def ListParams.default() -> ListParams:
159 ListParams{"", "", 500, "", ""}
160
161def WatchParams.default() -> WatchParams:
162 WatchParams{"", "", 290, True{}}
163
164def ListParams.query(lp: ListParams) -> List<&2, Q>:
165 match lp:
166 case ListParams{l, f, n, c, rv}:
167 [Q{"labelSelector", l}, Q{"fieldSelector", f}, Q{"limit", Query.u32(n)},
168 Q{"continue", c}, Q{"resourceVersion", rv}]
169
170def WatchParams.query(wp: WatchParams, rv: String) -> List<&2, Q>:
171 match wp:
172 case WatchParams{l, f, t, b}:
173 [Q{"watch", "true"}, Q{"labelSelector", l}, Q{"fieldSelector", f},
174 Q{"timeoutSeconds", Query.u32(t)}, Q{"allowWatchBookmarks", Query.bool(b)},
175 Q{"resourceVersion", rv}]
176
177
178
179
180def Req.json_headers() -> List<&2, H.Header>:
181 [H.Header{"Accept", "application/json"}]
182
183def Req.body_headers(ct: String) -> List<&2, H.Header>:
184 [H.Header{"Accept", "application/json"}, H.Header{"Content-Type", ct}]
185
186def Req.get(path: String) -> H.Req:
187 H.Req{"GET", path, Req.json_headers(), ""}
188
189def Req.list(a: Api, lp: ListParams) -> H.Req:
190 Req.get(Api.path(a) ++ Query.render(ListParams.query(lp)))
191
192def Req.watch(a: Api, wp: WatchParams, rv: String) -> H.Req:
193 Req.get(Api.path(a) ++ Query.render(WatchParams.query(wp, rv)))
194
195def Req.read(a: Api, name: String) -> H.Req:
196 Req.get(Api.item(a, name))
197
198def Req.create(a: Api, obj: J.Json) -> H.Req:
199 H.Req{"POST", Api.path(a), Req.body_headers("application/json"), J.Json.show(obj)}
200
201def Req.replace(a: Api, name: String, obj: J.Json) -> H.Req:
202 H.Req{"PUT", Api.item(a, name), Req.body_headers("application/json"), J.Json.show(obj)}
203
204def Req.delete(a: Api, name: String) -> H.Req:
205 H.Req{"DELETE", Api.item(a, name), Req.json_headers(), ""}
206
207def PatchKind.content_type(p: PatchKind) -> String:
208 match p:
209 case PMerge{}:
210 "application/merge-patch+json"
211 case PJson{}:
212 "application/json-patch+json"
213 case PStrategic{}:
214 "application/strategic-merge-patch+json"
215 case PApply{m, f}:
216 "application/apply-patch+yaml"
217
218def PatchKind.query(p: PatchKind) -> List<&2, Q>:
219 match p:
220 case PApply{m, f}:
221 [Q{"fieldManager", m}, Q{"force", Query.bool(f)}]
222 case _:
223 Nil{}
224
225def Req.sub.go(s: String, empty: Bool) -> String:
226 match empty:
227 case True{}:
228 ""
229 case False{}:
230 "/" ++ s
231
232def Req.sub(+s: String) -> String:
233 Req.sub.go(s, String.is_empty(s))
234
235
236
237def Req.patch(a: Api, name: String, +sub: String, +p: PatchKind, body: J.Json) -> H.Req:
238 H.Req{"PATCH", Api.item(a, name) ++ Req.sub(sub) ++ Query.render(PatchKind.query(p)),
239 Req.body_headers(PatchKind.content_type(p)), J.Json.show(body)}
240
241def Req.replace_status(a: Api, name: String, obj: J.Json) -> H.Req:
242 H.Req{"PUT", Api.item(a, name) ++ "/status", Req.body_headers("application/json"),
243 J.Json.show(obj)}
244
245
246
247
248def Obj.meta(o: J.Json, field: String) -> J.Json:
249 J.Json.get(J.Json.get(o, "metadata"), field)
250
251def Obj.name(o: J.Json) -> String:
252 J.Json.str_or(Obj.meta(o, "name"), "")
253
254def Obj.namespace(o: J.Json) -> String:
255 J.Json.str_or(Obj.meta(o, "namespace"), "")
256
257def Obj.uid(o: J.Json) -> String:
258 J.Json.str_or(Obj.meta(o, "uid"), "")
259
260def Obj.rv(o: J.Json) -> String:
261 J.Json.str_or(Obj.meta(o, "resourceVersion"), "")
262
263def Obj.num_text(j: J.Json) -> String:
264 match j:
265 case J.JNum{s}:
266 s
267 case _:
268 ""
269
270def Obj.generation(o: J.Json) -> String:
271 Obj.num_text(Obj.meta(o, "generation"))
272
273def Obj.is_deleting(o: J.Json) -> Bool:
274 Bool.not(J.Json.is_null(Obj.meta(o, "deletionTimestamp")))
275
276def Obj.strs(xs: List<&2, J.Json>) -> List<&2, String>:
277 match xs:
278 case Nil{}:
279 Nil{}
280 case Con{J.JStr{s}, t}:
281 s <> Obj.strs(t)
282 case Con{x, t}:
283 Obj.strs(t)
284
285def Obj.finalizers(o: J.Json) -> List<&2, String>:
286 Obj.strs(J.Json.items(Obj.meta(o, "finalizers")))
287
288def Obj.label(o: J.Json, key: String) -> Maybe<&2, String>:
289 J.Json.as_str(J.Json.get(Obj.meta(o, "labels"), key))
290
291def Obj.key.go(ns: String, name: String, cluster: Bool) -> String:
292 match cluster:
293 case True{}:
294 name
295 case False{}:
296 ns ++ "/" ++ name
297
298def Obj.key.of(+ns: String, name: String) -> String:
299 Obj.key.go(ns, name, String.is_empty(ns))
300
301
302
303def Obj.key(+o: J.Json) -> String:
304 Obj.key.of(Obj.namespace(o), Obj.name(o))
305
306
307type ObjRef is Data:
308 ObjRef{ns: String, name: String}
309
310def ObjRef.key(r: ObjRef) -> String:
311 match r:
312 case ObjRef{ns, name}:
313 Obj.key.of(ns, name)
314
315def ObjRef.of(+o: J.Json) -> ObjRef:
316 ObjRef{Obj.namespace(o), Obj.name(o)}
317
318def Obj.owners.put(ns: String, name: String, hit: Bool, rest: List<&2, ObjRef>) ->
319 List<&2, ObjRef>:
320 match hit:
321 case True{}:
322 ObjRef{ns, name} <> rest
323 case False{}:
324 rest
325
326def Obj.owners.go(xs: List<&2, J.Json>, +ns: String, +kind: String) -> List<&2, ObjRef>:
327 match xs:
328 case Nil{}:
329 Nil{}
330 case Con{+r, t}:
331 Obj.owners.put(ns, J.Json.str_or(J.Json.get(r, "name"), ""),
332 String.eq(J.Json.str_or(J.Json.get(r, "kind"), ""), kind), Obj.owners.go(t, ns, kind))
333
334
335
336def Obj.owners(+o: J.Json, +kind: String) -> List<&2, ObjRef>:
337 Obj.owners.go(J.Json.items(Obj.meta(o, "ownerReferences")), Obj.namespace(o), kind)
338
339def Res.kind(r: Res) -> String:
340 match r:
341 case Res{g, v, k, p, n}:
342 k
343
344
345def Obj.owner_ref(+o: J.Json, +res: Res) -> J.Json:
346 J.Json.obj([J.KV{"apiVersion", J.JStr{Res.api_version(res)}}, J.KV{"kind", J.JStr{Res.kind(res)}},
347 J.KV{"name", J.JStr{Obj.name(o)}}, J.KV{"uid", J.JStr{Obj.uid(o)}},
348 J.KV{"controller", J.JBool{True{}}}, J.KV{"blockOwnerDeletion", J.JBool{True{}}}])
349
350
351
352
353type Page is Data:
354 Page{items: List<&2, J.Json>, rv: String, cont: String}
355
356def Page.of(+j: J.Json) -> Page:
357 Page{J.Json.items(J.Json.get(j, "items")),
358 J.Json.str_or(J.Json.at(j, ["metadata", "resourceVersion"]), ""),
359 J.Json.str_or(J.Json.at(j, ["metadata", "continue"]), "")}
360
361
362type Status is Data:
363 Status{code: U32, reason: String, message: String}
364
365def Status.of(+j: J.Json) -> Status:
366 Status{Maybe.default(&2, U32, J.Json.as_u32(J.Json.get(j, "code")), 0),
367 J.Json.str_or(J.Json.get(j, "reason"), ""), J.Json.str_or(J.Json.get(j, "message"), "")}
368
369def Status.show(s: Status) -> String:
370 match s:
371 case Status{c, r, m}:
372 U32.show(c) ++ " " ++ r ++ ": " ++ m
373
374type WEvent is Data:
375 WAdded{obj: J.Json}
376 WModified{obj: J.Json}
377 WDeleted{obj: J.Json}
378 WBookmark{rv: String}
379 WError{status: Status}
380 WBad{msg: String}
381
382def WEvent.of.go(t: String, +o: J.Json) -> WEvent:
383 match t:
384 case "ADDED":
385 WAdded{o}
386 case "MODIFIED":
387 WModified{o}
388 case "DELETED":
389 WDeleted{o}
390 case "BOOKMARK":
391 WBookmark{Obj.rv(o)}
392 case "ERROR":
393 WError{Status.of(o)}
394 case _:
395 WBad{"unknown watch event type"}
396
397def WEvent.of(+j: J.Json) -> WEvent:
398 WEvent.of.go(J.Json.str_or(J.Json.get(j, "type"), ""), J.Json.get(j, "object"))
399
400def WEvent.parse.go(r: Result<&2, &2, String, J.Json>) -> WEvent:
401 match r:
402 case Done{j}:
403 WEvent.of(j)
404 case Fail{e}:
405 WBad{e}
406
407def WEvent.parse(line: String) -> WEvent:
408 WEvent.parse.go(J.Json.parse(line))