diff --git a/arion-compose.cabal b/arion-compose.cabal index 83aa6f6..2ff2f0b 100644 --- a/arion-compose.cabal +++ b/arion-compose.cabal @@ -19,6 +19,7 @@ data-files: nix/*.nix , nix/modules/composition/*.nix , nix/modules/nixos/*.nix , nix/modules/service/*.nix + , nix/modules/lib/*.nix -- all data is verbatim from some sources data-dir: src diff --git a/nix/arion.nix b/nix/arion.nix index 4b76fc1..b53b1f4 100644 --- a/nix/arion.nix +++ b/nix/arion.nix @@ -11,7 +11,7 @@ let eval = import (srcDir + "/nix/eval-composition.nix"); build = args@{...}: let composition = eval args; - in composition.config.build.dockerComposeYaml; + in composition.config.out.dockerComposeYaml; in justStaticExecutables (overrideCabal arion-compose (o: { diff --git a/src/haskell/lib/Arion/Nix.hs b/src/haskell/lib/Arion/Nix.hs index cef6a92..a90343a 100644 --- a/src/haskell/lib/Arion/Nix.hs +++ b/src/haskell/lib/Arion/Nix.hs @@ -51,7 +51,7 @@ evaluateComposition ea = do , "--strict" , "--json" , "--attr" - , "config.build.dockerComposeYamlAttrs" + , "config.out.dockerComposeYamlAttrs" ] args = [ evalComposition ] @@ -99,7 +99,7 @@ buildComposition outLink ea = do evalComposition <- getEvalCompositionFile let commandArgs = [ "--attr" - , "config.build.dockerComposeYaml" + , "config.out.dockerComposeYaml" , "--out-link" , outLink ] diff --git a/src/nix/eval-composition.nix b/src/nix/eval-composition.nix index c890973..1081864 100644 --- a/src/nix/eval-composition.nix +++ b/src/nix/eval-composition.nix @@ -34,7 +34,7 @@ let }; in - # Typically you need composition.config.build.dockerComposeYaml + # Typically you need composition.config.out.dockerComposeYaml composition // { # throw in lib and pkgs for repl convenience inherit lib; diff --git a/src/nix/modules/composition/arion-base-image.nix b/src/nix/modules/composition/arion-base-image.nix index 2be1d97..132bc4c 100644 --- a/src/nix/modules/composition/arion-base-image.nix +++ b/src/nix/modules/composition/arion-base-image.nix @@ -34,7 +34,7 @@ in config = { arionBaseImage = "${name}:${tag}"; - build.imagesToLoad = lib.mkIf (lib.any (s: s.service.useHostStore) (lib.attrValues config.docker-compose.services)) [ + build.imagesToLoad = lib.mkIf (lib.any (s: s.service.useHostStore) (lib.attrValues config.services)) [ { image = builtImage; imageName = name; imageTag = tag; } ]; }; diff --git a/src/nix/modules/composition/docker-compose.nix b/src/nix/modules/composition/docker-compose.nix index 577fbb8..a74c958 100644 --- a/src/nix/modules/composition/docker-compose.nix +++ b/src/nix/modules/composition/docker-compose.nix @@ -3,10 +3,10 @@ This is a composition-level module. It defines the low-level options that are read by arion, like - - build.dockerComposeYaml + - out.dockerComposeYaml It declares options like - - docker-compose.services + - services */ compositionArgs@{ lib, config, options, pkgs, ... }: @@ -31,20 +31,24 @@ let in { + imports = [ + ../lib/assert.nix + (lib.mkRenamedOptionModule ["docker-compose" "services"] ["services"]) + ]; options = { - build.dockerComposeYaml = lib.mkOption { + out.dockerComposeYaml = lib.mkOption { type = lib.types.package; description = "A derivation that produces a docker-compose.yaml file for this composition."; readOnly = true; }; - build.dockerComposeYamlText = lib.mkOption { + out.dockerComposeYamlText = lib.mkOption { type = lib.types.str; - description = "The text of build.dockerComposeYaml."; + description = "The text of out.dockerComposeYaml."; readOnly = true; }; - build.dockerComposeYamlAttrs = lib.mkOption { + out.dockerComposeYamlAttrs = lib.mkOption { type = lib.types.attrsOf lib.types.unspecified; - description = "The text of build.dockerComposeYaml."; + description = "The text of out.dockerComposeYaml."; readOnly = true; }; docker-compose.raw = lib.mkOption { @@ -55,19 +59,19 @@ in type = lib.types.attrs; description = "Attribute set that will be turned into the x-arion section of the docker-compose.yaml file."; }; - docker-compose.services = lib.mkOption { + services = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule service); description = "An attribute set of service configurations. A service specifies how to run an image as a container."; }; }; config = { - build.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.build.dockerComposeYamlText; - build.dockerComposeYamlText = builtins.toJSON (config.build.dockerComposeYamlAttrs); - build.dockerComposeYamlAttrs = config.docker-compose.raw; + out.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.out.dockerComposeYamlText; + out.dockerComposeYamlText = builtins.toJSON (config.out.dockerComposeYamlAttrs); + out.dockerComposeYamlAttrs = config.assertWarn config.docker-compose.raw; docker-compose.raw = { version = "3.4"; - services = lib.mapAttrs (k: c: c.build.service) config.docker-compose.services; + services = lib.mapAttrs (k: c: c.out.service) config.services; x-arion = config.docker-compose.extended; }; }; diff --git a/src/nix/modules/composition/images.nix b/src/nix/modules/composition/images.nix index bb505da..dc885a2 100644 --- a/src/nix/modules/composition/images.nix +++ b/src/nix/modules/composition/images.nix @@ -4,7 +4,7 @@ let serviceImages = lib.mapAttrs addDetails ( - lib.filterAttrs filterFunction config.docker-compose.services + lib.filterAttrs filterFunction config.services ); filterFunction = serviceName: service: @@ -14,7 +14,7 @@ let addDetails = serviceName: service: builtins.addErrorContext "while evaluating the image for service ${serviceName}" (let - inherit (service.config) build; + inherit (service) build; in { image = build.image.outPath; imageName = build.imageName or service.image.name; diff --git a/src/nix/modules/composition/service-info.nix b/src/nix/modules/composition/service-info.nix index 10acb06..4ac106f 100644 --- a/src/nix/modules/composition/service-info.nix +++ b/src/nix/modules/composition/service-info.nix @@ -7,7 +7,7 @@ let serviceInfo = lib.mapAttrs getInfo ( - lib.filterAttrs filterFunction config.docker-compose.services + lib.filterAttrs filterFunction config.services ); filterFunction = _serviceName: service: diff --git a/src/nix/modules/lib/README.md b/src/nix/modules/lib/README.md new file mode 100644 index 0000000..48a0c04 --- /dev/null +++ b/src/nix/modules/lib/README.md @@ -0,0 +1,2 @@ +`assertions.nix`: Nixpkgs +`assert.nix`: extracted from Nixpkgs, adapted diff --git a/src/nix/modules/lib/assert.nix b/src/nix/modules/lib/assert.nix new file mode 100644 index 0000000..f9bf4a8 --- /dev/null +++ b/src/nix/modules/lib/assert.nix @@ -0,0 +1,32 @@ +{ config, lib, pkgs, ... }: + +# based on nixpkgs/nixos/modules/system/activation/top-level.nix + +let + inherit (lib) filter concatStringsSep types mkOption; + + # lib.showWarnings since 19.09 + showWarnings = warnings: res: lib.fold (w: x: lib.warn w x) res warnings; + warn = msg: builtins.trace "warning: ${msg}"; + + # Handle assertions and warnings + + failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions); + + assertWarn = if failedAssertions != [] + then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}" + else showWarnings config.warnings; + +in + +{ + imports = [ ./assertions.nix ]; + options.assertWarn = mkOption { + type = types.unspecified; # a function + # It's for the wrapping program to know about this. User need not care. + internal = true; + readOnly = true; + }; + config = { inherit assertWarn; }; +} + diff --git a/src/nix/modules/lib/assertions.nix b/src/nix/modules/lib/assertions.nix new file mode 100644 index 0000000..550b3ac --- /dev/null +++ b/src/nix/modules/lib/assertions.nix @@ -0,0 +1,34 @@ +{ lib, ... }: + +with lib; + +{ + + options = { + + assertions = mkOption { + type = types.listOf types.unspecified; + internal = true; + default = []; + example = [ { assertion = false; message = "you can't enable this for that reason"; } ]; + description = '' + This option allows modules to express conditions that must + hold for the evaluation of the system configuration to + succeed, along with associated error messages for the user. + ''; + }; + + warnings = mkOption { + internal = true; + default = []; + type = types.listOf types.str; + example = [ "The `foo' service is deprecated and will go away soon!" ]; + description = '' + This option allows modules to show warnings to users during + the evaluation of the system configuration. + ''; + }; + + }; + # impl of assertions is in +} diff --git a/src/nix/modules/service/all-modules.nix b/src/nix/modules/service/all-modules.nix index bfbb13c..18c0432 100644 --- a/src/nix/modules/service/all-modules.nix +++ b/src/nix/modules/service/all-modules.nix @@ -7,4 +7,5 @@ ./image.nix ./nixos.nix ./nixos-init.nix + ../lib/assert.nix ] diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix index 80a795d..0451b78 100644 --- a/src/nix/modules/service/docker-compose-service.nix +++ b/src/nix/modules/service/docker-compose-service.nix @@ -1,6 +1,6 @@ /* - This service-level module defines the build.service option, using + This service-level module defines the out.service option, using the user-facing options service.image, service.volumes, etc. */ @@ -25,8 +25,12 @@ let in { + imports = [ + (lib.mkRenamedOptionModule ["build" "service"] ["out" "service"]) + ]; + options = { - build.service = mkOption { + out.service = mkOption { type = attrsOf types.unspecified; description = '' Raw input for the service in docker-compose.yaml. @@ -37,12 +41,13 @@ in This option is user accessible because it may serve as an escape hatch for some. ''; + apply = config.assertWarn; }; service.name = mkOption { type = str; description = '' - The name of the service - <name> in the composition-level docker-compose.services.<name> + The name of the service - <name> in the composition-level services.<name> ''; readOnly = true; }; @@ -209,7 +214,7 @@ in }; }; - config.build.service = { + config.out.service = { inherit (config.service) volumes environment diff --git a/tests/arion-test/default.nix b/tests/arion-test/default.nix index e197fb2..caf892a 100644 --- a/tests/arion-test/default.nix +++ b/tests/arion-test/default.nix @@ -30,9 +30,9 @@ in virtualisation.pathsInNixDB = [ # Pre-build the image because we don't want to build the world # in the vm. - (preEval [ ../../examples/minimal/arion-compose.nix ]).config.build.dockerComposeYaml - (preEval [ ../../examples/full-nixos/arion-compose.nix ]).config.build.dockerComposeYaml - (preEval [ ../../examples/nixos-unit/arion-compose.nix ]).config.build.dockerComposeYaml + (preEval [ ../../examples/minimal/arion-compose.nix ]).config.out.dockerComposeYaml + (preEval [ ../../examples/full-nixos/arion-compose.nix ]).config.out.dockerComposeYaml + (preEval [ ../../examples/nixos-unit/arion-compose.nix ]).config.out.dockerComposeYaml pkgs.stdenv ]; };