blob: d12a78687aa86ac682dc41f838ea8c43e1b8af5f [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{
Rafał Hirszccda3332020-05-16 21:01:03 +020056 // resource contructors will use kinds/versions/fields compatible at least with version:
57 minKubeVersion: {
58 major: 1,
59 minor: 9,
60 version: "%s.%s" % [self.major, self.minor],
61 },
62
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +020063 // Returns array of values from given object. Does not include hidden fields.
64 objectValues(o):: [o[field] for field in std.objectFields(o)],
65
66 // Returns array of [key, value] pairs from given object. Does not include hidden fields.
67 objectItems(o):: [[k, o[k]] for k in std.objectFields(o)],
68
69 // Replace all occurrences of `_` with `-`.
70 hyphenate(s):: std.join("-", std.split(s, "_")),
71
72 // Convert an octal (as a string) to number,
73 parseOctal(s):: (
74 local len = std.length(s);
75 local leading = std.substr(s, 0, len - 1);
76 local last = std.parseInt(std.substr(s, len - 1, 1));
77 assert last < 8 : "found '%s' digit >= 8" % [last];
78 last + (if len > 1 then 8 * $.parseOctal(leading) else 0)
79 ),
80
81 // Convert {foo: {a: b}} to [{name: foo, a: b}]
82 mapToNamedList(o):: [{ name: $.hyphenate(n) } + o[n] for n in std.objectFields(o)],
83
84 // Return object containing only these fields elements
85 filterMapByFields(o, fields): { [field]: o[field] for field in std.setInter(std.objectFields(o), fields) },
86
87 // Convert from SI unit suffixes to regular number
88 siToNum(n):: (
89 local convert =
90 if std.endsWith(n, "m") then [1, 0.001]
91 else if std.endsWith(n, "K") then [1, 1e3]
92 else if std.endsWith(n, "M") then [1, 1e6]
93 else if std.endsWith(n, "G") then [1, 1e9]
94 else if std.endsWith(n, "T") then [1, 1e12]
95 else if std.endsWith(n, "P") then [1, 1e15]
96 else if std.endsWith(n, "E") then [1, 1e18]
97 else if std.endsWith(n, "Ki") then [2, std.pow(2, 10)]
98 else if std.endsWith(n, "Mi") then [2, std.pow(2, 20)]
99 else if std.endsWith(n, "Gi") then [2, std.pow(2, 30)]
100 else if std.endsWith(n, "Ti") then [2, std.pow(2, 40)]
101 else if std.endsWith(n, "Pi") then [2, std.pow(2, 50)]
102 else if std.endsWith(n, "Ei") then [2, std.pow(2, 60)]
103 else error "Unknown numerical suffix in " + n;
104 local n_len = std.length(n);
105 std.parseInt(std.substr(n, 0, n_len - convert[0])) * convert[1]
106 ),
107
108 local remap(v, start, end, newstart) =
109 if v >= start && v <= end then v - start + newstart else v,
110 local remapChar(c, start, end, newstart) =
111 std.char(remap(
112 std.codepoint(c), std.codepoint(start), std.codepoint(end), std.codepoint(newstart)
113 )),
114 toLower(s):: (
115 std.join("", [remapChar(c, "A", "Z", "a") for c in std.stringChars(s)])
116 ),
117 toUpper(s):: (
118 std.join("", [remapChar(c, "a", "z", "A") for c in std.stringChars(s)])
119 ),
120
Rafał Hirszccda3332020-05-16 21:01:03 +0200121 boolXor(x, y):: ((if x then 1 else 0) + (if y then 1 else 0) == 1),
122
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200123 _Object(apiVersion, kind, name):: {
124 local this = self,
125 apiVersion: apiVersion,
126 kind: kind,
127 metadata: {
128 name: name,
129 labels: { name: std.join("-", std.split(this.metadata.name, ":")) },
130 annotations: {},
131 },
132 },
133
134 List(): {
135 apiVersion: "v1",
136 kind: "List",
137 items_:: {},
138 items: $.objectValues(self.items_),
139 },
140
141 Namespace(name): $._Object("v1", "Namespace", name) {
142 },
143
144 Endpoints(name): $._Object("v1", "Endpoints", name) {
145 Ip(addr):: { ip: addr },
146 Port(p):: { port: p },
147
148 subsets: [],
149 },
150
151 Service(name): $._Object("v1", "Service", name) {
152 local service = self,
153
154 target_pod:: error "service target_pod required",
155 port:: self.target_pod.spec.containers[0].ports[0].containerPort,
156
157 // Helpers that format host:port in various ways
158 host:: "%s.%s.svc" % [self.metadata.name, self.metadata.namespace],
159 host_colon_port:: "%s:%s" % [self.host, self.spec.ports[0].port],
160 http_url:: "http://%s/" % self.host_colon_port,
161 proxy_urlpath:: "/api/v1/proxy/namespaces/%s/services/%s/" % [
162 self.metadata.namespace,
163 self.metadata.name,
164 ],
165 // Useful in Ingress rules
166 name_port:: {
167 serviceName: service.metadata.name,
168 servicePort: service.spec.ports[0].port,
169 },
170
171 spec: {
172 selector: service.target_pod.metadata.labels,
173 ports: [
174 {
175 port: service.port,
Rafał Hirszccda3332020-05-16 21:01:03 +0200176 name: service.target_pod.spec.containers[0].ports[0].name,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200177 targetPort: service.target_pod.spec.containers[0].ports[0].containerPort,
178 },
179 ],
180 type: "ClusterIP",
181 },
182 },
183
184 PersistentVolume(name): $._Object("v1", "PersistentVolume", name) {
185 spec: {},
186 },
187
188 // TODO: This is a terrible name
189 PersistentVolumeClaimVolume(pvc): {
190 persistentVolumeClaim: { claimName: pvc.metadata.name },
191 },
192
193 StorageClass(name): $._Object("storage.k8s.io/v1beta1", "StorageClass", name) {
194 provisioner: error "provisioner required",
195 },
196
197 PersistentVolumeClaim(name): $._Object("v1", "PersistentVolumeClaim", name) {
198 local pvc = self,
199
200 storageClass:: null,
201 storage:: error "storage required",
202
203 metadata+: if pvc.storageClass != null then {
204 annotations+: {
205 "volume.beta.kubernetes.io/storage-class": pvc.storageClass,
206 },
207 } else {},
208
209 spec: {
210 resources: {
211 requests: {
212 storage: pvc.storage,
213 },
214 },
215 accessModes: ["ReadWriteOnce"],
Rafał Hirszccda3332020-05-16 21:01:03 +0200216 [if pvc.storageClass != null then "storageClassName"]: pvc.storageClass,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200217 },
218 },
219
220 Container(name): {
221 name: name,
222 image: error "container image value required",
223 imagePullPolicy: if std.endsWith(self.image, ":latest") then "Always" else "IfNotPresent",
224
225 envList(map):: [
Rafał Hirszccda3332020-05-16 21:01:03 +0200226 if std.type(map[x]) == "object"
227 then {
228 name: x,
229 valueFrom: map[x],
230 } else {
231 // Let `null` value stay as such (vs string-ified)
232 name: x,
233 value: if map[x] == null then null else std.toString(map[x]),
234 }
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200235 for x in std.objectFields(map)
236 ],
237
238 env_:: {},
239 env: self.envList(self.env_),
240
241 args_:: {},
242 args: ["--%s=%s" % kv for kv in $.objectItems(self.args_)],
243
244 ports_:: {},
245 ports: $.mapToNamedList(self.ports_),
246
247 volumeMounts_:: {},
248 volumeMounts: $.mapToNamedList(self.volumeMounts_),
249
250 stdin: false,
251 tty: false,
252 assert !self.tty || self.stdin : "tty=true requires stdin=true",
253 },
254
255 PodDisruptionBudget(name): $._Object("policy/v1beta1", "PodDisruptionBudget", name) {
256 local this = self,
257 target_pod:: error "target_pod required",
258 spec: {
Rafał Hirszccda3332020-05-16 21:01:03 +0200259 assert $.boolXor(
260 std.objectHas(self, "minAvailable"),
261 std.objectHas(self, "maxUnavailable")
262 ) : "PDB '%s': exactly one of minAvailable/maxUnavailable required" % name,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200263 selector: {
264 matchLabels: this.target_pod.metadata.labels,
265 },
266 },
267 },
268
269 Pod(name): $._Object("v1", "Pod", name) {
270 spec: $.PodSpec,
271 },
272
273 PodSpec: {
274 // The 'first' container is used in various defaults in k8s.
275 local container_names = std.objectFields(self.containers_),
276 default_container:: if std.length(container_names) > 1 then "default" else container_names[0],
277 containers_:: {},
278
279 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 +0200280 containers: (
281 assert std.length(self.containers_) > 0 : "Pod must have at least one container (via containers_ map)";
282 [{ name: $.hyphenate(name) } + self.containers_[name] for name in container_names_ordered if self.containers_[name] != null]
283 ),
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200284
285 // Note initContainers are inherently ordered, and using this
286 // named object will lose that ordering. If order matters, then
287 // manipulate `initContainers` directly (perhaps
288 // appending/prepending to `super.initContainers` to mix+match
289 // both approaches)
290 initContainers_:: {},
291 initContainers: [{ name: $.hyphenate(name) } + self.initContainers_[name] for name in std.objectFields(self.initContainers_) if self.initContainers_[name] != null],
292
293 volumes_:: {},
294 volumes: $.mapToNamedList(self.volumes_),
295
296 imagePullSecrets: [],
297
298 terminationGracePeriodSeconds: 30,
299
Rafał Hirszccda3332020-05-16 21:01:03 +0200300 assert std.length(self.containers) > 0 : "Pod must have at least one container (via containers array)",
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200301
302 // Return an array of pod's ports numbers
303 ports(proto):: [
304 p.containerPort
305 for p in std.flattenArrays([
306 c.ports
307 for c in self.containers
308 ])
309 if (
310 (!(std.objectHas(p, "protocol")) && proto == "TCP")
311 ||
312 ((std.objectHas(p, "protocol")) && p.protocol == proto)
313 )
314 ],
315
316 },
317
318 EmptyDirVolume(): {
319 emptyDir: {},
320 },
321
322 HostPathVolume(path, type=""): {
323 hostPath: { path: path, type: type },
324 },
325
326 GitRepoVolume(repository, revision): {
327 gitRepo: {
328 repository: repository,
329
330 // "master" is possible, but should be avoided for production
331 revision: revision,
332 },
333 },
334
335 SecretVolume(secret): {
336 secret: { secretName: secret.metadata.name },
337 },
338
339 ConfigMapVolume(configmap): {
340 configMap: { name: configmap.metadata.name },
341 },
342
343 ConfigMap(name): $._Object("v1", "ConfigMap", name) {
344 data: {},
345
346 // I keep thinking data values can be any JSON type. This check
347 // will remind me that they must be strings :(
348 local nonstrings = [
349 k
350 for k in std.objectFields(self.data)
351 if std.type(self.data[k]) != "string"
352 ],
353 assert std.length(nonstrings) == 0 : "data contains non-string values: %s" % [nonstrings],
354 },
355
356 // subtype of EnvVarSource
357 ConfigMapRef(configmap, key): {
Rafał Hirszccda3332020-05-16 21:01:03 +0200358 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 +0200359 configMapKeyRef: {
360 name: configmap.metadata.name,
361 key: key,
362 },
363 },
364
365 Secret(name): $._Object("v1", "Secret", name) {
366 local secret = self,
367
368 type: "Opaque",
369 data_:: {},
370 data: { [k]: std.base64(secret.data_[k]) for k in std.objectFields(secret.data_) },
371 },
372
373 // subtype of EnvVarSource
374 SecretKeyRef(secret, key): {
Rafał Hirszccda3332020-05-16 21:01:03 +0200375 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 +0200376 secretKeyRef: {
377 name: secret.metadata.name,
378 key: key,
379 },
380 },
381
382 // subtype of EnvVarSource
383 FieldRef(key): {
384 fieldRef: {
385 apiVersion: "v1",
386 fieldPath: key,
387 },
388 },
389
390 // subtype of EnvVarSource
391 ResourceFieldRef(key, divisor="1"): {
392 resourceFieldRef: {
393 resource: key,
394 divisor: std.toString(divisor),
395 },
396 },
397
Rafał Hirszccda3332020-05-16 21:01:03 +0200398 Deployment(name): $._Object("apps/v1", "Deployment", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200399 local deployment = self,
400
401 spec: {
402 template: {
403 spec: $.PodSpec,
404 metadata: {
405 labels: deployment.metadata.labels,
406 annotations: {},
407 },
408 },
409
410 selector: {
411 matchLabels: deployment.spec.template.metadata.labels,
412 },
413
414 strategy: {
415 type: "RollingUpdate",
416
417 local pvcs = [
418 v
419 for v in deployment.spec.template.spec.volumes
420 if std.objectHas(v, "persistentVolumeClaim")
421 ],
422 local is_stateless = std.length(pvcs) == 0,
423
424 // Apps trying to maintain a majority quorum or similar will
425 // want to tune these carefully.
426 // NB: Upstream default is surge=1 unavail=1
427 rollingUpdate: if is_stateless then {
428 maxSurge: "25%", // rounds up
429 maxUnavailable: "25%", // rounds down
430 } else {
431 // Poor-man's StatelessSet. Useful mostly with replicas=1.
432 maxSurge: 0,
433 maxUnavailable: 1,
434 },
435 },
436
437 // NB: Upstream default is 0
438 minReadySeconds: 30,
439
Rafał Hirszccda3332020-05-16 21:01:03 +0200440 // NB: Regular k8s default is to keep all revisions
441 revisionHistoryLimit: 10,
442
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200443 replicas: 1,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200444 },
445 },
446
447 CrossVersionObjectReference(target): {
448 apiVersion: target.apiVersion,
449 kind: target.kind,
450 name: target.metadata.name,
451 },
452
453 HorizontalPodAutoscaler(name): $._Object("autoscaling/v1", "HorizontalPodAutoscaler", name) {
454 local hpa = self,
455
456 target:: error "target required",
457
458 spec: {
459 scaleTargetRef: $.CrossVersionObjectReference(hpa.target),
460
461 minReplicas: hpa.target.spec.replicas,
462 maxReplicas: error "maxReplicas required",
463
464 assert self.maxReplicas >= self.minReplicas,
465 },
466 },
467
Rafał Hirszccda3332020-05-16 21:01:03 +0200468 StatefulSet(name): $._Object("apps/v1", "StatefulSet", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200469 local sset = self,
470
471 spec: {
472 serviceName: name,
473
474 updateStrategy: {
475 type: "RollingUpdate",
476 rollingUpdate: {
477 partition: 0,
478 },
479 },
480
481 template: {
482 spec: $.PodSpec,
483 metadata: {
484 labels: sset.metadata.labels,
485 annotations: {},
486 },
487 },
488
489 selector: {
490 matchLabels: sset.spec.template.metadata.labels,
491 },
492
493 volumeClaimTemplates_:: {},
494 volumeClaimTemplates: [
495 // StatefulSet is overly fussy about "changes" (even when
496 // they're no-ops).
497 // In particular annotations={} is apparently a "change",
498 // since the comparison is ignorant of defaults.
499 std.prune($.PersistentVolumeClaim($.hyphenate(kv[0])) + { apiVersion:: null, kind:: null } + kv[1])
500 for kv in $.objectItems(self.volumeClaimTemplates_)
501 ],
502
503 replicas: 1,
504 assert self.replicas >= 1,
505 },
506 },
507
508 Job(name): $._Object("batch/v1", "Job", name) {
509 local job = self,
510
511 spec: $.JobSpec {
512 template+: {
513 metadata+: {
514 labels: job.metadata.labels,
515 },
516 },
517 },
518 },
519
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200520 CronJob(name): $._Object("batch/v1beta1", "CronJob", name) {
521 local cronjob = self,
522
523 spec: {
524 jobTemplate: {
525 spec: $.JobSpec {
526 template+: {
527 metadata+: {
528 labels: cronjob.metadata.labels,
529 },
530 },
531 },
532 },
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200533 schedule: error "Need to provide spec.schedule",
534 successfulJobsHistoryLimit: 10,
535 failedJobsHistoryLimit: 20,
536 // NB: upstream concurrencyPolicy default is "Allow"
537 concurrencyPolicy: "Forbid",
538 },
539 },
540
541 JobSpec: {
542 local this = self,
543
544 template: {
545 spec: $.PodSpec {
546 restartPolicy: "OnFailure",
547 },
548 },
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200549 completions: 1,
550 parallelism: 1,
551 },
552
Rafał Hirszccda3332020-05-16 21:01:03 +0200553 DaemonSet(name): $._Object("apps/v1", "DaemonSet", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200554 local ds = self,
555 spec: {
556 updateStrategy: {
557 type: "RollingUpdate",
558 rollingUpdate: {
559 maxUnavailable: 1,
560 },
561 },
562 template: {
563 metadata: {
564 labels: ds.metadata.labels,
565 annotations: {},
566 },
567 spec: $.PodSpec,
568 },
569
570 selector: {
571 matchLabels: ds.spec.template.metadata.labels,
572 },
573 },
574 },
575
Rafał Hirszccda3332020-05-16 21:01:03 +0200576 Ingress(name): $._Object("networking.k8s.io/v1beta1", "Ingress", name) {
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200577 spec: {},
578
579 local rel_paths = [
580 p.path
581 for r in self.spec.rules
582 for p in r.http.paths
583 if !std.startsWith(p.path, "/")
584 ],
585 assert std.length(rel_paths) == 0 : "paths must be absolute: " + rel_paths,
586 },
587
588 ThirdPartyResource(name): $._Object("extensions/v1beta1", "ThirdPartyResource", name) {
589 versions_:: [],
590 versions: [{ name: n } for n in self.versions_],
591 },
592
593 CustomResourceDefinition(group, version, kind): {
594 local this = self,
595 apiVersion: "apiextensions.k8s.io/v1beta1",
596 kind: "CustomResourceDefinition",
597 metadata+: {
598 name: this.spec.names.plural + "." + this.spec.group,
599 },
600 spec: {
601 scope: "Namespaced",
602 group: group,
603 version: version,
604 names: {
605 kind: kind,
606 singular: $.toLower(self.kind),
607 plural: self.singular + "s",
608 listKind: self.kind + "List",
609 },
610 },
611 },
612
613 ServiceAccount(name): $._Object("v1", "ServiceAccount", name) {
614 },
615
616 Role(name): $._Object("rbac.authorization.k8s.io/v1beta1", "Role", name) {
617 rules: [],
618 },
619
620 ClusterRole(name): $.Role(name) {
621 kind: "ClusterRole",
622 },
623
624 Group(name): {
625 kind: "Group",
626 name: name,
627 apiGroup: "rbac.authorization.k8s.io",
628 },
629
630 User(name): {
631 kind: "User",
632 name: name,
633 apiGroup: "rbac.authorization.k8s.io",
634 },
635
636 RoleBinding(name): $._Object("rbac.authorization.k8s.io/v1beta1", "RoleBinding", name) {
637 local rb = self,
638
639 subjects_:: [],
640 subjects: [{
641 kind: o.kind,
642 namespace: o.metadata.namespace,
643 name: o.metadata.name,
644 } for o in self.subjects_],
645
646 roleRef_:: error "roleRef is required",
647 roleRef: {
648 apiGroup: "rbac.authorization.k8s.io",
649 kind: rb.roleRef_.kind,
650 name: rb.roleRef_.metadata.name,
651 },
652 },
653
654 ClusterRoleBinding(name): $.RoleBinding(name) {
655 kind: "ClusterRoleBinding",
656 },
657
Rafał Hirszccda3332020-05-16 21:01:03 +0200658 // NB: encryptedData can be imported into a SealedSecret as follows:
659 // kubectl get secret ... -ojson mysec | kubeseal | jq -r .spec.encryptedData > sealedsecret.json
660 // encryptedData: std.parseJson(importstr "sealedsecret.json")
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200661 SealedSecret(name): $._Object("bitnami.com/v1alpha1", "SealedSecret", name) {
662 spec: {
Rafał Hirszccda3332020-05-16 21:01:03 +0200663 encryptedData: {},
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200664 },
Rafał Hirszccda3332020-05-16 21:01:03 +0200665 assert std.length(std.objectFields(self.spec.encryptedData)) != 0 : "SealedSecret '%s' has empty encryptedData field" % name,
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200666 },
667
668 // NB: helper method to access several Kubernetes objects podRef,
669 // used below to extract its labels
670 podRef(obj):: ({
671 Pod: obj,
672 Deployment: obj.spec.template,
673 StatefulSet: obj.spec.template,
674 DaemonSet: obj.spec.template,
675 Job: obj.spec.template,
676 CronJob: obj.spec.jobTemplate.spec.template,
677 }[obj.kind]),
678
679 // NB: return a { podSelector: ... } ready to use for e.g. NSPs (see below)
680 // pod labels can be optionally filtered by their label name 2nd array arg
681 podLabelsSelector(obj, filter=null):: {
682 podSelector: std.prune({
683 matchLabels:
684 if filter != null then $.filterMapByFields($.podRef(obj).metadata.labels, filter)
685 else $.podRef(obj).metadata.labels,
686 }),
687 },
688
689 // NB: Returns an array as [{ port: num, protocol: "PROTO" }, {...}, ... ]
690 // Need to split TCP, UDP logic to be able to dedup each set of protocol ports
691 podsPorts(obj_list):: std.flattenArrays([
692 [
693 { port: port, protocol: protocol }
694 for port in std.set(
695 std.flattenArrays([$.podRef(obj).spec.ports(protocol) for obj in obj_list])
696 )
697 ]
698 for protocol in ["TCP", "UDP"]
699 ]),
700
701 // NB: most of the "helper" stuff comes from above (podLabelsSelector, podsPorts),
702 // NetworkPolicy returned object will have "Ingress", "Egress" policyTypes auto-set
703 // based on populated spec.ingress or spec.egress
704 // See tests/test-simple-validate.jsonnet for example(s).
705 NetworkPolicy(name): $._Object("networking.k8s.io/v1", "NetworkPolicy", name) {
706 local networkpolicy = self,
707 spec: {
708 policyTypes: std.prune([
709 if networkpolicy.spec.ingress != [] then "Ingress" else null,
710 if networkpolicy.spec.egress != [] then "Egress" else null,
711 ]),
712 ingress: $.objectValues(self.ingress_),
713 ingress_:: {},
714 egress: $.objectValues(self.egress_),
715 egress_:: {},
Rafał Hirszccda3332020-05-16 21:01:03 +0200716 podSelector: {},
717 },
718 },
719
720 VerticalPodAutoscaler(name):: $._Object("autoscaling.k8s.io/v1beta2", "VerticalPodAutoscaler", name) {
721 local vpa = self,
722
723 target:: error "target required",
724
725 spec: {
726 targetRef: $.CrossVersionObjectReference(vpa.target),
727
728 updatePolicy: {
729 updateMode: "Auto",
730 },
731 },
732 },
733 // Helper function to ease VPA creation as e.g.:
734 // foo_vpa:: kube.createVPAFor($.foo_deploy)
735 createVPAFor(target, mode="Auto"):: $.VerticalPodAutoscaler(target.metadata.name) {
736 target:: target,
737
738 metadata+: {
739 namespace: target.metadata.namespace,
740 labels+: target.metadata.labels,
741 },
742 spec+: {
743 updatePolicy+: {
744 updateMode: mode,
745 },
Sergiusz Bazanskie31d64f2019-10-02 20:59:26 +0200746 },
747 },
748}