From b3164076d55d63c6549a917a4c831d85bdd52f1b Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Wed, 20 Nov 2024 11:01:05 +0100 Subject: [PATCH] Docs/modules: seperate out inventory,role specific interface --- clanModules/importer/default.nix | 4 - clanModules/zerotier/default.nix | 2 - docs/mkdocs.yml | 3 +- docs/nix/flake-module.nix | 15 ++- docs/nix/get-module-docs.nix | 25 ++++- docs/nix/render_options/__init__.py | 148 +++++++++++++++++++++------- 6 files changed, 145 insertions(+), 52 deletions(-) delete mode 100644 clanModules/importer/default.nix delete mode 100644 clanModules/zerotier/default.nix diff --git a/clanModules/importer/default.nix b/clanModules/importer/default.nix deleted file mode 100644 index 7ee1167ac..000000000 --- a/clanModules/importer/default.nix +++ /dev/null @@ -1,4 +0,0 @@ -# Dont import this file -# It is only here for backwards compatibility. -# Dont author new modules with this file. -{ } diff --git a/clanModules/zerotier/default.nix b/clanModules/zerotier/default.nix deleted file mode 100644 index 8007b6f11..000000000 --- a/clanModules/zerotier/default.nix +++ /dev/null @@ -1,2 +0,0 @@ -# TODO: only kept this file to not break documentation generation. -{ } diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 3331bff0c..742e11969 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -66,6 +66,8 @@ nav: - Reference: - Overview: reference/index.md - Clan Modules: + - reference/clanModules/index.md + # This is the module overview and should stay at the top - reference/clanModules/admin.md - reference/clanModules/borgbackup-static.md - reference/clanModules/borgbackup.md @@ -78,7 +80,6 @@ nav: - reference/clanModules/golem-provider.md - reference/clanModules/heisenbridge.md - reference/clanModules/importer.md - - reference/clanModules/index.md - reference/clanModules/iwd.md - reference/clanModules/localbackup.md - reference/clanModules/localsend.md diff --git a/docs/nix/flake-module.nix b/docs/nix/flake-module.nix index 07e6cdf60..a4523c5d6 100644 --- a/docs/nix/flake-module.nix +++ b/docs/nix/flake-module.nix @@ -14,6 +14,7 @@ jsonDocs = pkgs.callPackage ./get-module-docs.nix { inherit (self) clanModules; evalClanModules = self.lib.evalClanModules; + modulesRolesOptions = self.lib.evalClanModulesWithRoles self.clanModules; }; # Frontmatter for clanModules @@ -23,9 +24,11 @@ in docs.optionsJSON; - clanModulesFileInfo = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModules); - # clanModulesReadmes = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesReadmes); - # clanModulesMeta = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesMeta); + # Options available via ` imports = [ clanModules.${moduleName} ]; ` (Direct nix import) + clanModulesViaNix = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesViaNix); + + # Options available when imported via ` inventory.${moduleName}....${rolesName} ` + clanModulesViaRoles = pkgs.writeText "info.json" (builtins.toJSON jsonDocs.clanModulesViaRoles); # Simply evaluated options (JSON) renderOptions = @@ -69,7 +72,8 @@ export CLAN_CORE_PATH=${self} export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json # A file that contains the links to all clanModule docs - export CLAN_MODULES=${clanModulesFileInfo} + export CLAN_MODULES_VIA_ROLES=${clanModulesViaRoles} + export CLAN_MODULES_VIA_NIX=${clanModulesViaNix} # Frontmatter format for clanModules export CLAN_MODULES_FRONTMATTER_DOCS=${clanModulesFrontmatter}/share/doc/nixos/options.json @@ -83,6 +87,9 @@ ''; in { + legacyPackages = { + inherit jsonDocs clanModulesViaNix clanModulesViaRoles; + }; devShells.docs = pkgs.callPackage ./shell.nix { inherit (self'.packages) docs clan-cli-docs inventory-api-docs; inherit diff --git a/docs/nix/get-module-docs.nix b/docs/nix/get-module-docs.nix index d78d9be6f..3f4972e85 100644 --- a/docs/nix/get-module-docs.nix +++ b/docs/nix/get-module-docs.nix @@ -1,4 +1,5 @@ { + modulesRolesOptions, nixosOptionsDoc, clanModules, evalClanModules, @@ -6,14 +7,28 @@ }: { # clanModules docs - clanModules = lib.mapAttrs ( + clanModulesViaNix = lib.mapAttrs ( name: module: - (nixosOptionsDoc { - options = ((evalClanModules [ module ]).options).clan.${name} or { }; - warningsAreErrors = true; - }).optionsJSON + if builtins.pathExists (module + "/default.nix") then + (nixosOptionsDoc { + options = ((evalClanModules [ module ]).options).clan.${name} or { }; + warningsAreErrors = true; + }).optionsJSON + else + { } ) clanModules; + clanModulesViaRoles = lib.mapAttrs ( + _moduleName: rolesOptions: + lib.mapAttrs ( + _roleName: options: + (nixosOptionsDoc { + inherit options; + warningsAreErrors = true; + }).optionsJSON + ) rolesOptions + ) modulesRolesOptions; + clanCore = (nixosOptionsDoc { options = ((evalClanModules [ ]).options).clan.core or { }; diff --git a/docs/nix/render_options/__init__.py b/docs/nix/render_options/__init__.py index 90bec6042..974e6128b 100644 --- a/docs/nix/render_options/__init__.py +++ b/docs/nix/render_options/__init__.py @@ -35,9 +35,15 @@ from clan_cli.errors import ClanError CLAN_CORE_PATH = Path(os.environ["CLAN_CORE_PATH"]) CLAN_CORE_DOCS = Path(os.environ["CLAN_CORE_DOCS"]) CLAN_MODULES_FRONTMATTER_DOCS = os.environ.get("CLAN_MODULES_FRONTMATTER_DOCS") -CLAN_MODULES = os.environ.get("CLAN_MODULES") BUILD_CLAN_PATH = os.environ.get("BUILD_CLAN_PATH") +## Clan modules ## +# Some modules can be imported via nix natively +CLAN_MODULES_VIA_NIX = os.environ.get("CLAN_MODULES_VIA_NIX") +# Some modules can be imported via inventory +CLAN_MODULES_VIA_ROLES = os.environ.get("CLAN_MODULES_VIA_ROLES") + + OUT = os.environ.get("out") @@ -130,15 +136,28 @@ def render_option( return res +def print_options(options_file: str, head: str, no_options: str) -> str: + res = "" + with (Path(options_file) / "share/doc/nixos/options.json").open() as f: + options: dict[str, dict[str, Any]] = json.load(f) + + res += head if len(options.items()) else no_options + for option_name, info in options.items(): + res += render_option(option_name, info, 4) + return res + + def module_header(module_name: str, has_inventory_feature: bool = False) -> str: indicator = " 🔹" if has_inventory_feature else "" return f"# {module_name}{indicator}\n\n" -def module_usage(module_name: str) -> str: - return f"""## Usage +def module_nix_usage(module_name: str) -> str: + return f"""## Usage via Nix -To use this module, import it like th: +**This module can be also imported directly in your nixos configuration. Although it is recommended to use the [inventory](../../reference/nix-api/inventory.md) interface if available.** + +Some modules are considered 'low-level' or 'expert modules' and are not available via the inventory interface. ```nix {{config, lib, inputs, ...}}: {{ @@ -146,6 +165,7 @@ To use this module, import it like th: # ... }} ``` + """ @@ -153,7 +173,11 @@ clan_core_descr = """`clan.core` is always included in each machine `config`. Your can customize your machines behavior with the configuration [options](#module-options) provided below. """ -options_head = "\n## Module Options\n" +options_head = """ +### Module Options + +The following options are available for this module. +""" def produce_clan_modules_frontmatter_docs() -> None: @@ -260,13 +284,16 @@ def render_roles(roles: list[str] | None, module_name: str) -> str: if roles: roles_list = "\n".join([f" - `{r}`" for r in roles]) return f""" -## Inventory Roles +### Roles -Predefined roles +This module can be used via predefined roles {roles_list} +Every role has its own configuration options. Which are each listed below. + For more information, see the [inventory guide](../../manual/inventory.md). + """ return "" @@ -295,8 +322,12 @@ def render_categories(categories: list[str], frontmatter: Frontmatter) -> str: def produce_clan_modules_docs() -> None: - if not CLAN_MODULES: - msg = f"Environment variables are not set correctly: $out={CLAN_MODULES}" + if not CLAN_MODULES_VIA_NIX: + msg = f"Environment variables are not set correctly: $CLAN_MODULES_VIA_NIX={CLAN_MODULES_VIA_NIX}" + raise ClanError(msg) + + if not CLAN_MODULES_VIA_ROLES: + msg = f"Environment variables are not set correctly: $CLAN_MODULES_VIA_ROLES={CLAN_MODULES_VIA_ROLES}" raise ClanError(msg) if not CLAN_CORE_PATH: @@ -307,15 +338,19 @@ def produce_clan_modules_docs() -> None: msg = f"Environment variables are not set correctly: $out={OUT}" raise ClanError(msg) - with Path(CLAN_MODULES).open() as f: - links: dict[str, str] = json.load(f) - modules_index = "# Modules Overview\n\n" modules_index += clan_modules_descr modules_index += "## Overview\n\n" modules_index += '
\n\n' + with Path(CLAN_MODULES_VIA_ROLES).open() as f2: + role_links: dict[str, dict[str, str]] = json.load(f2) + + with Path(CLAN_MODULES_VIA_NIX).open() as f: + links: dict[str, str] = json.load(f) + for module_name, options_file in links.items(): + print(f"Rendering {module_name}") readme_file = CLAN_CORE_PATH / "clanModules" / module_name / "README.md" with readme_file.open() as f: readme = f.read() @@ -324,38 +359,79 @@ def produce_clan_modules_docs() -> None: modules_index += build_option_card(module_name, frontmatter) - with (Path(options_file) / "share/doc/nixos/options.json").open() as f: - options: dict[str, dict[str, Any]] = json.load(f) - print(f"Rendering options for {module_name}...") - output = module_header(module_name, "inventory" in frontmatter.features) + ##### Print module documentation ##### - if frontmatter.description: - output += f"**{frontmatter.description}**\n\n" + # 1. Header + output = module_header(module_name, "inventory" in frontmatter.features) - output += "## Categories\n\n" - output += render_categories(frontmatter.categories, frontmatter) - output += "\n---\n\n" + # 2. Description from README.md + if frontmatter.description: + output += f"**{frontmatter.description}**\n\n" - output += f"{readme_content}\n" + # 3. Categories from README.md + output += "## Categories\n\n" + output += render_categories(frontmatter.categories, frontmatter) + output += "\n---\n\n" - # get_roles(str) -> list[str] | None - roles = get_roles(CLAN_CORE_PATH / "clanModules" / module_name) - if roles: - output += render_roles(roles, module_name) + # 3. README.md content + output += f"{readme_content}\n" - output += module_usage(module_name) + # 4. Usage + ##### Print usage via Inventory ##### - output += options_head if len(options.items()) else "" - for option_name, info in options.items(): - output += render_option(option_name, info) + # get_roles(str) -> list[str] | None + # if not isinstance(options_file, str): + roles = get_roles(CLAN_CORE_PATH / "clanModules" / module_name) + if roles: + # Render inventory usage + output += """## Usage via Inventory\n\n""" + output += render_roles(roles, module_name) + for role in roles: + role_options_file = role_links[module_name][role] + # Abort if the options file is not found + if not isinstance(role_options_file, str): + print( + f"Error: module: {module_name} in role: {role} - options file not found, Got {role_options_file}" + ) + exit(1) - outfile = Path(OUT) / f"clanModules/{module_name}.md" - outfile.parent.mkdir( - parents=True, - exist_ok=True, + no_options = f"**The `{module_name}` `{role}` doesnt offer / require any options to be set.**" + + heading = f"""### Options of `{role}` role + +The following options are available when using the `{role}` role. +""" + output += print_options(role_options_file, heading, no_options) + else: + # No roles means no inventory usage + output += """## Usage via Inventory + +**This module cannot be used via the inventory interface.** +""" + + ##### Print usage via Nix / nixos ##### + if not isinstance(options_file, str): + print( + f"Skipping {module_name}: Cannot be used via import clanModules.{module_name}" ) - with outfile.open("w") as of: - of.write(output) + output += """## Usage via Nix + +**This module cannot be imported directly in your nixos configuration.** + +""" + + else: + output += module_nix_usage(module_name) + no_options = "** This module doesnt require any options to be set.**" + output += print_options(options_file, options_head, no_options) + + outfile = Path(OUT) / f"clanModules/{module_name}.md" + outfile.parent.mkdir( + parents=True, + exist_ok=True, + ) + with outfile.open("w") as of: + of.write(output) modules_index += "
" modules_index += "\n"