From a48df5b993165184471cf40a24f7afa3f5b9b239 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Wed, 1 May 2024 23:16:17 +0200 Subject: [PATCH] Add package function-schema and module-schema. Add check for module jsonschema. --- checks/flake-module.nix | 3 +- checks/schemas.nix | 48 ----------------- clanModules/flake-module.nix | 1 - clanModules/syncthing/default.nix | 4 ++ docs/mkdocs.yml | 1 - docs/nix/get-module-docs.nix | 15 +----- lib/default.nix | 2 +- lib/description.nix | 19 +++++++ lib/jsonschema/default.nix | 51 +++++++++++++----- nixosModules/clanCore/metadata.nix | 4 +- {clanModules => nixosModules}/waypipe.nix | 0 pkgs/flake-module.nix | 1 + pkgs/schemas/flake-module.nix | 66 +++++++++++++++++++++++ 13 files changed, 134 insertions(+), 81 deletions(-) delete mode 100644 checks/schemas.nix create mode 100644 lib/description.nix rename {clanModules => nixosModules}/waypipe.nix (100%) create mode 100644 pkgs/schemas/flake-module.nix diff --git a/checks/flake-module.nix b/checks/flake-module.nix index 7366db4a..efb3d1fe 100644 --- a/checks/flake-module.nix +++ b/checks/flake-module.nix @@ -46,7 +46,6 @@ syncthing = import ./syncthing nixosTestArgs; wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs; }; - schemaTests = pkgs.callPackages ./schemas.nix { inherit self; }; flakeOutputs = lib.mapAttrs' ( @@ -58,7 +57,7 @@ self'.legacyPackages.homeConfigurations or { } ); in - { inherit renderClanOptions; } // nixosTests // schemaTests // flakeOutputs; + { inherit renderClanOptions; } // nixosTests // flakeOutputs; legacyPackages = { nixosTests = let diff --git a/checks/schemas.nix b/checks/schemas.nix deleted file mode 100644 index 5307cc35..00000000 --- a/checks/schemas.nix +++ /dev/null @@ -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 diff --git a/clanModules/flake-module.nix b/clanModules/flake-module.nix index 83b35809..8578e179 100644 --- a/clanModules/flake-module.nix +++ b/clanModules/flake-module.nix @@ -21,7 +21,6 @@ root-password = ./root-password; thelounge = ./thelounge.nix; vm-user = ./vm-user.nix; - waypipe = ./waypipe.nix; xfce = ./xfce.nix; xfce-vm = ./xfce-vm.nix; zt-tcp-relay = ./zt-tcp-relay.nix; diff --git a/clanModules/syncthing/default.nix b/clanModules/syncthing/default.nix index a5bd2d97..07f18a2c 100644 --- a/clanModules/syncthing/default.nix +++ b/clanModules/syncthing/default.nix @@ -34,6 +34,10 @@ ''; type = lib.types.listOf lib.types.str; default = [ ]; + example = [ + "folder1" + "folder2" + ]; }; }; diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c7fe4868..70e456fc 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -68,7 +68,6 @@ nav: - reference/clanModules/syncthing.md - reference/clanModules/thelounge.md - reference/clanModules/vm-user.md - - reference/clanModules/waypipe.md - reference/clanModules/xfce-vm.md - reference/clanModules/xfce.md - reference/clanModules/zt-tcp-relay.md diff --git a/docs/nix/get-module-docs.nix b/docs/nix/get-module-docs.nix index 05e02766..25e67a88 100644 --- a/docs/nix/get-module-docs.nix +++ b/docs/nix/get-module-docs.nix @@ -38,20 +38,7 @@ let ) clanModules; clanModulesReadmes = builtins.mapAttrs ( - module_name: _module: - 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 + module_name: _module: self.lib.modules.getDescription module_name ) clanModules; # clanCore docs diff --git a/lib/default.nix b/lib/default.nix index 58e95e79..4f5558ce 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -6,6 +6,6 @@ }: { jsonschema = import ./jsonschema { inherit lib; }; - + modules = import ./description.nix { inherit clan-core; }; buildClan = import ./build-clan { inherit clan-core lib nixpkgs; }; } diff --git a/lib/description.nix b/lib/description.nix new file mode 100644 index 00000000..0cd30218 --- /dev/null +++ b/lib/description.nix @@ -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; +} diff --git a/lib/jsonschema/default.nix b/lib/jsonschema/default.nix index 5ecda439..66f9b8fe 100644 --- a/lib/jsonschema/default.nix +++ b/lib/jsonschema/default.nix @@ -17,7 +17,10 @@ let 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); @@ -67,6 +70,10 @@ rec { option: let 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 = option.description.text or option.description; }; @@ -93,7 +100,7 @@ rec { ]; optionsList = filterExcluded optionsList'; in - default // description // { anyOf = map parseOption optionsList; } + default // example // description // { anyOf = map parseOption optionsList; } # handle nested options (not a submodule) else if !option ? _type then @@ -116,6 +123,7 @@ rec { }; in default + // example // description // { anyOf = [ @@ -128,63 +136,77 @@ rec { option.type.name == "bool" # return jsonschema property definition for bool then - default // description // { type = "boolean"; } + default // example // description // { type = "boolean"; } # parse float else if option.type.name == "float" # return jsonschema property definition for float then - default // description // { type = "number"; } + default // example // description // { type = "number"; } # parse int else if (option.type.name == "int" || option.type.name == "positiveInt") # return jsonschema property definition for int 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 else if option.type.name == "str" # return jsonschema property definition for string 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 else if option.type.name == "path" # return jsonschema property definition for path then - default // description // { type = "string"; } + default // example // description // { type = "string"; } # parse anything else if option.type.name == "anything" # return jsonschema property definition for anything then - default // description // { type = allBasicTypes; } + default // example // description // { type = allBasicTypes; } # parse unspecified else if option.type.name == "unspecified" # return jsonschema property definition for unspecified then - default // description // { type = allBasicTypes; } + default // example // description // { type = allBasicTypes; } # parse raw else if option.type.name == "raw" # return jsonschema property definition for raw then - default // description // { type = allBasicTypes; } + default // example // description // { type = allBasicTypes; } # parse enum else if option.type.name == "enum" # return jsonschema property definition for enum then - default // description // { enum = option.type.functor.payload; } + default // example // description // { enum = option.type.functor.payload; } # parse listOf submodule else if @@ -192,6 +214,7 @@ rec { # return jsonschema property definition for listOf submodule then default + // example // description // { type = "array"; @@ -211,6 +234,7 @@ rec { }; in default + // example // description // { type = "array"; @@ -222,7 +246,7 @@ rec { (option.type.name == "listOf") && (option.type.functor.wrapped.name == "unspecified") # return jsonschema property definition for list then - default // description // { type = "array"; } + default // example // description // { type = "array"; } # parse attrsOf submodule else if @@ -230,6 +254,7 @@ rec { # return jsonschema property definition for attrsOf submodule then default + // example // description // { type = "object"; @@ -242,6 +267,7 @@ rec { # return jsonschema property definition for attrs then default + // example // description // { type = "object"; @@ -262,6 +288,7 @@ rec { }; in default + // example // description // { type = "object"; diff --git a/nixosModules/clanCore/metadata.nix b/nixosModules/clanCore/metadata.nix index d92e5791..ce12633a 100644 --- a/nixosModules/clanCore/metadata.nix +++ b/nixosModules/clanCore/metadata.nix @@ -22,8 +22,8 @@ ''; }; clanDir = lib.mkOption { - type = lib.types.either lib.types.path lib.types.str; - default = "."; + type = lib.types.path; + default = ./.; description = '' the location of the flake repo, used to calculate the location of facts and secrets ''; diff --git a/clanModules/waypipe.nix b/nixosModules/waypipe.nix similarity index 100% rename from clanModules/waypipe.nix rename to nixosModules/waypipe.nix diff --git a/pkgs/flake-module.nix b/pkgs/flake-module.nix index 6964d535..bea7c2dd 100644 --- a/pkgs/flake-module.nix +++ b/pkgs/flake-module.nix @@ -4,6 +4,7 @@ ./clan-cli/flake-module.nix ./clan-vm-manager/flake-module.nix ./installer/flake-module.nix + ./schemas/flake-module.nix ]; perSystem = diff --git a/pkgs/schemas/flake-module.nix b/pkgs/schemas/flake-module.nix new file mode 100644 index 00000000..c0be5826 --- /dev/null +++ b/pkgs/schemas/flake-module.nix @@ -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 + ''; + }; + }; +}