src/config.bend
bend-kube-runtime@0.2.0.0 · 375 lines · 55 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/utf8.bend as U
4import bend-kube-runtime@0.2.0.0/src/base64.bend as B
5
6
7
8
9
10
11
12
13
14
15
16
17
18type Src is Data:
19 Inline{text: String}
20 FromFile{path: String}
21 FromExec{cmd: String, args: List<&2, String>}
22 NoSrc{}
23
24
25
26type Config is Data:
27 Config{host: String, port: U32, base: String, ca: Src, cert: Src, key: Src, token: Src,
28 namespace: String, pool: Maybe<&2, Chan(Socket)>}
29
30def Config.host(c: Config) -> String:
31 match c:
32 case Config{h, p, b, ca, cert, key, tok, ns, pl}:
33 h
34
35def Config.namespace(c: Config) -> String:
36 match c:
37 case Config{h, p, b, ca, cert, key, tok, ns, pl}:
38 ns
39
40
41
42
43type Url is Data:
44 Url{host: String, port: U32, base: String}
45
46def Url.drop_scheme.go(s: String, tls: Bool) -> String:
47 match tls:
48 case True{}:
49 String.drop(s, 8n)
50 case False{}:
51 s
52
53def Url.drop_scheme(+s: String) -> String:
54 Url.drop_scheme.go(s, String.starts_with(s, "https://"))
55
56
57def Url.split_path(s: String, acc: String) -> String & String:
58 match s:
59 case SNil{}:
60 (String.reverse(acc), "")
61 case SCon{'/', t}:
62 (String.reverse(acc), SCon{'/', t})
63 case SCon{c, t}:
64 Url.split_path(t, SCon{c, acc})
65
66def Url.port(p: String, +dflt: U32) -> U32:
67 Maybe.default(&2, U32, U32.read(p), dflt)
68
69def Url.host_port.v6(parts: List<&2, String>, +dflt: U32) -> String & U32:
70 match parts:
71 case Con{h, Con{p, rest}}:
72 (h, Url.port(String.drop(p, 1n), dflt))
73 case Con{h, Nil{}}:
74 (h, dflt)
75 case Nil{}:
76 ("", dflt)
77
78def Url.host_port.v4(parts: List<&2, String>, +dflt: U32) -> String & U32:
79 match parts:
80 case Con{h, Con{p, rest}}:
81 (h, Url.port(p, dflt))
82 case Con{h, Nil{}}:
83 (h, dflt)
84 case Nil{}:
85 ("", dflt)
86
87
88
89def Url.host_port(a: String, +dflt: U32) -> String & U32:
90 match a:
91 case SCon{'[', t}:
92 Url.host_port.v6(String.split(t, ']'), dflt)
93 case +x:
94 Url.host_port.v4(String.split(x, ':'), dflt)
95
96def Url.trim_slash.go(s: String) -> String:
97 match s:
98 case SCon{'/', t}:
99 t
100 case x:
101 x
102
103def Url.trim_slash(s: String) -> String:
104 String.reverse(Url.trim_slash.go(String.reverse(s)))
105
106def Url.parse.fin(hp: String & U32, base: String) -> Url:
107 match hp:
108 case (h, p):
109 Url{h, p, base}
110
111def Url.parse.go(ap: String & String) -> Url:
112 match ap:
113 case (a, p):
114 Url.parse.fin(Url.host_port(a, 443), Url.trim_slash(p))
115
116def Url.parse(s: String) -> Url:
117 Url.parse.go(Url.split_path(Url.drop_scheme(s), ""))
118
119def Kube.named.if(+x: J.Json, +field: String, rest: J.Json, hit: Bool) -> J.Json:
120 match hit:
121 case True{}:
122 J.Json.get(x, field)
123 case False{}:
124 rest
125
126
127
128
129
130
131def Kube.named(xs: List<&2, J.Json>, +name: String, +field: String) -> J.Json:
132 match xs:
133 case Nil{}:
134 J.JNull{}
135 case Con{+x, t}:
136 Kube.named.if(x, field, Kube.named(t, name, field),
137 String.eq(J.Json.str_or(J.Json.get(x, "name"), ""), name))
138
139def Kube.src.b64(bs: Maybe<&2, List<&2, U32>>) -> Src:
140 match bs:
141 case Some{b}:
142 Inline{U.Utf8.decode_all(b)}
143 case None{}:
144 NoSrc{}
145
146def Kube.src.go(data: J.Json, path: J.Json) -> Src:
147 match data path:
148 case J.JStr{d} _:
149 Kube.src.b64(B.B64.decode(d))
150 case _ J.JStr{p}:
151 FromFile{p}
152 case _ _:
153 NoSrc{}
154
155
156def Kube.src(+o: J.Json, +name: String) -> Src:
157 Kube.src.go(J.Json.get(o, name ++ "-data"), J.Json.get(o, name))
158
159def Kube.strs(xs: List<&2, J.Json>) -> List<&2, String>:
160 match xs:
161 case Nil{}:
162 Nil{}
163 case Con{J.JStr{x}, t}:
164 x <> Kube.strs(t)
165 case Con{x, t}:
166 Kube.strs(t)
167
168
169
170def Kube.token.exec(c: J.Json, env: List<&2, String>, args: List<&2, String>) -> Src:
171 match c:
172 case J.JStr{cmd}:
173 FromExec{"env", List.append(&2, String, env,
174 ("KUBERNETES_EXEC_INFO={\"apiVersion\":\"client.authentication.k8s.io/v1\",\"kind\":\"ExecCredential\",\"spec\":{\"interactive\":false}}")
175 <> cmd <> args)}
176 case _:
177 NoSrc{}
178
179
180def Kube.envs(xs: List<&2, J.Json>) -> List<&2, String>:
181 match xs:
182 case Nil{}:
183 Nil{}
184 case Con{+x, t}:
185 (J.Json.str_or(J.Json.get(x, "name"), "") ++ "=" ++ J.Json.str_or(J.Json.get(x, "value"), ""))
186 <> Kube.envs(t)
187
188def Kube.token.go(t: J.Json, f: J.Json, +e: J.Json) -> Src:
189 match t f:
190 case J.JStr{s} _:
191 Inline{s}
192 case _ J.JStr{p}:
193 FromFile{p}
194 case _ _:
195 Kube.token.exec(J.Json.get(e, "command"), Kube.envs(J.Json.items(J.Json.get(e, "env"))),
196 Kube.strs(J.Json.items(J.Json.get(e, "args"))))
197
198def Kube.token(+u: J.Json) -> Src:
199 Kube.token.go(J.Json.get(u, "token"), J.Json.get(u, "tokenFile"), J.Json.get(u, "exec"))
200
201def Kube.config.pick.go(ctx: String, cur: String, empty: Bool) -> String:
202 match empty:
203 case True{}:
204 cur
205 case False{}:
206 ctx
207
208def Kube.config.pick(+ctx: String, cur: String) -> String:
209 Kube.config.pick.go(ctx, cur, String.is_empty(ctx))
210
211def Kube.config.fin(u: Url, ca: Src, cert: Src, key: Src, tok: Src, ns: String) ->
212 Result<&2, &2, String, Config>:
213 match u:
214 case Url{h, p, b}:
215 Done{Config{h, p, b, ca, cert, key, tok, ns, None{}}}
216
217def Kube.config.server(s: J.Json, ca: Src, cert: Src, key: Src, tok: Src, ns: String) ->
218 Result<&2, &2, String, Config>:
219 match s:
220 case J.JStr{url}:
221 Kube.config.fin(Url.parse(url), ca, cert, key, tok, ns)
222 case _:
223 Fail{"kubeconfig: the cluster has no server"}
224
225def Kube.config.build(+cl: J.Json, +u: J.Json, ns: String, name: String, missing: Bool) ->
226 Result<&2, &2, String, Config>:
227 match missing:
228 case True{}:
229 Fail{"kubeconfig: no context named " ++ name}
230 case False{}:
231 Kube.config.server(J.Json.get(cl, "server"), Kube.src(cl, "certificate-authority"),
232 Kube.src(u, "client-certificate"), Kube.src(u, "client-key"), Kube.token(u), ns)
233
234def Kube.config.with(+kc: J.Json, name: String, +c: J.Json) -> Result<&2, &2, String, Config>:
235 Kube.config.build(
236 Kube.named(J.Json.items(J.Json.get(kc, "clusters")),
237 J.Json.str_or(J.Json.get(c, "cluster"), ""), "cluster"),
238 Kube.named(J.Json.items(J.Json.get(kc, "users")),
239 J.Json.str_or(J.Json.get(c, "user"), ""), "user"),
240 J.Json.str_or(J.Json.get(c, "namespace"), "default"), name, J.Json.is_null(c))
241
242def Kube.config.ctx(+kc: J.Json, +name: String) -> Result<&2, &2, String, Config>:
243 Kube.config.with(kc, name, Kube.named(J.Json.items(J.Json.get(kc, "contexts")), name, "context"))
244
245
246
247def Kube.config(+kc: J.Json, ctx: String) -> Result<&2, &2, String, Config>:
248 Kube.config.ctx(kc, Kube.config.pick(ctx, J.Json.str_or(J.Json.get(kc, "current-context"), "")))
249
250
251
252
253def InCluster.dir() -> String:
254 "/var/run/secrets/kubernetes.io/serviceaccount"
255
256
257
258def InCluster.config(host: String, port: String, ns: String) -> Config:
259 Config{host, Url.port(port, 443), "", FromFile{InCluster.dir() ++ "/ca.crt"}, NoSrc{}, NoSrc{},
260 FromFile{InCluster.dir() ++ "/token"}, String.trim(ns), None{}}
261
262
263
264
265def Kube.names(xs: List<&2, J.Json>) -> List<&2, String>:
266 match xs:
267 case Nil{}:
268 Nil{}
269 case Con{+x, t}:
270 J.Json.str_or(J.Json.get(x, "name"), "") <> Kube.names(t)
271
272def Kube.merge.new.put(x: J.Json, rest: List<&2, J.Json>, dup: Bool) -> List<&2, J.Json>:
273 match dup:
274 case True{}:
275 rest
276 case False{}:
277 x <> rest
278
279def Kube.merge.new(b: List<&2, J.Json>, +seen: List<&2, String>) -> List<&2, J.Json>:
280 match b:
281 case Nil{}:
282 Nil{}
283 case Con{+x, t}:
284 Kube.merge.new.put(x, Kube.merge.new(t, seen),
285 List.contains(~String, ~String.eq, seen, J.Json.str_or(J.Json.get(x, "name"), "")))
286
287def Kube.merge.list(+a: List<&2, J.Json>, b: List<&2, J.Json>) -> List<&2, J.Json>:
288 List.append(&2, J.Json, a, Kube.merge.new(b, Kube.names(a)))
289
290def Kube.merge.field(+a: J.Json, +b: J.Json, +f: String) -> J.Json:
291 J.Json.arr(Kube.merge.list(J.Json.items(J.Json.get(a, f)), J.Json.items(J.Json.get(b, f))))
292
293def Kube.merge.cur(a: String, b: String) -> String:
294 Kube.config.pick(a, b)
295
296
297
298
299def Kube.merge(+a: J.Json, +b: J.Json) -> J.Json:
300 J.Json.obj([J.KV{"clusters", Kube.merge.field(a, b, "clusters")},
301 J.KV{"contexts", Kube.merge.field(a, b, "contexts")},
302 J.KV{"users", Kube.merge.field(a, b, "users")},
303 J.KV{"current-context", J.JStr{Kube.merge.cur(J.Json.str_or(J.Json.get(a, "current-context"), ""),
304 J.Json.str_or(J.Json.get(b, "current-context"), ""))}}])
305
306def Kube.rebase.str(dir: String, s: String, abs: Bool) -> String:
307 match abs:
308 case True{}:
309 s
310 case False{}:
311 dir ++ "/" ++ s
312
313
314def Kube.rebase.path(+dir: String, p: J.Json) -> J.Json:
315 match p:
316 case J.JStr{+s}:
317 J.JStr{Kube.rebase.str(dir, s, String.starts_with(s, "/"))}
318 case x:
319 x
320
321def Kube.rebase.key(+o: J.Json, +dir: String, +k: String, missing: Bool) -> J.Json:
322 match missing:
323 case True{}:
324 o
325 case False{}:
326 J.Json.set(o, k, Kube.rebase.path(dir, J.Json.get(o, k)))
327
328def Kube.rebase.keys(ks: List<&2, String>, +o: J.Json, +dir: String) -> J.Json:
329 match ks:
330 case Nil{}:
331 o
332 case Con{+k, t}:
333 Kube.rebase.keys(t, Kube.rebase.key(o, dir, k, J.Json.is_null(J.Json.get(o, k))), dir)
334
335def Kube.rebase.items(xs: List<&2, J.Json>, +dir: String, +inner: String, +ks: List<&2, String>) ->
336 List<&2, J.Json>:
337 match xs:
338 case Nil{}:
339 Nil{}
340 case Con{+x, t}:
341 J.Json.set(x, inner, Kube.rebase.keys(ks, J.Json.get(x, inner), dir))
342 <> Kube.rebase.items(t, dir, inner, ks)
343
344
345def Kube.rebase(+kc: J.Json, +dir: String) -> J.Json:
346 J.Json.set(J.Json.set(kc, "clusters",
347 J.Json.arr(Kube.rebase.items(J.Json.items(J.Json.get(kc, "clusters")), dir, "cluster",
348 ["certificate-authority"]))),
349 "users", J.Json.arr(Kube.rebase.items(J.Json.items(J.Json.get(kc, "users")), dir, "user",
350 ["client-certificate", "client-key", "tokenFile"])))
351
352def Kube.dir.fin(d: String, bare: Bool) -> String:
353 match bare:
354 case True{}:
355 "."
356 case False{}:
357 d
358
359def Kube.dir.go(+parts: List<&2, String>) -> String:
360 Kube.dir.fin(String.join(List.take(&2, String, parts, Nat.sub(List.length(&2, String, parts), 1n)), "/"),
361 Nat.is_le(List.length(&2, String, parts), 1n))
362
363
364def Kube.dir(+p: String) -> String:
365 Kube.dir.go(String.split(p, '/'))
366
367def Config.with_pool(c: Config, pool: Chan(Socket)) -> Config:
368 match c:
369 case Config{h, p, b, ca, cert, key, tok, ns, pl}:
370 Config{h, p, b, ca, cert, key, tok, ns, Some{pool}}
371
372def Config.pool(c: Config) -> Maybe<&2, Chan(Socket)>:
373 match c:
374 case Config{h, p, b, ca, cert, key, tok, ns, pl}:
375 pl