diff --git a/docs/options.md b/docs/options.md new file mode 100644 index 0000000..ec7da12 --- /dev/null +++ b/docs/options.md @@ -0,0 +1,3 @@ +# Options + +{{ include_raw("options.md") }} diff --git a/lib/default.nix b/lib/default.nix index 2f311bc..716cb0b 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -2,155 +2,227 @@ lib, kubenix, ... -} @ attrs: -with lib; rec { - evalValues = file: {rawValues, ...} @ args: (lib.evalModules { - specialArgs = { - utils = import ./utils.nix attrs; - }; - modules = [ - file - (_: { - # pass through all args to the values.nix module - config = - rawValues - // { - _module.args = args; +} @ attrs: let + inherit (lib) mkOption types evalModules concatMapStringsSep assertMsg; + nixlet-lib = rec { + nixletModule = ./nixletModule.nix; + + evalValues = file: { + rawValues, + dependencies, + args, + check ? true, + ... + }: let + moduleArgs = + args + // { + utils = import ./utils.nix attrs; + }; + # get the values from the dependencies, then import them nested + # (so you can set postgres.replicaCount in values.nix for example when adding "postgres" as dependency) + extraModules = map (depName: { + options.${depName} = mkOption { + type = types.submodule { + imports = ["${dependencies.${depName}.path}/values.nix"]; + _module.args = + moduleArgs + // { + # make sure that dependencies see their own name and version etc. + nixlet = { + inherit (dependencies.${depName}) name version description; + inherit (moduleArgs.nixlet) project; + }; + }; }; - }) - ]; - }); - mkValues = file: args: (evalValues file args).config; + default = {}; + description = let + n = dependencies.${depName}; + in '' + Imported Nixlet as a dependency: - # wraps mkNixletInner to allow passing either a path or an attrset - mkNixlet = arg: - mkNixletInner ( - if (builtins.typeOf arg) == "set" - then arg - else - {path = arg;} - // ( - if builtins.pathExists "${arg}/nixlet.nix" - then (import "${arg}/nixlet.nix") - else throw "Nixlet at '${arg}' does not contain nixlet.nix and mkNixlet was called with just a path" - ) - ); + |Name|Version|Description| + |----|-------|-----------| + |${n.name}|${n.version}|${n.description}| + ''; + }; + }) (builtins.attrNames dependencies); + in + builtins.addErrorContext "[nixlets] while evaluating values" ( + evalModules { + modules = + [ + file + { + _module = { + args = moduleArgs; + inherit check; + }; + } + {config = rawValues;} + ] + ++ extraModules; + } + ); - mkNixletInner = { - path, - name, - version ? null, - description ? "", - defaultProject ? null, - ... - }: let - # every nixlet gets "nixlet" as arg with some useful data about itself - baseNixletArg = { - inherit name version description; - project = defaultProject; - }; - nixlet = { - inherit name version description path; - values = evalValues "${path}/values.nix" { - rawValues = {}; - nixlet = baseNixletArg; + # wraps mkNixletInner to allow passing either a path or an attrset + mkNixlet = arg: + mkNixletInner ( + if (builtins.typeOf arg) == "set" + then arg + else + {path = arg;} + // ( + if builtins.pathExists "${arg}/nixlet.nix" + then (import "${arg}/nixlet.nix") + else throw "Nixlet at '${arg}' does not contain nixlet.nix and mkNixlet was called with just a path" + ) + ); + + mkNixletInner = { + path, + name, + version ? null, + description ? "", + defaultProject ? null, + ... + }: let + # every nixlet gets "nixlet" as arg with some useful data about itself + baseNixletArg = { + inherit name version description; + project = defaultProject; }; - mkDocs = opts: mkDocs (opts // {inherit nixlet;}); - eval = { - system, - project ? defaultProject, - overrides ? (_: {}), - values ? {}, - }: - assert lib.assertMsg (project != null) "No default project set, please pass a project to the render method"; let - nixletArg = baseNixletArg // {inherit project;}; + nixlet = { + _type = "nixlet"; + inherit name version description path; + # just values of the current nixlet (lighweight) + values = evalValues "${path}/values.nix" { + rawValues = {}; + dependencies = {}; + # no checking since this doesn't include dependencies + check = false; + args.nixlet = baseNixletArg; + }; + # full values, including dependencies etc. (complex) + fullValues = args: let + evaled = nixlet.eval args; in - kubenix.evalModules.${system} { - module = {kubenix, ...}: { - imports = with kubenix.modules; [ - k8s - helm - docker - files - ./secretsModule.nix - (_: let - finalValues = mkValues "${path}/values.nix" { - rawValues = values; - nixlet = nixletArg; - }; - in { - imports = [path]; - _module.args.nixlet = - { - values = finalValues; - } - // nixletArg; - }) - overrides - ]; - kubenix.project = project; - }; + evalValues "${path}/values.nix" { + rawValues = {}; + inherit (evaled.config.nixlet) dependencies; + args.nixlet = baseNixletArg; }; - render = { - system, - project ? defaultProject, - overrides ? (_: {}), - values ? {}, - }: - (nixlet.eval { - inherit system project overrides values; - }) - .config - .kubernetes - .resultYAML; - # combines all secrets files in a single directory - secrets = args: (nixlet.eval args).config.kubernetes.secretsCombined; - }; - in - nixlet; + mkDocs = opts: mkDocs (opts // {inherit nixlet;}); + eval = { + system, + project ? defaultProject, + overrides ? (_: {}), + values ? {}, + }: + assert assertMsg (project != null) "No default project set, please pass a project to the eval/render method"; let + nixletArg = baseNixletArg // {inherit project;}; + in + builtins.addErrorContext "[nixlets] while evaluating nixlet ${name}" ( + kubenix.evalModules.${system} { + module = { + config, + kubenix, + ... + }: { + imports = with kubenix.modules; [ + k8s + helm + docker + files + ./secretsModule.nix + ./nixletModule.nix + (let + finalValues = + (evalValues "${path}/values.nix" { + rawValues = values; + inherit (config.nixlet) dependencies; + args.nixlet = nixletArg; + }).config; + in { + imports = [path]; + _module.args = { + nixlet = + { + values = finalValues; + } + // nixletArg; + inherit nixlet-lib system; + }; + }) + overrides + ]; + kubenix.project = project; + }; + } + ); + render = { + system, + project ? defaultProject, + overrides ? (_: {}), + values ? {}, + }: + (nixlet.eval { + inherit system project overrides values; + }) + .config + .kubernetes + .resultYAML; + # combines all secrets files in a single directory + secrets = args: (nixlet.eval args).config.kubernetes.secretsCombined; - fetchNixlet = url: sha256: mkNixlet (builtins.fetchTarball {inherit url sha256;}); - fetchNixletFromGitlab = { - project, - name, - version, - sha256, - }: let - projectEscaped = builtins.replaceStrings ["/"] ["%2F"] project; - in - fetchNixlet "https://gitlab.com/api/v4/projects/${projectEscaped}/packages/generic/${name}/${version}/${name}.tar.gz" sha256; + }; + in + nixlet; - uploadNixletsToGitlab = { - pkgs, - projectId, - nixlets, - ... - }: - pkgs.writeShellScriptBin "nixlets-upload" ( - '' - if [[ -z "$AUTH_HEADER" ]]; then - echo "Must provide AUTH_HEADER environment variable!" 1>&2 - exit 1 - fi - '' - + lib.concatStringsSep "\n" ( - builtins.map (nixlet: - with nixlet; '' - URL="https://gitlab.com/api/v4/projects/${projectId}/packages/generic/${name}/${version}/${name}.tar.gz" - if ${pkgs.curl}/bin/curl --output /dev/null --silent --head --fail --header "$AUTH_HEADER" $URL; then - echo "> Skipped ${name}@${version} because it already exists in the Package Registry" - else - echo "> Uploading new version ${name}@${version}" - ${pkgs.gnutar}/bin/tar -czf /tmp/${name}.tar.gz --mode='u+rwX' -C ${path} --transform 's/^\./\/${name}/' . - ${pkgs.curl}/bin/curl --header "$AUTH_HEADER" --upload-file "/tmp/${name}.tar.gz" "$URL"; echo; - ${pkgs.coreutils}/bin/rm -f /tmp/${nixlet.name}.tar.gz - echo "> Finished ${name}@${version}, see above" - fi - '') - nixlets - ) - ); + fetchNixlet = url: sha256: mkNixlet (builtins.fetchTarball {inherit url sha256;}); + fetchNixletFromGitlab = { + project, + name, + version, + sha256, + }: let + projectEscaped = builtins.replaceStrings ["/"] ["%2F"] project; + in + fetchNixlet "https://gitlab.com/api/v4/projects/${projectEscaped}/packages/generic/${name}/${version}/${name}.tar.gz" sha256; - mkDocs = opts: - import ./valuesDocs.nix (opts // {inherit lib;}); -} + uploadNixletsToGitlab = { + pkgs, + projectId, + nixlets, + ... + }: + pkgs.writeShellScriptBin "nixlets-upload" ( + '' + if [[ -z "$AUTH_HEADER" ]]; then + echo "Must provide AUTH_HEADER environment variable!" 1>&2 + exit 1 + fi + '' + + concatMapStringsSep "\n" ( + (nixlet: + with nixlet; '' + URL="https://gitlab.com/api/v4/projects/${projectId}/packages/generic/${name}/${version}/${name}.tar.gz" + if ${pkgs.curl}/bin/curl --output /dev/null --silent --head --fail --header "$AUTH_HEADER" $URL; then + echo "> Skipped ${name}@${version} because it already exists in the Package Registry" + else + echo "> Uploading new version ${name}@${version}" + ${pkgs.gnutar}/bin/tar -czf /tmp/${name}.tar.gz --mode='u+rwX' -C ${path} --transform 's/^\./\/${name}/' . + ${pkgs.curl}/bin/curl --header "$AUTH_HEADER" --upload-file "/tmp/${name}.tar.gz" "$URL"; echo; + ${pkgs.coreutils}/bin/rm -f /tmp/${nixlet.name}.tar.gz + echo "> Finished ${name}@${version}, see above" + fi + '') + nixlets + ) + ); + + mkDocs = opts: + import ./valuesDocs.nix (opts // {inherit lib;}); + }; +in + nixlet-lib diff --git a/lib/nixletModule.nix b/lib/nixletModule.nix new file mode 100644 index 0000000..3f52380 --- /dev/null +++ b/lib/nixletModule.nix @@ -0,0 +1,75 @@ +{ + lib, + config, + nixlet, + system, + ... +}: let + inherit (lib) mkOption types mkOptionType isType mkMerge mapAttrs mkIf literalExpression; + cfg = config.nixlet; + + nixletType = mkOptionType { + name = "nixlet"; + description = "reference"; + descriptionClass = "noun"; + check = isType "nixlet"; + }; +in { + imports = [ + { + # shortcut, allows accessing deps a bit shorter/more easily + _module.args.deps = cfg.deps; + } + ]; + options.nixlet = { + dependencies = mkOption { + type = types.attrsOf nixletType; + default = {}; + description = '' + Import other nixlets as dependencies. Works similar to Helm, specify values for these + Nixlets by using their name as a prefix. Like `postgres.replicaCount` in `values.nix` for example. + ''; + example = literalExpression '' + { + "postgres" = nixlet-lib.mkNixlet ; + "mongodb" = nixlet-lib.fetchNixlet ...; # etc. + } + ''; + }; + deps = mkOption { + readOnly = true; + type = types.attrsOf types.attrs; + default = mapAttrs (name: val: + builtins.addErrorContext "[nixlets] while evaluating dependency ${name}" + (val.eval { + inherit system; + inherit (config.kubenix) project; + values = nixlet.values.${name}; + }).config) + cfg.dependencies; + description = '' + Evaluated dependency nixlets. Allows accessing their resources like for example: + + ```nix + config.nixlet.deps."".kubernetes.resources + ``` + ''; + }; + depAutoMerge = mkOption { + type = types.bool; + default = true; + description = '' + Whether to automatically merge dependency nixlets' configs + with the current nixlet. If disabled, you can access dependency outputs via: + + ```nix + config.nixlet.deps."".kubernetes.resources + ``` + ''; + }; + }; + + config = mkIf cfg.depAutoMerge { + kubernetes.resources = mkMerge (map (dep: dep.kubernetes.resources) (builtins.attrValues cfg.deps)); + }; +} diff --git a/lib/valuesDocs.nix b/lib/valuesDocs.nix index 3ddf161..82611e2 100644 --- a/lib/valuesDocs.nix +++ b/lib/valuesDocs.nix @@ -1,6 +1,8 @@ { lib, nixlet, + # whether to generate docs for the full values, including dependencies + fullValues ? false, transformOptions ? opt: opt, filter ? _: true, headingDepth ? 3, @@ -13,7 +15,12 @@ mapAttrsToList concatStrings replicate + optionalString + optionAttrSetToDocList + attrByPath + generators ; + inherit (generators) toPretty; _transformOptions = opt: transformOptions (opt @@ -25,7 +32,12 @@ name = lib.removePrefix "config." opt.name; }); - rawOpts = lib.optionAttrSetToDocList nixlet.values.options; + valueSource = + if fullValues + # TODO: get rid of system, just here cuz of kubenix + then (nixlet.fullValues {system = "x86_64-linux";}) + else nixlet.values; + rawOpts = optionAttrSetToDocList valueSource.options; transformedOpts = map _transformOptions rawOpts; filteredOpts = lib.filter (opt: opt.visible && !opt.internal) transformedOpts; @@ -58,7 +70,20 @@ ${opt.type} ``` '' - + (lib.optionalString (opt ? default && opt.default != null) '' + # used to show what changes a nixlet did to values of dependencies + + (let + val = toPretty {} (attrByPath opt.loc "_not found_" valueSource.config); + default = removeSuffix "\n" opt.default.text; + in + optionalString (opt.type != "submodule" && val != default) + '' + **Overridden value**: + + ```nix + ${val} + ``` + '') + + (optionalString (opt ? default && opt.default != null) '' **Default value**: @@ -66,7 +91,7 @@ ${removeSuffix "\n" opt.default.text} ``` '') - + (lib.optionalString (opt ? example) '' + + (optionalString (opt ? example) '' **Example value**: diff --git a/nix/repo/docs.nix b/nix/repo/docs.nix index b111e3f..9d25c38 100644 --- a/nix/repo/docs.nix +++ b/nix/repo/docs.nix @@ -3,8 +3,22 @@ cell, ... }: let - inherit (inputs) doclib; + inherit (inputs) pkgs doclib nixlet-lib; inherit (cell) nixlets; + + optionsDoc = doclib.mkOptionDocs { + module = nixlet-lib.nixletModule; + roots = [ + { + url = "https://gitlab.com/TECHNOFAB/nixlets/-/blob/main/lib"; + path = "${inputs.self}/lib"; + } + ]; + }; + optionsDocs = pkgs.runCommand "options-docs" {} '' + mkdir -p $out + ln -s ${optionsDoc} $out/options.md + ''; in (doclib.mkDocs { docs."default" = { @@ -23,9 +37,13 @@ in domains = ["nixlets.projects.tf"]; }; }; + macros = { + enable = true; + includeDir = toString optionsDocs; + }; dynamic-nav = { enable = true; - files."Nixlets Values" = builtins.map (val: {${val.name} = val.mkDocs {};}) (builtins.attrValues nixlets); + files."Nixlets Values" = builtins.map (val: {${val.name} = val.mkDocs {fullValues = true;};}) (builtins.attrValues nixlets); }; config = { site_name = "Nixlets"; @@ -45,6 +63,7 @@ in {"Usage" = "usage.md";} {"Generating Docs" = "generating_docs.md";} {"Secrets" = "secrets.md";} + {"Options" = "options.md";} ]; markdown_extensions = [ { diff --git a/nix/repo/flake.lock b/nix/repo/flake.lock index 3db8b5f..a749fed 100644 --- a/nix/repo/flake.lock +++ b/nix/repo/flake.lock @@ -38,11 +38,11 @@ "nixmkdocs-lib": { "locked": { "dir": "lib", - "lastModified": 1766062227, - "narHash": "sha256-jhr5CUi9eDeMIAJn7ayXP8Wr+Y2loV5EhdDIKDkRIdw=", + "lastModified": 1767549915, + "narHash": "sha256-by3r2qddlyzylup5fzSaDwtoy3eFHNKb65IuIq6bsAs=", "owner": "TECHNOFAB", "repo": "nixmkdocs", - "rev": "cb0bb5dc3382e8ba5d81324a2f1fd94ccd5a5df4", + "rev": "f3b2f4b19178e97c5580367be0f97e61a085db6d", "type": "gitlab" }, "original": {