diff --git a/modules/build.nix b/modules/build.nix index 15f2d76..d57f279 100644 --- a/modules/build.nix +++ b/modules/build.nix @@ -46,6 +46,11 @@ in { options = with lib; { build = { + bootstrapPackage = mkOption { + type = types.package; + internal = true; + description = "The package containing the bootstrap appOfApps application manifest."; + }; extrasPackage = mkOption { type = types.package; internal = true; @@ -66,6 +71,8 @@ in { config = { build = { + bootstrapPackage = mkApp config.applications.__bootstrap; + extrasPackage = pkgs.stdenv.mkDerivation { name = "nixidy-extras-${envName}"; @@ -85,11 +92,15 @@ in { }; environmentPackage = let - joined = pkgs.linkFarm "nixidy-apps-joined-${envName}" (lib.mapAttrsToList (_: app: { + joined = pkgs.linkFarm "nixidy-apps-joined-${envName}" ( + map (name: let + app = config.applications.${name}; + in { name = app.output.path; path = mkApp app; }) - config.applications); + config.nixidy.publicApps + ); in pkgs.symlinkJoin { name = "nixidy-environment-${envName}"; diff --git a/modules/default.nix b/modules/default.nix index 8c5777c..a596907 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -35,6 +35,6 @@ }; in { inherit (module) config; - inherit (module.config.build) environmentPackage activationPackage; + inherit (module.config.build) environmentPackage activationPackage bootstrapPackage; meta = {inherit (module.config.nixidy.target) repository branch;}; } diff --git a/modules/nixidy.nix b/modules/nixidy.nix index 7f1606d..b74c472 100644 --- a/modules/nixidy.nix +++ b/modules/nixidy.nix @@ -5,8 +5,6 @@ }: let cfg = config.nixidy; - apps = builtins.removeAttrs config.applications [cfg.appOfApps.name]; - extraFilesOpts = with lib; {name, ...}: { options = { @@ -167,6 +165,11 @@ in { default = "argocd"; description = "Destination namespace for generated Argo CD Applications in the app of apps applications."; }; + project = mkOption { + type = types.str; + default = "default"; + description = "The project of the generated bootstrap app for appOfApps"; + }; }; charts = mkOption { @@ -179,57 +182,116 @@ in { default = null; description = "Path to a directory containing sub-directory structure that can be used to build a charts attrset. This will be passed as `charts` to every module."; }; + + publicApps = mkOption { + type = with types; listOf str; + default = []; + internal = true; + description = '' + List of the names of all applications that do not contain the internal `__` prefix. + ''; + }; }; config = { applications.${cfg.appOfApps.name} = { inherit (cfg.appOfApps) namespace; - resources.applications = - lib.attrsets.mapAttrs ( - n: app: { - metadata = { - inherit (app) name; - annotations = - if app.annotations != {} - then app.annotations - else null; - }; - spec = { - inherit (app) project; - - source = { - repoURL = cfg.target.repository; - targetRevision = cfg.target.branch; - path = lib.path.subpath.join [ - cfg.target.rootPath - app.output.path - ]; - }; - destination = { - inherit (app) namespace; - inherit (app.destination) server; - }; - syncPolicy = - (lib.optionalAttrs app.syncPolicy.autoSync.enabled { - automated = { - inherit (app.syncPolicy.autoSync) prune selfHeal; + resources.applications = let + appsWithoutAppsOfApps = lib.filter (n: n != cfg.appOfApps.name) cfg.publicApps; + in + builtins.listToAttrs + (map ( + name: let + app = config.applications.${name}; + in { + inherit name; + + value = { + metadata = { + inherit (app) name; + annotations = + if app.annotations != {} + then app.annotations + else null; + }; + spec = { + inherit (app) project; + + source = { + repoURL = cfg.target.repository; + targetRevision = cfg.target.branch; + path = lib.path.subpath.join [ + cfg.target.rootPath + app.output.path + ]; + }; + destination = { + inherit (app) namespace; + inherit (app.destination) server; }; - }) - // (lib.optionalAttrs (lib.length app.syncPolicy.finalSyncOpts > 0) { - syncOptions = app.syncPolicy.finalSyncOpts; - }); - }; - } - ) - apps; + syncPolicy = + (lib.optionalAttrs app.syncPolicy.autoSync.enabled { + automated = { + inherit (app.syncPolicy.autoSync) prune selfHeal; + }; + }) + // (lib.optionalAttrs (lib.length app.syncPolicy.finalSyncOpts > 0) { + syncOptions = app.syncPolicy.finalSyncOpts; + }); + }; + }; + } + ) + appsWithoutAppsOfApps); + }; + + # This application's resources are printed on + # stdout when `nixidy bootstrap .#` is run + applications.__bootstrap = let + app = config.applications.${cfg.appOfApps.name}; + in { + inherit (cfg.appOfApps) namespace; + + resources.applications.${cfg.appOfApps.name} = { + metadata.namespace = cfg.appOfApps.namespace; + spec = { + inherit (cfg.appOfApps) project; + + source = { + repoURL = cfg.target.repository; + targetRevision = cfg.target.branch; + path = lib.path.subpath.join [ + cfg.target.rootPath + app.output.path + ]; + }; + destination = { + inherit (app) namespace; + inherit (app.destination) server; + }; + # Maybe this should be configurable but + # generally I think autoSync would be + # desirable on the initial appOfApps. + syncPolicy.automated = { + prune = true; + selfHeal = true; + }; + }; + }; }; _module.args.charts = config.nixidy.charts; - nixidy.charts = lib.optionalAttrs (cfg.chartsDir != null) (mkChartAttrs cfg.chartsDir); + nixidy = { + charts = lib.optionalAttrs (cfg.chartsDir != null) (mkChartAttrs cfg.chartsDir); + + extraFiles = lib.optionalAttrs (cfg.build.revision != null) { + ".revision".text = cfg.build.revision; + }; - nixidy.extraFiles = lib.optionalAttrs (cfg.build.revision != null) { - ".revision".text = cfg.build.revision; + publicApps = + builtins.filter (n: !(lib.hasPrefix "__" n)) + (builtins.attrNames config.applications); }; }; } diff --git a/nixidy/nixidy b/nixidy/nixidy index ab137b5..9f54465 100644 --- a/nixidy/nixidy +++ b/nixidy/nixidy @@ -63,6 +63,22 @@ function doSwitch() { "${ENVIRON}/activate" } +function doBootstrap() { + setFlakeParam + + if [[ -z "$FLAKE_ENV" ]]; then + doHelp + exit 1 + fi + + BOOTSTRAP=$(nix build "${FLAKE_ROOT}#nixidyEnvs.${NIX_SYSTEM}.${FLAKE_ENV}.bootstrapPackage" --no-link --print-out-paths) + + for manifest in "$BOOTSTRAP/*.yaml"; do + echo "---" + cat $manifest + done +} + function doHelp() { echo "Usage: $0 [OPTION] COMMAND" echo @@ -86,6 +102,10 @@ function doHelp() { echo echo " switch FLAKE_URI Build and switch to nixidy environment from flake URI." echo " Example: .#prod" + echo + echo " bootstrap FLAKE_URI" + echo " Output a manifest to bootstrap appOfApps." + echo " Example: .#prod" } COMMAND="" @@ -98,7 +118,7 @@ while [[ $# -gt 0 ]]; do opt="$1" shift case $opt in - build|switch|info|help) + build|switch|info|bootstrap|help) COMMAND="$opt" ;; --no-link) @@ -145,6 +165,9 @@ case $COMMAND in switch) doSwitch ;; + bootstrap) + doBootstrap + ;; help) doHelp ;; diff --git a/tests/default.nix b/tests/default.nix index 2476c24..0bbf207 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -9,6 +9,7 @@ ./create-namespace.nix ./yamls.nix ./override-name.nix + ./internal-apps.nix ./helm/no-values.nix ./helm/with-values.nix ./helm/transformer.nix diff --git a/tests/internal-apps.nix b/tests/internal-apps.nix new file mode 100644 index 0000000..cd46f86 --- /dev/null +++ b/tests/internal-apps.nix @@ -0,0 +1,42 @@ +{ + lib, + config, + ... +}: let + apps = config.applications.${config.nixidy.appOfApps.name}; +in { + # `__` prefix makes an application an + # internal application + applications.__test = { + # Even if you override the name it + # should use the attribute name to + # exclude internal applications + name = "test"; + }; + + test = with lib; { + name = "internal applications"; + description = "Check that applications with the internal application prefix is not a part of appOfApps."; + assertions = [ + { + description = "Application `__test` should not be included."; + + expression = hasAttr "__test" apps.resources.applications; + + expected = false; + } + + { + description = "App of apps should not have an Application resource for an internal application."; + + expression = + findFirst + (x: x.kind == "Application" && x.metadata.name == "test") + null + apps.objects; + + expected = null; + } + ]; + }; +}