Merge "cluster: deploy NixOS-based ceph"
diff --git a/kube/kube.libsonnet b/kube/kube.libsonnet
index 8d7254a..7e69720 100644
--- a/kube/kube.libsonnet
+++ b/kube/kube.libsonnet
@@ -46,6 +46,7 @@
 
     // Make OpenAPI v3 schema specification less painful.
     OpenAPI:: {
+        local openapi = self,
         Validation(obj):: {
             openAPIV3Schema: obj.render,
         },
@@ -54,69 +55,85 @@
             required:: true,
         },
 
-        Dict:: {
-            local dict = self,
+        renderable:: {
             required:: false,
+            render:: {},
+        },
+        local renderable = self.renderable,
+
+        lift(f, keys):: {
+            [if f[k] != null then k]: f[k]
+            for k in keys
+        },
+        local lift = self.lift,
+
+        parametrized(type, keys=[]):: (
+            local keysp = keys + [
+                "description",
+                "nullable",
+                "x-kubernetes-preserve-unknown-fields",
+            ];
+            renderable {
+                render:: lift(self, keysp) {
+                    type: type,
+                },
+            } + {
+                [k]: null
+                for k in keysp
+            }
+        ),
+        local parametrized = self.parametrized,
+
+        Any:: renderable,
+        Boolean:: parametrized("boolean"),
+        Integer:: parametrized("integer", ["minimum", "maximum", "format"]),
+        Number:: parametrized("number"),
+        String:: parametrized("string", ["pattern"]),
+
+        Dict:: renderable {
+            local d = self,
 
             local requiredList = [
-                k for k in std.filter(function(k) dict[k].required, std.objectFields(dict))
+                k for k in std.filter(function(k) d[k].required, std.objectFields(d))
             ],
 
-            render:: {
+            render+: {
                 properties: {
-                    [k]: dict[k].render
-                    for k in std.objectFields(dict)
+                    [k]: d[k].render
+                    for k in std.objectFields(d)
                 },
-            } + (if std.length(requiredList) > 0 then {
-                required: requiredList,
-            } else {}),
+                [if std.length(requiredList) > 0 then 'required']: requiredList,
+            },
+        },
+        Object(props={}):: parametrized("object") {
+            local requiredList = [
+                k for k in std.filter(function(k) props[k].required, std.objectFields(props))
+            ],
+
+            render+: {
+                [if std.length(std.objectFields(props)) > 0 then "properties"]: {
+                    [k]: props[k].render
+                    for k in std.objectFields(props)
+                },
+                [if std.length(requiredList) > 0 then 'required']: requiredList,
+            },
         },
 
-        Array(items):: {
-            required:: false,
-            render:: {
-                type: "array",
+        Array(items):: parametrized("array") {
+            render+: {
                 items: items.render,
             },
         },
 
-        Integer:: {
-            local integer = self,
-            required:: false,
-            render:: {
-                type: "integer",
-            } + (if integer.minimum != null then {
-                minimum: integer.minimum,
-            } else {}) + (if integer.maximum != null then {
-                maximum: integer.maximum,
-            } else {}),
-
-            minimum:: null,
-            maximum:: null,
-        },
-
-        String:: {
-            local string = self,
-            required:: false,
-            render:: {
-                type: "string",
-            } + (if string.pattern != null then {
-                pattern: string.pattern,
-            } else {}),
-
-            pattern:: null,
-        },
-
-        Boolean:: {
-            required:: false,
-            render:: {
-                type: "boolean",
+        Enum(items):: parametrized("string") {
+            render+: {
+                enum: items,
             },
         },
 
-        Any:: {
-            required:: false,
-            render:: {},
+        KubeAny(nullable=false):: self.Object() {
+            [if nullable then "nullable"]: true,
+            "x-kubernetes-preserve-unknown-fields": true,
         },
     },
 }