1
0
forked from clan/clan-core

Add package function-schema and module-schema. Add check for module jsonschema.

This commit is contained in:
Luis Hebendanz 2024-05-01 23:16:17 +02:00
parent e08342a6f3
commit a48df5b993
13 changed files with 134 additions and 81 deletions

View File

@ -46,7 +46,6 @@
syncthing = import ./syncthing nixosTestArgs; syncthing = import ./syncthing nixosTestArgs;
wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs; wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs;
}; };
schemaTests = pkgs.callPackages ./schemas.nix { inherit self; };
flakeOutputs = flakeOutputs =
lib.mapAttrs' ( lib.mapAttrs' (
@ -58,7 +57,7 @@
self'.legacyPackages.homeConfigurations or { } self'.legacyPackages.homeConfigurations or { }
); );
in in
{ inherit renderClanOptions; } // nixosTests // schemaTests // flakeOutputs; { inherit renderClanOptions; } // nixosTests // flakeOutputs;
legacyPackages = { legacyPackages = {
nixosTests = nixosTests =
let let

View File

@ -1,48 +0,0 @@
{
self,
runCommand,
check-jsonschema,
pkgs,
lib,
...
}:
let
clanModules.clanCore = self.nixosModules.clanCore;
baseModule = {
imports = (import (pkgs.path + "/nixos/modules/module-list.nix")) ++ [
{
nixpkgs.hostPlatform = "x86_64-linux";
clanCore.clanName = "dummy";
}
];
};
optionsFromModule =
module:
let
evaled = lib.evalModules {
modules = [
module
baseModule
];
};
in
evaled.options.clan;
clanModuleSchemas = lib.mapAttrs (
_: module: self.lib.jsonschema.parseOptions (optionsFromModule module)
) clanModules;
mkTest =
name: schema:
runCommand "schema-${name}" { } ''
${check-jsonschema}/bin/check-jsonschema \
--check-metaschema ${builtins.toFile "schema-${name}" (builtins.toJSON schema)}
touch $out
'';
in
lib.mapAttrs' (name: schema: {
name = "schema-${name}";
value = mkTest name schema;
}) clanModuleSchemas

View File

@ -21,7 +21,6 @@
root-password = ./root-password; root-password = ./root-password;
thelounge = ./thelounge.nix; thelounge = ./thelounge.nix;
vm-user = ./vm-user.nix; vm-user = ./vm-user.nix;
waypipe = ./waypipe.nix;
xfce = ./xfce.nix; xfce = ./xfce.nix;
xfce-vm = ./xfce-vm.nix; xfce-vm = ./xfce-vm.nix;
zt-tcp-relay = ./zt-tcp-relay.nix; zt-tcp-relay = ./zt-tcp-relay.nix;

View File

@ -34,6 +34,10 @@
''; '';
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ ]; default = [ ];
example = [
"folder1"
"folder2"
];
}; };
}; };

View File

@ -68,7 +68,6 @@ nav:
- reference/clanModules/syncthing.md - reference/clanModules/syncthing.md
- reference/clanModules/thelounge.md - reference/clanModules/thelounge.md
- reference/clanModules/vm-user.md - reference/clanModules/vm-user.md
- reference/clanModules/waypipe.md
- reference/clanModules/xfce-vm.md - reference/clanModules/xfce-vm.md
- reference/clanModules/xfce.md - reference/clanModules/xfce.md
- reference/clanModules/zt-tcp-relay.md - reference/clanModules/zt-tcp-relay.md

View File

@ -38,20 +38,7 @@ let
) clanModules; ) clanModules;
clanModulesReadmes = builtins.mapAttrs ( clanModulesReadmes = builtins.mapAttrs (
module_name: _module: module_name: _module: self.lib.modules.getDescription module_name
let
readme = "${self}/clanModules/${module_name}/README.md";
readmeContents =
if
builtins.trace "Trying to get Module README.md for ${module_name} from ${readme}"
# TODO: Edge cases
(builtins.pathExists readme)
then
(builtins.readFile readme)
else
null;
in
readmeContents
) clanModules; ) clanModules;
# clanCore docs # clanCore docs

View File

@ -6,6 +6,6 @@
}: }:
{ {
jsonschema = import ./jsonschema { inherit lib; }; jsonschema = import ./jsonschema { inherit lib; };
modules = import ./description.nix { inherit clan-core; };
buildClan = import ./build-clan { inherit clan-core lib nixpkgs; }; buildClan = import ./build-clan { inherit clan-core lib nixpkgs; };
} }

