{ pkgs ? import {}, lib ? pkgs.lib, spec ? ./istio-schema.json }: with lib; let gen = rec { mkMerge = values: ''mkMerge [${concatMapStrings (value: " ${value} ") values}]''; toNixString = value: if isAttrs value || isList value then builtins.toJSON value else if isString value then ''"${value}"'' else if value == null then "null" else builtins.toString value; removeEmptyLines = str: concatStringsSep "\n" (filter (l: (builtins.match "( |)+" l) == null) (splitString "\n" str)); mkOption = { description ? null, type ? null, default ? null, apply ? null }: removeEmptyLines ''mkOption { ${optionalString (description != null) "description = ${builtins.toJSON description};"} ${optionalString (type != null) ''type = ${type};''} ${optionalString (default != null) ''default = ${toNixString default};''} ${optionalString (apply != null) ''apply = ${apply};''} }''; mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}"; types = { unspecified = "types.unspecified"; str = "types.str"; int = "types.int"; bool = "types.bool"; attrs = "types.attrs"; nullOr = val: "(types.nullOr ${val})"; attrsOf = val: "(types.attrsOf ${val})"; listOf = val: "(types.listOf ${val})"; coercedTo = coercedType: coerceFunc: finalType: "(types.coercedTo ${coercedType} ${coerceFunc} ${finalType})"; either = val1: val2: "(types.either ${val1} ${val2})"; loaOf = type: "(types.loaOf ${type})"; }; hasTypeMapping = def: hasAttr "type" def && elem def.type ["string" "integer" "boolean"]; mergeValuesByKey = mergeKey: ''(mergeValuesByKey "${mergeKey}")''; mapType = def: if def.type == "string" then if hasAttr "format" def && def.format == "int-or-string" then types.either types.int types.str else types.str else if def.type == "integer" then types.int else if def.type == "number" then types.int else if def.type == "boolean" then types.bool else if def.type == "object" then types.attrs else throw "type ${def.type} not supported"; submoduleOf = definitions: ref: ''(submoduleOf "${ref}")''; submoduleForDefinition = ref: name: kind: group: version: ''(submoduleForDefinition "${ref}" "${name}" "${kind}" "${group}" "${version}")''; coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: ''(coerceAttrsOfSubmodulesToListByKey "${ref}" "${mergeKey}")''; attrsToList = "values: if values != null then mapAttrsToList (n: v: v) values else values"; refDefinition = attr: head (tail (tail (splitString "/" attr."$ref"))); }; fixJSON = content: replaceStrings ["\\u"] ["u"] content; fetchSpecs = path: builtins.fromJSON (fixJSON (builtins.readFile path)); genDefinitions = swagger: with gen; (mapAttrs (name: definition: # if $ref is in definition it means it's an alias of other definition if hasAttr "$ref" definition then definitions."${refDefinition definition}" else if !(hasAttr "properties" definition) then { type = mapType definition; } else { options = mapAttrs (propName: property: let isRequired = elem propName (definition.required or []); requiredOrNot = type: if isRequired then type else types.nullOr type; optionProperties = # if $ref is in property it references other definition, # but if other definition does not have properties, then just take it's type if hasAttr "$ref" property then if hasTypeMapping swagger.definitions.${refDefinition property} then { type = requiredOrNot (mapType swagger.definitions.${refDefinition property}); } else { type = requiredOrNot (submoduleOf definitions (refDefinition property)); } else if !(hasAttr "type" property) then { type = types.unspecified; } # if property has an array type else if property.type == "array" then # if reference is in items it can reference other type of another # definition if hasAttr "$ref" property.items then # if it is a reference to simple type if hasTypeMapping swagger.definitions.${refDefinition property.items} then { type = requiredOrNot (types.listOf (mapType swagger.definitions.${refDefinition property.items}.type)); } # if a reference is to complex type else # if x-kubernetes-patch-merge-key is set then make it an # attribute set of submodules if hasAttr "x-kubernetes-patch-merge-key" property then let mergeKey = property."x-kubernetes-patch-merge-key"; in { type = requiredOrNot (coerceAttrsOfSubmodulesToListByKey (refDefinition property.items) mergeKey); apply = attrsToList; } # in other case it's a simple list else { type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items))); } # in other case it only references a simple type else { type = requiredOrNot (types.listOf (mapType property.items)); } else if property.type == "object" && hasAttr "additionalProperties" property then # if it is a reference to simple type if ( hasAttr "$ref" property.additionalProperties && hasTypeMapping swagger.definitions.${refDefinition property.additionalProperties} ) then { type = requiredOrNot (types.attrsOf (mapType swagger.definitions.${refDefinition property.additionalProperties})); } else if hasAttr "$ref" property.additionalProperties then { type = requiredOrNot types.attrs; } # if is an array else if property.additionalProperties.type == "array" then { type = requiredOrNot (types.loaOf (mapType property.additionalProperties.items)); } else { type = requiredOrNot (types.attrsOf (mapType property.additionalProperties)); } # just a simple property else { type = requiredOrNot (mapType property); }; in mkOption ({ description = property.description or ""; } // optionProperties) ) definition.properties; config = let optionalProps = filterAttrs (propName: property: !(elem propName (definition.required or [])) ) definition.properties; in mapAttrs (name: property: mkOverride 1002 null) optionalProps; } ) swagger.definitions); genResources = swagger: (mapAttrsToList (name: property: rec { splittedType = splitString "." (removePrefix "me.snowdrop.istio.api." property.javaType); group = (concatStringsSep "." (take ((length splittedType) - 2) splittedType)) + ".istio.io"; kind = removeSuffix "Spec" (last splittedType); version = last (take ((length splittedType) - 1) splittedType); ref = removePrefix "#/definitions/" property."$ref"; }) (filterAttrs (name: property: (hasPrefix "me.snowdrop.istio.api" property.javaType) && hasSuffix "Spec" property.javaType ) swagger.properties)) ++ (mapAttrsToList (name: property: rec { splittedType = splitString "." (removePrefix "me.snowdrop.istio.mixer." property.javaType); group = "config.istio.io"; version = "v1alpha2"; kind = head (tail splittedType); ref = removePrefix "#/definitions/" property."$ref"; }) (filterAttrs (name: property: (hasPrefix "me.snowdrop.istio.mixer" property.javaType) && hasSuffix "Spec" property.javaType ) swagger.properties)); swagger = fetchSpecs spec; definitions = genDefinitions swagger; in pkgs.writeText "gen.nix" "# This file was generated with kubenix k8s generator, do not edit {lib, config, ... }: with lib; let types = lib.types // rec { str = mkOptionType { name = \"str\"; description = \"string\"; check = isString; merge = mergeEqualOption; }; # Either value of type `finalType` or `coercedType`, the latter is # converted to `finalType` using `coerceFunc`. coercedTo = coercedType: coerceFunc: finalType: mkOptionType rec { name = \"coercedTo\"; description = \"\${finalType.description} or \${coercedType.description}\"; check = x: finalType.check x || coercedType.check x; merge = loc: defs: let coerceVal = val: if finalType.check val then val else let coerced = coerceFunc val; in assert finalType.check coerced; coerced; in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); getSubOptions = finalType.getSubOptions; getSubModules = finalType.getSubModules; substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); typeMerge = t1: t2: null; functor = (defaultFunctor name) // { wrapped = finalType; }; }; }; mkOptionDefault = mkOverride 1001; extraOptions = { kubenix = {}; }; mergeValuesByKey = mergeKey: values: listToAttrs (map (value: nameValuePair ( if isAttrs value.\${mergeKey} then toString value.\${mergeKey}.content else (toString value.\${mergeKey}) ) value) values); submoduleOf = ref: types.submodule ({name, ...}: { options = definitions.\"\${ref}\".options; config = definitions.\"\${ref}\".config; }); submoduleWithMergeOf = ref: mergeKey: types.submodule ({name, ...}: let convertName = name: if definitions.\"\${ref}\".options.\${mergeKey}.type == types.int then toInt name else name; in { options = definitions.\"\${ref}\".options; config = definitions.\"\${ref}\".config // { \${mergeKey} = mkOverride 1002 (convertName name); }; }); submoduleForDefinition = ref: resource: kind: group: version: types.submodule ({name, ...}: { options = definitions.\"\${ref}\".options // extraOptions; config = mkMerge ([ definitions.\"\${ref}\".config { kind = mkOptionDefault kind; apiVersion = mkOptionDefault version; # metdata.name cannot use option default, due deep config metadata.name = mkOptionDefault name; } ] ++ (config.defaults.\${resource} or []) ++ (config.defaults.all or [])); }); coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: (types.coercedTo (types.listOf (submoduleOf ref)) (mergeValuesByKey mergeKey) (types.attrsOf (submoduleWithMergeOf ref mergeKey)) ); definitions = { ${concatStrings (mapAttrsToList (name: value: " \"${name}\" = {${optionalString (hasAttr "options" value) " options = {${concatStrings (mapAttrsToList (name: value: " \"${name}\" = ${value}; ") value.options)}}; "}${optionalString (hasAttr "config" value) " config = {${concatStrings (mapAttrsToList (name: value: " \"${name}\" = ${value}; ") value.config)}}; "}}; ") definitions)} }; in { kubernetes.customResources = [ ${concatMapStrings (resource: ''{ group = "${resource.group}"; version = "${resource.version}"; kind = "${resource.kind}"; description = ""; module = definitions."${resource.ref}"; }'') (genResources swagger)} ]; } "