{ name, pkgs, lib, spec, }: 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"))); }; refType = attr: head (tail (tail (splitString "/" attr."$ref"))); compareVersions = ver1: ver2: let getVersion = v: substring 1 10 v; splitVersion = v: builtins.splitVersion (getVersion v); isAlpha = v: elem "alpha" (splitVersion v); patchVersion = v: if isAlpha v then "" else if length (splitVersion v) == 1 then "${getVersion v}prod" else getVersion v; v1 = patchVersion ver1; v2 = patchVersion ver2; in builtins.compareVersions v1 v2; 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 {} # in other case it's an actual 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)); } # 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; mapCharPairs = f: s1: s2: concatStrings (imap0 ( i: c1: f i c1 ( if i >= stringLength s2 then "" else elemAt (stringToCharacters s2) i ) ) (stringToCharacters s1)); getAttrName = resource: kind: mapCharPairs ( i: c1: c2: if hasPrefix "API" kind && i == 0 then "A" else if i == 0 then c1 else if c2 == "" || (toLower c2) != c1 then c1 else c2 ) resource kind; genResourceTypes = swagger: mapAttrs' (name: path: let ref = refType (head path.post.parameters).schema; group' = path.post."x-kubernetes-group-version-kind".group; version' = path.post."x-kubernetes-group-version-kind".version; kind' = path.post."x-kubernetes-group-version-kind".kind; name' = last (splitString "/" name); attrName = getAttrName name' kind'; in nameValuePair ref { inherit ref attrName; name = name'; group = if group' == "" then "core" else group'; version = version'; kind = kind'; description = swagger.definitions.${ref}.description; defintion = refDefinition (head path.post.parameters).schema; }) (filterAttrs ( _name: path: hasAttr "post" path && path.post."x-kubernetes-action" == "post" ) swagger.paths); swagger = fetchSpecs spec; definitions = genDefinitions swagger; resourceTypes = genResourceTypes swagger; resourceTypesByKind = zipAttrs (mapAttrsToList (_name: resourceType: { ${resourceType.kind} = resourceType; }) resourceTypes); resourcesTypesByKindSortByVersion = mapAttrs ( _kind: resourceTypes: reverseList (sort ( r1: r2: compareVersions r1.version r2.version > 0 ) resourceTypes) ) resourceTypesByKind; latestResourceTypesByKind = mapAttrs (_kind: resources: last resources) resourcesTypesByKindSortByVersion; genResourceOptions = resource: with gen; let submoduleForDefinition' = definition: submoduleForDefinition definition.ref definition.name definition.kind definition.group definition.version; in mkOption { description = resource.description; type = types.attrsOf (submoduleForDefinition' resource); default = {}; }; generated = '' # This file was generated with kubenix k8s generator, do not edit { lib, options, config, ... }: with lib; let getDefaults = resource: group: version: kind: catAttrs "default" (filter (default: (default.resource == null || default.resource == resource) && (default.group == null || default.group == group) && (default.version == null || default.version == version) && (default.kind == null || default.kind == kind) ) config.defaults); 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; 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 or {}; config = definitions."''${ref}".config or {}; }); 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: let apiVersion = if group == "core" then version else "''${group}/''${version}"; in types.submodule ({name, ...}: { imports = getDefaults resource group version kind; options = definitions."''${ref}".options; config = mkMerge [ definitions."''${ref}".config { kind = mkOptionDefault kind; apiVersion = mkOptionDefault apiVersion; # metdata.name cannot use option default, due deep config metadata.name = mkOptionDefault name; } ]; }); 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 { # all resource versions options = { resources = { ${concatStrings (mapAttrsToList (_: rt: '' "${rt.group}"."${rt.version}"."${rt.kind}" = ${genResourceOptions rt}; '') resourceTypes)} } // { ${concatStrings (mapAttrsToList (_: rt: '' "${rt.attrName}" = ${genResourceOptions rt}; '') latestResourceTypesByKind)} }; }; config = { # expose resource definitions inherit definitions; # register resource types types = [${concatStrings (mapAttrsToList (_: rt: '' { name = "${rt.name}"; group = "${rt.group}"; version = "${rt.version}"; kind = "${rt.kind}"; attrName = "${rt.attrName}"; }'') resourceTypes)}]; resources = { ${concatStrings (mapAttrsToList (_: rt: '' "${rt.group}"."${rt.version}"."${rt.kind}" = mkAliasDefinitions options.resources."${rt.attrName}"; '') latestResourceTypesByKind)} }; }; } ''; in pkgs.runCommand "k8s-${name}-gen.nix" { buildInputs = [pkgs.nixpkgs-fmt]; } '' cat << 'GENERATED' > ./raw "${generated}" GENERATED nixpkgs-fmt ./raw cp ./raw $out ''