19
lib/description.nix Normal file
View File

@ -0,0 +1,19 @@
{ clan-core, ... }:
{
getDescription =
modulename:
let
readme = "${clan-core}/clanModules/${modulename}/README.md";
readmeContents =
if
builtins.trace "Trying to get Module README.md for ${modulename} from ${readme}"
# TODO: Edge cases
(builtins.pathExists readme)
then
(builtins.readFile readme)
else
null;
in
readmeContents;
}

View File

@ -17,7 +17,10 @@ let
location: ${lib.concatStringsSep "." option.loc} location: ${lib.concatStringsSep "." option.loc}
''; '';
isExcludedOption = option: (lib.elem (option.type.name or null) excludedTypes); # Exclude the option if it's type is in the excludedTypes list
# or if the option has a defaultText attribute
isExcludedOption =
option: ((lib.elem (option.type.name or null) excludedTypes) || (option ? defaultText));
filterExcluded = lib.filter (opt: !isExcludedOption opt); filterExcluded = lib.filter (opt: !isExcludedOption opt);
@ -67,6 +70,10 @@ rec {
option: option:
let let
default = lib.optionalAttrs (option ? default) { inherit (option) default; }; default = lib.optionalAttrs (option ? default) { inherit (option) default; };
example = lib.optionalAttrs (option ? example) {
examples =
if (builtins.typeOf option.example) == "list" then option.example else [ option.example ];
};
description = lib.optionalAttrs (option ? description) { description = lib.optionalAttrs (option ? description) {
description = option.description.text or option.description; description = option.description.text or option.description;
}; };
@ -93,7 +100,7 @@ rec {
]; ];
optionsList = filterExcluded optionsList'; optionsList = filterExcluded optionsList';
in in
default // description // { anyOf = map parseOption optionsList; } default // example // description // { anyOf = map parseOption optionsList; }
# handle nested options (not a submodule) # handle nested options (not a submodule)
else if !option ? _type then else if !option ? _type then
@ -116,6 +123,7 @@ rec {
}; };
in in
default default
// example
// description // description
// { // {
anyOf = [ anyOf = [
@ -128,63 +136,77 @@ rec {
option.type.name == "bool" option.type.name == "bool"
# return jsonschema property definition for bool # return jsonschema property definition for bool
then then
default // description // { type = "boolean"; } default // example // description // { type = "boolean"; }
# parse float # parse float
else if else if
option.type.name == "float" option.type.name == "float"
# return jsonschema property definition for float # return jsonschema property definition for float
then then
default // description // { type = "number"; } default // example // description // { type = "number"; }
# parse int # parse int
else if else if
(option.type.name == "int" || option.type.name == "positiveInt") (option.type.name == "int" || option.type.name == "positiveInt")
# return jsonschema property definition for int # return jsonschema property definition for int
then then
default // description // { type = "integer"; } default // example // description // { type = "integer"; }
# TODO: Add support for intMatching in jsonschema
# parse port type aka. "unsignedInt16"
else if option.type.name == "unsignedInt16" then
default // example // description // { type = "integer"; }
# parse string # parse string
else if else if
option.type.name == "str" option.type.name == "str"
# return jsonschema property definition for string # return jsonschema property definition for string
then then
default // description // { type = "string"; } default // example // description // { type = "string"; }
# TODO: Add support for stringMatching in jsonschema
# parse stringMatching
else if lib.strings.hasPrefix "strMatching" option.type.name then
default // example // description // { type = "string"; }
# TODO: Add support for separatedString in jsonschema
else if lib.strings.hasPrefix "separatedString" option.type.name then
default // example // description // { type = "string"; }
# parse string # parse string
else if else if
option.type.name == "path" option.type.name == "path"
# return jsonschema property definition for path # return jsonschema property definition for path
then then
default // description // { type = "string"; } default // example // description // { type = "string"; }
# parse anything # parse anything
else if else if
option.type.name == "anything" option.type.name == "anything"
# return jsonschema property definition for anything # return jsonschema property definition for anything
then then
default // description // { type = allBasicTypes; } default // example // description // { type = allBasicTypes; }
# parse unspecified # parse unspecified
else if else if
option.type.name == "unspecified" option.type.name == "unspecified"
# return jsonschema property definition for unspecified # return jsonschema property definition for unspecified
then then
default // description // { type = allBasicTypes; } default // example // description // { type = allBasicTypes; }
# parse raw # parse raw
else if else if
option.type.name == "raw" option.type.name == "raw"
# return jsonschema property definition for raw # return jsonschema property definition for raw
then then
default // description // { type = allBasicTypes; } default // example // description // { type = allBasicTypes; }
# parse enum # parse enum
else if else if
option.type.name == "enum" option.type.name == "enum"
# return jsonschema property definition for enum # return jsonschema property definition for enum
then then
default // description // { enum = option.type.functor.payload; } default // example // description // { enum = option.type.functor.payload; }
# parse listOf submodule # parse listOf submodule
else if else if
@ -192,6 +214,7 @@ rec {
# return jsonschema property definition for listOf submodule # return jsonschema property definition for listOf submodule
then then
default default
// example
// description // description
// { // {
type = "array"; type = "array";
@ -211,6 +234,7 @@ rec {
}; };
in in
default default
// example
// description // description
// { // {
type = "array"; type = "array";
@ -222,7 +246,7 @@ rec {
(option.type.name == "listOf") && (option.type.functor.wrapped.name == "unspecified") (option.type.name == "listOf") && (option.type.functor.wrapped.name == "unspecified")
# return jsonschema property definition for list # return jsonschema property definition for list
then then
default // description // { type = "array"; } default // example // description // { type = "array"; }
# parse attrsOf submodule # parse attrsOf submodule
else if else if
@ -230,6 +254,7 @@ rec {
# return jsonschema property definition for attrsOf submodule # return jsonschema property definition for attrsOf submodule
then then
default default
// example
// description // description
// { // {
type = "object"; type = "object";
@ -242,6 +267,7 @@ rec {
# return jsonschema property definition for attrs # return jsonschema property definition for attrs
then then
default default
// example
// description // description
// { // {
type = "object"; type = "object";
@ -262,6 +288,7 @@ rec {
}; };
in in
default default
// example
// description // description
// { // {
type = "object"; type = "object";

View File

@ -22,8 +22,8 @@
''; '';
}; };
clanDir = lib.mkOption { clanDir = lib.mkOption {
type = lib.types.either lib.types.path lib.types.str; type = lib.types.path;
default = "."; default = ./.;
description = '' description = ''
the location of the flake repo, used to calculate the location of facts and secrets the location of the flake repo, used to calculate the location of facts and secrets
''; '';

View File

@ -4,6 +4,7 @@
./clan-cli/flake-module.nix ./clan-cli/flake-module.nix
./clan-vm-manager/flake-module.nix ./clan-vm-manager/flake-module.nix
./installer/flake-module.nix ./installer/flake-module.nix
./schemas/flake-module.nix
]; ];
perSystem = perSystem =

View File

@ -0,0 +1,66 @@
{ self, ... }:
{
perSystem =
{ pkgs, lib, ... }:
let
clanModules = self.clanModules;
# Uncomment if you only want one module to be available
# clanModules = {
# syncthing = self.clanModules.syncthing;
# };
baseModule = {
imports = (import (pkgs.path + "/nixos/modules/module-list.nix")) ++ [
{
nixpkgs.hostPlatform = "x86_64-linux";
clanCore.clanName = "dummy";
}
];
};
optionsFromModule =
modulename: module:
let
evaled = lib.evalModules {
modules = [
module
baseModule
self.nixosModules.clanCore
];
};
in
if (evaled.options.clan ? "${modulename}") then evaled.options.clan.${modulename} else { };
clanModuleSchemas = lib.mapAttrs (
modulename: module: self.lib.jsonschema.parseOptions (optionsFromModule modulename module)
) clanModules;
clanModuleFunctionSchemas = lib.mapAttrsFlatten (modulename: module: {
name = modulename;
description = self.lib.modules.getDescription modulename;
parameters = self.lib.jsonschema.parseOptions (optionsFromModule modulename module);
}) clanModules;
in
rec {
checks = {
module-schema = pkgs.runCommand "schema-checks" { } ''
${pkgs.check-jsonschema}/bin/check-jsonschema \
--check-metaschema ${packages.module-schema}
touch $out
'';
};
packages = {
module-schema = pkgs.runCommand "jsonschema" { } ''
MSCHEMA=${builtins.toFile "module-schemas.json" (builtins.toJSON clanModuleSchemas)}
cp "$MSCHEMA" $out
'';
function-schema = pkgs.runCommand "function-schema" { } ''
FSCHEMA=${builtins.toFile "function-schemas.json" (builtins.toJSON clanModuleFunctionSchemas)}
cp "$FSCHEMA" $out
'';
};
};
}