blob: 1b7ac59cdf2eb5d7c206cce1a2ddbf12a278e6ac [file] [log] [blame]
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +02001// Generic library of Kubernetes objects (https://github.com/bitnami-labs/kube-libsonnet)
2//
3// Objects in this file follow the regular Kubernetes API object
4// schema with two exceptions:
5//
6// ## Optional helpers
7//
8// A few objects have defaults or additional "helper" hidden
9// (double-colon) fields that will help with common situations. For
10// example, `Service.target_pod` generates suitable `selector` and
11// `ports` blocks for the common case of a single-pod/single-port
12// service. If for some reason you don't want the helper, just
13// provide explicit values for the regular Kubernetes fields that the
14// helper *would* have generated, and the helper logic will be
15// ignored.
16//
17// ## The Underscore Convention:
18//
19// Various constructs in the Kubernetes API use JSON arrays to
20// represent unordered sets or named key/value maps. This is
21// particularly annoying with jsonnet since we want to use jsonnet's
22// powerful object merge operation with these constructs.
23//
24// To combat this, this library attempts to provide more "jsonnet
25// native" variants of these arrays in alternative hidden fields that
26// end with an underscore. For example, the `env_` block in
27// `Container`:
28// ```
29// kube.Container("foo") {
30// env_: { FOO: "bar" },
31// }
32// ```
33// ... produces the expected `container.env` JSON array:
34// ```
35// {
36// "env": [
37// { "name": "FOO", "value": "bar" }
38// ]
39// }
40// ```
41//
42// If you are confused by the underscore versions, or don't want them
43// in your situation then just ignore them and set the regular
44// non-underscore field as usual.
45//
46//
47// ## TODO
48//
49// TODO: Expand this to include all API objects.
50//
51// Should probably fill out all the defaults here too, so jsonnet can
52// reference them. In addition, jsonnet validation is more useful
53// (client-side, and gives better line information).
54
55{
Bartosz Stebel3a15b832021-06-16 19:14:50 +020056 // In case you may want/need to skip assertions for speed reasons (rather big configmaps/etc),
57 // load the library with e.g.
58 // local kube = (import "lib/kube.libsonnet") { _assert:: false };
59 _assert:: true,
60
Rafał Hirszccda3332020-05-16 21:01:03 +020061 // resource contructors will use kinds/versions/fields compatible at least with version:
62 minKubeVersion: {
63 major: 1,
Bartosz Stebel3a15b832021-06-16 19:14:50 +020064 minor: 14,
Rafał Hirszccda3332020-05-16 21:01:03 +020065 version: "%s.%s" % [self.major, self.minor],
66 },
67
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +020068 // Returns array of values from given object. Does not include hidden fields.
69 objectValues(o):: [o[field] for field in std.objectFields(o)],
70
71 // Returns array of [key, value] pairs from given object. Does not include hidden fields.
72 objectItems(o):: [[k, o[k]] for k in std.objectFields(o)],
73
74 // Replace all occurrences of `_` with `-`.
75 hyphenate(s):: std.join("-", std.split(s, "_")),
76
77 // Convert an octal (as a string) to number,
78 parseOctal(s):: (
79 local len = std.length(s);
80 local leading = std.substr(s, 0, len - 1);
81 local last = std.parseInt(std.substr(s, len - 1, 1));
Bartosz Stebel3a15b832021-06-16 19:14:50 +020082 assert (!$._assert) || last < 8 : "found '%s' digit >= 8" % [last];
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +020083 last + (if len > 1 then 8 * $.parseOctal(leading) else 0)
84 ),
85
86 // Convert {foo: {a: b}} to [{name: foo, a: b}]
87 mapToNamedList(o):: [{ name: $.hyphenate(n) } + o[n] for n in std.objectFields(o)],
88
89 // Return object containing only these fields elements
90 filterMapByFields(o, fields): { [field]: o[field] for field in std.setInter(std.objectFields(o), fields) },
91
92 // Convert from SI unit suffixes to regular number
93 siToNum(n):: (
94 local convert =
95 if std.endsWith(n, "m") then [1, 0.001]
96 else if std.endsWith(n, "K") then [1, 1e3]
97 else if std.endsWith(n, "M") then [1, 1e6]
98 else if std.endsWith(n, "G") then [1, 1e9]
99 else if std.endsWith(n, "T") then [1, 1e12]
100 else if std.endsWith(n, "P") then [1, 1e15]
101 else if std.endsWith(n, "E") then [1, 1e18]
102 else if std.endsWith(n, "Ki") then [2, std.pow(2, 10)]
103 else if std.endsWith(n, "Mi") then [2, std.pow(2, 20)]
104 else if std.endsWith(n, "Gi") then [2, std.pow(2, 30)]
105 else if std.endsWith(n, "Ti") then [2, std.pow(2, 40)]
106 else if std.endsWith(n, "Pi") then [2, std.pow(2, 50)]
107 else if std.endsWith(n, "Ei") then [2, std.pow(2, 60)]
108 else error "Unknown numerical suffix in " + n;
109 local n_len = std.length(n);
110 std.parseInt(std.substr(n, 0, n_len - convert[0])) * convert[1]
111 ),
112
113 local remap(v, start, end, newstart) =
114 if v >= start && v <= end then v - start + newstart else v,
115 local remapChar(c, start, end, newstart) =
116 std.char(remap(
117 std.codepoint(c), std.codepoint(start), std.codepoint(end), std.codepoint(newstart)
118 )),
119 toLower(s):: (
120 std.join("", [remapChar(c, "A", "Z", "a") for c in std.stringChars(s)])
121 ),
122 toUpper(s):: (
123 std.join("", [remapChar(c, "a", "z", "A") for c in std.stringChars(s)])
124 ),
125
Rafał Hirszccda3332020-05-16 21:01:03 +0200126 boolXor(x, y):: ((if x then 1 else 0) + (if y then 1 else 0) == 1),
127
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200128 _Object(apiVersion, kind, name):: {
129 local this = self,
130 apiVersion: apiVersion,
131 kind: kind,
132 metadata: {
133 name: name,
134 labels: { name: std.join("-", std.split(this.metadata.name, ":")) },
135 annotations: {},
136 },
137 },
138
139 List(): {
140 apiVersion: "v1",
141 kind: "List",
142 items_:: {},
143 items: $.objectValues(self.items_),
144 },
145
146 Namespace(name): $._Object("v1", "Namespace", name) {
147 },
148
149 Endpoints(name): $._Object("v1", "Endpoints", name) {
150 Ip(addr):: { ip: addr },
151 Port(p):: { port: p },
152
153 subsets: [],
154 },
155
156 Service(name): $._Object("v1", "Service", name) {
157 local service = self,
158
159 target_pod:: error "service target_pod required",
160 port:: self.target_pod.spec.containers[0].ports[0].containerPort,
161
162 // Helpers that format host:port in various ways
163 host:: "%s.%s.svc" % [self.metadata.name, self.metadata.namespace],
164 host_colon_port:: "%s:%s" % [self.host, self.spec.ports[0].port],
165 http_url:: "http://%s/" % self.host_colon_port,
166 proxy_urlpath:: "/api/v1/proxy/namespaces/%s/services/%s/" % [
167 self.metadata.namespace,
168 self.metadata.name,
169 ],
170 // Useful in Ingress rules
171 name_port:: {
172 serviceName: service.metadata.name,
173 servicePort: service.spec.ports[0].port,
174 },
175
176 spec: {
177 selector: service.target_pod.metadata.labels,
178 ports: [
179 {
180 port: service.port,
Rafał Hirszccda3332020-05-16 21:01:03 +0200181 name: service.target_pod.spec.containers[0].ports[0].name,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200182 targetPort: service.target_pod.spec.containers[0].ports[0].containerPort,
183 },
184 ],
185 type: "ClusterIP",
186 },
187 },
188
189 PersistentVolume(name): $._Object("v1", "PersistentVolume", name) {
190 spec: {},
191 },
192
193 // TODO: This is a terrible name
194 PersistentVolumeClaimVolume(pvc): {
195 persistentVolumeClaim: { claimName: pvc.metadata.name },
196 },
197
198 StorageClass(name): $._Object("storage.k8s.io/v1beta1", "StorageClass", name) {
199 provisioner: error "provisioner required",
200 },
201
202 PersistentVolumeClaim(name): $._Object("v1", "PersistentVolumeClaim", name) {
203 local pvc = self,
204
205 storageClass:: null,
206 storage:: error "storage required",
207
208 metadata+: if pvc.storageClass != null then {
209 annotations+: {
210 "volume.beta.kubernetes.io/storage-class": pvc.storageClass,
211 },
212 } else {},
213
214 spec: {
215 resources: {
216 requests: {
217 storage: pvc.storage,
218 },
219 },
220 accessModes: ["ReadWriteOnce"],
Rafał Hirszccda3332020-05-16 21:01:03 +0200221 [if pvc.storageClass != null then "storageClassName"]: pvc.storageClass,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200222 },
223 },
224
225 Container(name): {
226 name: name,
227 image: error "container image value required",
228 imagePullPolicy: if std.endsWith(self.image, ":latest") then "Always" else "IfNotPresent",
229
230 envList(map):: [
Rafał Hirszccda3332020-05-16 21:01:03 +0200231 if std.type(map[x]) == "object"
232 then {
233 name: x,
234 valueFrom: map[x],
235 } else {
236 // Let `null` value stay as such (vs string-ified)
237 name: x,
238 value: if map[x] == null then null else std.toString(map[x]),
239 }
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200240 for x in std.objectFields(map)
241 ],
242
243 env_:: {},
244 env: self.envList(self.env_),
245
246 args_:: {},
247 args: ["--%s=%s" % kv for kv in $.objectItems(self.args_)],
248
249 ports_:: {},
250 ports: $.mapToNamedList(self.ports_),
251
252 volumeMounts_:: {},
253 volumeMounts: $.mapToNamedList(self.volumeMounts_),
254
255 stdin: false,
256 tty: false,
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200257 assert (!$._assert) || (!self.tty || self.stdin) : "tty=true requires stdin=true",
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200258 },
259
260 PodDisruptionBudget(name): $._Object("policy/v1beta1", "PodDisruptionBudget", name) {
261 local this = self,
262 target_pod:: error "target_pod required",
263 spec: {
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200264 assert (!$._assert) || $.boolXor(
Rafał Hirszccda3332020-05-16 21:01:03 +0200265 std.objectHas(self, "minAvailable"),
266 std.objectHas(self, "maxUnavailable")
267 ) : "PDB '%s': exactly one of minAvailable/maxUnavailable required" % name,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200268 selector: {
269 matchLabels: this.target_pod.metadata.labels,
270 },
271 },
272 },
273
274 Pod(name): $._Object("v1", "Pod", name) {
275 spec: $.PodSpec,
276 },
277
278 PodSpec: {
279 // The 'first' container is used in various defaults in k8s.
280 local container_names = std.objectFields(self.containers_),
281 default_container:: if std.length(container_names) > 1 then "default" else container_names[0],
282 containers_:: {},
283
284 local container_names_ordered = [self.default_container] + [n for n in container_names if n != self.default_container],
Rafał Hirszccda3332020-05-16 21:01:03 +0200285 containers: (
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200286 assert (!$._assert) || std.length(self.containers_) > 0 : "Pod must have at least one container (via containers_ map)";
Rafał Hirszccda3332020-05-16 21:01:03 +0200287 [{ name: $.hyphenate(name) } + self.containers_[name] for name in container_names_ordered if self.containers_[name] != null]
288 ),
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200289
290 // Note initContainers are inherently ordered, and using this
291 // named object will lose that ordering. If order matters, then
292 // manipulate `initContainers` directly (perhaps
293 // appending/prepending to `super.initContainers` to mix+match
294 // both approaches)
295 initContainers_:: {},
296 initContainers: [{ name: $.hyphenate(name) } + self.initContainers_[name] for name in std.objectFields(self.initContainers_) if self.initContainers_[name] != null],
297
298 volumes_:: {},
299 volumes: $.mapToNamedList(self.volumes_),
300
301 imagePullSecrets: [],
302
303 terminationGracePeriodSeconds: 30,
304
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200305 assert (!$._assert) || std.length(self.containers) > 0 : "Pod must have at least one container (via containers array)",
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200306
307 // Return an array of pod's ports numbers
308 ports(proto):: [
309 p.containerPort
310 for p in std.flattenArrays([
311 c.ports
312 for c in self.containers
313 ])
314 if (
315 (!(std.objectHas(p, "protocol")) && proto == "TCP")
316 ||
317 ((std.objectHas(p, "protocol")) && p.protocol == proto)
318 )
319 ],
320
321 },
322
323 EmptyDirVolume(): {
324 emptyDir: {},
325 },
326
327 HostPathVolume(path, type=""): {
328 hostPath: { path: path, type: type },
329 },
330
331 GitRepoVolume(repository, revision): {
332 gitRepo: {
333 repository: repository,
334
335 // "master" is possible, but should be avoided for production
336 revision: revision,
337 },
338 },
339
340 SecretVolume(secret): {
341 secret: { secretName: secret.metadata.name },
342 },
343
344 ConfigMapVolume(configmap): {
345 configMap: { name: configmap.metadata.name },
346 },
347
348 ConfigMap(name): $._Object("v1", "ConfigMap", name) {
349 data: {},
350
351 // I keep thinking data values can be any JSON type. This check
352 // will remind me that they must be strings :(
353 local nonstrings = [
354 k
355 for k in std.objectFields(self.data)
356 if std.type(self.data[k]) != "string"
357 ],
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200358 assert (!$._assert) || std.length(nonstrings) == 0 : "data contains non-string values: %s" % [nonstrings],
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200359 },
360
361 // subtype of EnvVarSource
362 ConfigMapRef(configmap, key): {
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200363 assert (!$._assert) || std.objectHas(configmap.data, key) : "ConfigMap '%s' doesn't have '%s' field in configmap.data" % [configmap.metadata.name, key],
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200364 configMapKeyRef: {
365 name: configmap.metadata.name,
366 key: key,
367 },
368 },
369
370 Secret(name): $._Object("v1", "Secret", name) {
371 local secret = self,
372
373 type: "Opaque",
374 data_:: {},
375 data: { [k]: std.base64(secret.data_[k]) for k in std.objectFields(secret.data_) },
376 },
377
378 // subtype of EnvVarSource
379 SecretKeyRef(secret, key): {
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200380 assert (!$._assert) || std.objectHas(secret.data, key) : "Secret '%s' doesn't have '%s' field in secret.data" % [secret.metadata.name, key],
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200381 secretKeyRef: {
382 name: secret.metadata.name,
383 key: key,
384 },
385 },
386
387 // subtype of EnvVarSource
388 FieldRef(key): {
389 fieldRef: {
390 apiVersion: "v1",
391 fieldPath: key,
392 },
393 },
394
395 // subtype of EnvVarSource
396 ResourceFieldRef(key, divisor="1"): {
397 resourceFieldRef: {
398 resource: key,
399 divisor: std.toString(divisor),
400 },
401 },
402
Rafał Hirszccda3332020-05-16 21:01:03 +0200403 Deployment(name): $._Object("apps/v1", "Deployment", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200404 local deployment = self,
405
406 spec: {
407 template: {
408 spec: $.PodSpec,
409 metadata: {
410 labels: deployment.metadata.labels,
411 annotations: {},
412 },
413 },
414
415 selector: {
416 matchLabels: deployment.spec.template.metadata.labels,
417 },
418
419 strategy: {
420 type: "RollingUpdate",
421
422 local pvcs = [
423 v
424 for v in deployment.spec.template.spec.volumes
425 if std.objectHas(v, "persistentVolumeClaim")
426 ],
427 local is_stateless = std.length(pvcs) == 0,
428
429 // Apps trying to maintain a majority quorum or similar will
430 // want to tune these carefully.
431 // NB: Upstream default is surge=1 unavail=1
432 rollingUpdate: if is_stateless then {
433 maxSurge: "25%", // rounds up
434 maxUnavailable: "25%", // rounds down
435 } else {
436 // Poor-man's StatelessSet. Useful mostly with replicas=1.
437 maxSurge: 0,
438 maxUnavailable: 1,
439 },
440 },
441
442 // NB: Upstream default is 0
443 minReadySeconds: 30,
444
Rafał Hirszccda3332020-05-16 21:01:03 +0200445 // NB: Regular k8s default is to keep all revisions
446 revisionHistoryLimit: 10,
447
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200448 replicas: 1,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200449 },
450 },
451
452 CrossVersionObjectReference(target): {
453 apiVersion: target.apiVersion,
454 kind: target.kind,
455 name: target.metadata.name,
456 },
457
458 HorizontalPodAutoscaler(name): $._Object("autoscaling/v1", "HorizontalPodAutoscaler", name) {
459 local hpa = self,
460
461 target:: error "target required",
462
463 spec: {
464 scaleTargetRef: $.CrossVersionObjectReference(hpa.target),
465
466 minReplicas: hpa.target.spec.replicas,
467 maxReplicas: error "maxReplicas required",
468
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200469 assert (!$._assert) || self.maxReplicas >= self.minReplicas,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200470 },
471 },
472
Rafał Hirszccda3332020-05-16 21:01:03 +0200473 StatefulSet(name): $._Object("apps/v1", "StatefulSet", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200474 local sset = self,
475
476 spec: {
477 serviceName: name,
478
479 updateStrategy: {
480 type: "RollingUpdate",
481 rollingUpdate: {
482 partition: 0,
483 },
484 },
485
486 template: {
487 spec: $.PodSpec,
488 metadata: {
489 labels: sset.metadata.labels,
490 annotations: {},
491 },
492 },
493
494 selector: {
495 matchLabels: sset.spec.template.metadata.labels,
496 },
497
498 volumeClaimTemplates_:: {},
499 volumeClaimTemplates: [
500 // StatefulSet is overly fussy about "changes" (even when
501 // they're no-ops).
502 // In particular annotations={} is apparently a "change",
503 // since the comparison is ignorant of defaults.
504 std.prune($.PersistentVolumeClaim($.hyphenate(kv[0])) + { apiVersion:: null, kind:: null } + kv[1])
505 for kv in $.objectItems(self.volumeClaimTemplates_)
506 ],
507
508 replicas: 1,
Piotr Dobrowolski21c8cd62021-09-16 13:07:54 +0200509 # TODO(github.com/bitnami-labs/kube-libsonnet/pull/66): update from
510 # upstream when merged
511 # assert (!$._assert) || self.replicas >= 1,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200512 },
513 },
514
515 Job(name): $._Object("batch/v1", "Job", name) {
516 local job = self,
517
518 spec: $.JobSpec {
519 template+: {
520 metadata+: {
521 labels: job.metadata.labels,
522 },
523 },
524 },
525 },
526
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200527 CronJob(name): $._Object("batch/v1beta1", "CronJob", name) {
528 local cronjob = self,
529
530 spec: {
531 jobTemplate: {
532 spec: $.JobSpec {
533 template+: {
534 metadata+: {
535 labels: cronjob.metadata.labels,
536 },
537 },
538 },
539 },
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200540 schedule: error "Need to provide spec.schedule",
541 successfulJobsHistoryLimit: 10,
542 failedJobsHistoryLimit: 20,
543 // NB: upstream concurrencyPolicy default is "Allow"
544 concurrencyPolicy: "Forbid",
545 },
546 },
547
548 JobSpec: {
549 local this = self,
550
551 template: {
552 spec: $.PodSpec {
553 restartPolicy: "OnFailure",
554 },
555 },
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200556 completions: 1,
557 parallelism: 1,
558 },
559
Rafał Hirszccda3332020-05-16 21:01:03 +0200560 DaemonSet(name): $._Object("apps/v1", "DaemonSet", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200561 local ds = self,
562 spec: {
563 updateStrategy: {
564 type: "RollingUpdate",
565 rollingUpdate: {
566 maxUnavailable: 1,
567 },
568 },
569 template: {
570 metadata: {
571 labels: ds.metadata.labels,
572 annotations: {},
573 },
574 spec: $.PodSpec,
575 },
576
577 selector: {
578 matchLabels: ds.spec.template.metadata.labels,
579 },
580 },
581 },
582
Rafał Hirszccda3332020-05-16 21:01:03 +0200583 Ingress(name): $._Object("networking.k8s.io/v1beta1", "Ingress", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200584 spec: {},
585
586 local rel_paths = [
587 p.path
588 for r in self.spec.rules
589 for p in r.http.paths
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200590 if std.objectHas(p, "path") && !std.startsWith(p.path, "/")
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200591 ],
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200592 assert (!$._assert) || std.length(rel_paths) == 0 : "paths must be absolute: " + rel_paths,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200593 },
594
595 ThirdPartyResource(name): $._Object("extensions/v1beta1", "ThirdPartyResource", name) {
596 versions_:: [],
597 versions: [{ name: n } for n in self.versions_],
598 },
599
600 CustomResourceDefinition(group, version, kind): {
601 local this = self,
602 apiVersion: "apiextensions.k8s.io/v1beta1",
603 kind: "CustomResourceDefinition",
604 metadata+: {
605 name: this.spec.names.plural + "." + this.spec.group,
606 },
607 spec: {
608 scope: "Namespaced",
609 group: group,
610 version: version,
611 names: {
612 kind: kind,
613 singular: $.toLower(self.kind),
614 plural: self.singular + "s",
615 listKind: self.kind + "List",
616 },
617 },
618 },
619
620 ServiceAccount(name): $._Object("v1", "ServiceAccount", name) {
621 },
622
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200623 Role(name): $._Object("rbac.authorization.k8s.io/v1", "Role", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200624 rules: [],
625 },
626
627 ClusterRole(name): $.Role(name) {
628 kind: "ClusterRole",
629 },
630
631 Group(name): {
632 kind: "Group",
633 name: name,
634 apiGroup: "rbac.authorization.k8s.io",
635 },
636
637 User(name): {
638 kind: "User",
639 name: name,
640 apiGroup: "rbac.authorization.k8s.io",
641 },
642
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200643 RoleBinding(name): $._Object("rbac.authorization.k8s.io/v1", "RoleBinding", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200644 local rb = self,
645
646 subjects_:: [],
647 subjects: [{
648 kind: o.kind,
649 namespace: o.metadata.namespace,
650 name: o.metadata.name,
651 } for o in self.subjects_],
652
653 roleRef_:: error "roleRef is required",
654 roleRef: {
655 apiGroup: "rbac.authorization.k8s.io",
656 kind: rb.roleRef_.kind,
657 name: rb.roleRef_.metadata.name,
658 },
659 },
660
661 ClusterRoleBinding(name): $.RoleBinding(name) {
662 kind: "ClusterRoleBinding",
663 },
664
Rafał Hirszccda3332020-05-16 21:01:03 +0200665 // NB: encryptedData can be imported into a SealedSecret as follows:
666 // kubectl get secret ... -ojson mysec | kubeseal | jq -r .spec.encryptedData > sealedsecret.json
667 // encryptedData: std.parseJson(importstr "sealedsecret.json")
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200668 SealedSecret(name): $._Object("bitnami.com/v1alpha1", "SealedSecret", name) {
669 spec: {
Rafał Hirszccda3332020-05-16 21:01:03 +0200670 encryptedData: {},
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200671 },
Bartosz Stebel3a15b832021-06-16 19:14:50 +0200672 assert (!$._assert) || std.length(std.objectFields(self.spec.encryptedData)) != 0 : "SealedSecret '%s' has empty encryptedData field" % name,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200673 },
674
675 // NB: helper method to access several Kubernetes objects podRef,
676 // used below to extract its labels
677 podRef(obj):: ({
678 Pod: obj,
679 Deployment: obj.spec.template,
680 StatefulSet: obj.spec.template,
681 DaemonSet: obj.spec.template,
682 Job: obj.spec.template,
683 CronJob: obj.spec.jobTemplate.spec.template,
684 }[obj.kind]),
685
686 // NB: return a { podSelector: ... } ready to use for e.g. NSPs (see below)
687 // pod labels can be optionally filtered by their label name 2nd array arg
688 podLabelsSelector(obj, filter=null):: {
689 podSelector: std.prune({
690 matchLabels:
691 if filter != null then $.filterMapByFields($.podRef(obj).metadata.labels, filter)
692 else $.podRef(obj).metadata.labels,
693 }),
694 },
695
696 // NB: Returns an array as [{ port: num, protocol: "PROTO" }, {...}, ... ]
697 // Need to split TCP, UDP logic to be able to dedup each set of protocol ports
698 podsPorts(obj_list):: std.flattenArrays([
699 [
700 { port: port, protocol: protocol }
701 for port in std.set(
702 std.flattenArrays([$.podRef(obj).spec.ports(protocol) for obj in obj_list])
703 )
704 ]
705 for protocol in ["TCP", "UDP"]
706 ]),
707
708 // NB: most of the "helper" stuff comes from above (podLabelsSelector, podsPorts),
709 // NetworkPolicy returned object will have "Ingress", "Egress" policyTypes auto-set
710 // based on populated spec.ingress or spec.egress
711 // See tests/test-simple-validate.jsonnet for example(s).
712 NetworkPolicy(name): $._Object("networking.k8s.io/v1", "NetworkPolicy", name) {
713 local networkpolicy = self,
714 spec: {
715 policyTypes: std.prune([
716 if networkpolicy.spec.ingress != [] then "Ingress" else null,
717 if networkpolicy.spec.egress != [] then "Egress" else null,
718 ]),
719 ingress: $.objectValues(self.ingress_),
720 ingress_:: {},
721 egress: $.objectValues(self.egress_),
722 egress_:: {},
Rafał Hirszccda3332020-05-16 21:01:03 +0200723 podSelector: {},
724 },
725 },
726
727 VerticalPodAutoscaler(name):: $._Object("autoscaling.k8s.io/v1beta2", "VerticalPodAutoscaler", name) {
728 local vpa = self,
729
730 target:: error "target required",
731
732 spec: {
733 targetRef: $.CrossVersionObjectReference(vpa.target),
734
735 updatePolicy: {
736 updateMode: "Auto",
737 },
738 },
739 },
740 // Helper function to ease VPA creation as e.g.:
741 // foo_vpa:: kube.createVPAFor($.foo_deploy)
742 createVPAFor(target, mode="Auto"):: $.VerticalPodAutoscaler(target.metadata.name) {
743 target:: target,
744
745 metadata+: {
746 namespace: target.metadata.namespace,
747 labels+: target.metadata.labels,
748 },
749 spec+: {
750 updatePolicy+: {
751 updateMode: mode,
752 },
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200753 },
754 },
755}