packages feed

arion-compose 0.1.3.0 → 0.2.0.0

raw patch · 15 files changed

+330/−25 lines, 15 filesdep ~aesonPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: aeson

API changes (from Hackage documentation)

Files

CHANGELOG.md view
@@ -1,5 +1,37 @@ # Revision history for Arion +## Next 0.2.0.0++### BREAKING++* The `project.name` option is now mandatory for projects that aren't deployed with the NixOS module.+++* The NixOS module now sets the default network name to the project name (commonly referred to as `<name>` in the option path).+  If this is not desired, for instance if you need the projects to be on the same network, set `networks.default.name` in each of them.++* The NixOS module now sets the default project name. You can still set your own value with the `project.name` option.+  If you did not set one, docker compose heuristically determined the name to be `store`, so you may want to set `project.name = "store"` or prepare to rename the network manually.++### Removed++ - NixOS 20.09 support. Its docker-compose does not support the+   `networks.<name>.name` option, which is important in later versions.+   A newer, bundled docker compose may work there, but for now the decision+   is to drop this legacy version.++### Changed++* Healthcheck-based dependencies in `service.depends_on`.++### Added++* Support `service.healthcheck` for defining custom healthchecks.+* Arion now declares a `networks.default` by default, with `name` set to+  `project.name`. This improves compatibility with container runtimes by+  copying pre-existing behavior. Most users will want to keep using this+  behavior, but it can be disabled with `enableDefaultNetwork`.+ ## 0.1.3.0 -- 2020-05-03  ### Changed
arion-compose.cabal view
@@ -1,7 +1,7 @@ cabal-version:       2.4  name:                arion-compose-version: 0.1.3.0+version: 0.2.0.0 synopsis:            Run docker-compose with help from Nix/NixOS description:         Arion is a tool for building and running applications that consist of multiple docker containers using NixOS modules. It has special support for docker images that are built with Nix, for a smooth development experience and improved performance. homepage:            https://github.com/hercules-ci/arion#readme@@ -17,6 +17,7 @@                      src/haskell/testdata/**/*.json data-files:          nix/*.nix                    , nix/modules/composition/*.nix+                   , nix/modules/networks/*.nix                    , nix/modules/nixos/*.nix                    , nix/modules/service/*.nix                    , nix/modules/lib/*.nix@@ -24,9 +25,13 @@ -- all data is verbatim from some sources data-dir:            src +source-repository head+  type: git+  location: https://github.com/hercules-ci/arion+ common common-  build-depends:     base >=4.12.0.0 && <4.15-                   , aeson+  build-depends:     base >=4.12.0.0 && <4.17+                   , aeson >=2                    , aeson-pretty                    , async                    , bytestring
src/haskell/exe/Main.hs view
@@ -60,7 +60,7 @@           <> help "Use Nix expression EXPR to get the Nixpkgs attrset used for bootstrapping \                    \and evaluating the configuration." )     showTrace <- flag False True (long "show-trace"-                    <> help "Causes Nix to print out a stack trace in case of Nix expression evaluation errors.")+                    <> help "Causes Nix to print out a stack trace in case of Nix expression evaluation errors. Specify before command.")     -- TODO --option support (https://github.com/pcapriotti/optparse-applicative/issues/284)     userNixArgs <- many (T.pack <$> strOption (long "nix-arg" <> metavar "ARG" <> help "Pass an extra argument to nix. Example: --nix-arg --option --nix-arg substitute --nix-arg false"))     prebuiltComposeFile <- optional $ strOption
src/haskell/lib/Arion/Images.hs view
@@ -25,7 +25,8 @@     isNew i =       -- On docker, the image name is unmodified       (imageName i <> ":" <> imageTag i) `notElem` loaded-      -- -- On podman, you automatically get a localhost prefix+      -- On podman, you used to automatically get a localhost prefix+      -- however, since NixOS 22.05, this expected to be part of the name instead         && ("localhost/" <> imageName i <> ":" <> imageTag i) `notElem` loaded    traverse_ loadImage . filter isNew $ requestedImages
src/haskell/lib/Arion/Services.hs view
@@ -9,6 +9,7 @@ import Protolude hiding (to)  import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Key as AK import           Arion.Aeson (decodeFile)  import Control.Lens@@ -20,4 +21,4 @@    v <- decodeFile fp -  pure ((v :: Aeson.Value) ^.. key "x-arion" . key "serviceInfo" . key service . key "defaultExec" . _Array . traverse . _String)+  pure ((v :: Aeson.Value) ^.. key "x-arion" . key "serviceInfo" . key (AK.fromText service) . key "defaultExec" . _Array . traverse . _String)
src/haskell/testdata/Arion/NixSpec/arion-compose.json view
@@ -1,4 +1,9 @@ {+    "networks": {+        "default": {+            "name": "unit-test-data"+        }+    },     "services": {         "webserver": {             "command": [@@ -9,7 +14,7 @@                 "PATH": "/usr/bin:/run/current-system/sw/bin/",                 "container": "docker"             },-            "image": "webserver:<HASH>",+            "image": "localhost/webserver:<HASH>",             "ports": [                 "8000:80"             ],@@ -32,12 +37,12 @@         "images": [             {                 "imageExe": "<STOREPATH>",-                "imageName": "webserver",+                "imageName": "localhost/webserver",                 "imageTag": "<HASH>"             }         ],         "project": {-            "name": null+            "name": "unit-test-data"         },         "serviceInfo": {             "webserver": {
src/haskell/testdata/Arion/NixSpec/arion-compose.nix view
@@ -1,4 +1,5 @@ {+  project.name = "unit-test-data";   services.webserver = { pkgs, ... }: {     nixos.useSystemd = true;     nixos.configuration.boot.tmpOnTmpfs = true;
src/nix/eval-composition.nix view
@@ -12,7 +12,6 @@   inherit (pkgs) lib;    composition = lib.evalModules {-    check = true;     modules = builtinModules ++ modules;   }; @@ -24,6 +23,7 @@     _file = ./eval-composition.nix;     key = ./eval-composition.nix;     config._module.args.pkgs = lib.mkIf (pkgs != null) (lib.mkForce pkgs);+    config._module.args.check = true;     config.host.nixStorePrefix = hostNixStorePrefix;     config.host.uid = lib.toInt uid;   };@@ -33,5 +33,5 @@   composition // {     # throw in lib and pkgs for repl convenience     inherit lib;-    inherit (composition.config._module.args) pkgs;+    inherit (composition._module.args) pkgs;   }
+ src/nix/lib.nix view
@@ -0,0 +1,16 @@+{ lib }:+let++  link = url: text:+    ''link:${url}[${text}]'';++  dockerComposeRef = fragment:+    ''See ${link "https://docs.docker.com/compose/compose-file/#${fragment}" "Docker Compose#${fragment}"}'';++in+{+  inherit+    dockerComposeRef+    link+    ;+}
src/nix/modules.nix view
@@ -2,6 +2,7 @@     ./modules/composition/docker-compose.nix     ./modules/composition/host-environment.nix     ./modules/composition/images.nix+    ./modules/composition/networks.nix     ./modules/composition/service-info.nix     ./modules/composition/composition.nix ]
src/nix/modules/composition/composition.nix view
@@ -8,14 +8,18 @@ in {   options = {+    _module.args = mkOption {+      internal = true;+    };     project.name = mkOption {       description = ''         Name of the project.          See ${link "https://docs.docker.com/compose/reference/envvars/#compose_project_name" "COMPOSE_PROJECT_NAME"}++        This is not optional, because getting the project name from a directory name tends to produce different results for different repo checkout location names.       '';-      type = types.nullOr types.str;-      default = null;+      type = types.str;     };   };   config = {
+ src/nix/modules/composition/networks.nix view
@@ -0,0 +1,53 @@+{ config, lib, ... }:++let+  inherit (lib)+    mkOption+    optionalAttrs+    types+    ;+  inherit (import ../../lib.nix { inherit lib; })+    dockerComposeRef+    ;+in+{+  options = {+    networks = mkOption {+      type = types.lazyAttrsOf (types.submoduleWith {+        modules = [+          ../networks/network.nix+        ];+      });+      description = ''+        ${dockerComposeRef "networks-top-level-element"}+      '';+    };+    enableDefaultNetwork = mkOption {+      type = types.bool;+      description = ''+        Whether to define the default network:++        ```nix+        networks.default = {+          name = config.project.name;+        };+        ```+      '';+      default = true;+    };+  };+++  config = {++    networks = optionalAttrs config.enableDefaultNetwork {+      default = {+        name = config.project.name;+      };+    };++    docker-compose.raw.networks =+      lib.mapAttrs (k: v: v.out) config.networks;++  };+}
+ src/nix/modules/networks/network.nix view
@@ -0,0 +1,131 @@+{ config, lib, options, ... }:++let+  inherit (lib)+    mkOption+    optionalAttrs+    types+    ;+  inherit (import ../../lib.nix { inherit lib; })+    dockerComposeRef+    ;+in+{+  options = {+    driver = mkOption {+      description = ''+        `"none"`, `"host"`, or a platform-specific value.+        ${dockerComposeRef "driver"}+      '';+      type = types.str;+    };++    driver_opts = mkOption {+      description = ''+        ${dockerComposeRef "driver_opts"}+      '';+      type = types.lazyAttrsOf types.raw or types.unspecified;+    };++    attachable = mkOption {+      description = ''+        ${dockerComposeRef "attachable"}+      '';+      type = types.bool;+      example = true;+    };++    enable_ipv6 = mkOption {+      description = ''+        Whether we've entered the 21st century yet.+        +        ${dockerComposeRef "enable_ipv6"}+      '';+      type = types.bool;+    };++    ipam = mkOption {+      # TODO model sub-options+      description = ''+        Manage IP addresses.++        ${dockerComposeRef "ipam"}+      '';+      type = types.raw or types.unspecified;+    };++    internal = mkOption {+      description = ''+        Achieves "external isolation".++        ${dockerComposeRef "internal"}+      '';+      defaultText = false;+      type = types.bool;+    };++    labels = mkOption {+      description = ''+        Metadata.++        ${dockerComposeRef "labels"}+      '';+      # no list support, because less expressive wrt overriding+      type = types.attrsOf types.str;+    };++    external = mkOption {+      description = ''+        When `true`, don't create or destroy the network, but assume that it+        exists.++        ${dockerComposeRef "external"}+      '';+      type = types.bool;+    };++    name = mkOption {+      description = ''+        Set a custom name for the network.++        It shares a namespace with other projects' networks. `name` is used as-is.++        Note the `default` network's default `name` is set to `project.name` by Arion.++        ${dockerComposeRef "name"}+      '';+      type = types.str;+    };++    out = mkOption {+      internal = true;+      description = ''+        This network's contribution to the docker compose yaml file+        under the `networks.''${name}` key.+      '';+      type = lib.types.attrsOf lib.types.raw or lib.types.unspecified;+    };+  };++  config = {+    out =+      lib.mapAttrs+        (k: opt: opt.value)+        (lib.filterAttrs+          (k: opt: opt.isDefined)+          {+            inherit (options)+              driver+              driver_opts+              attachable+              enable_ipv6+              ipam+              internal+              labels+              external+              name+              ;+          }+        );+  };+}
src/nix/modules/service/docker-compose-service.nix view
@@ -4,16 +4,17 @@    the user-facing options service.image, service.volumes, etc.   */-{ pkgs, lib, config, ... }:+{ pkgs, lib, config, options, ... }:  let   inherit (lib) mkOption types;-  inherit (types) listOf nullOr attrsOf str either int bool;+  inherit (types) listOf nullOr attrsOf str either int bool submodule enum; -  link = url: text:-    ''link:${url}[${text}]'';-  dockerComposeRef = fragment:-    ''See ${link "https://docs.docker.com/compose/compose-file/#${fragment}" "Docker Compose#${fragment}"}'';+  inherit (import ../../lib.nix { inherit lib; })+    link+    dockerComposeRef+    ;+   dockerComposeKitchenSink = ''     Analogous to the `docker run` counterpart. @@ -100,10 +101,62 @@       default = null;       description = dockerComposeRef "container_name";     };-    service.depends_on = mkOption {-      type = listOf str;-      default = [];-      description = dockerComposeRef "depends_on";+    service.depends_on =+      let conditionsModule = {+            options = {+              condition = mkOption {+                type = enum ["service_started" "service_healthy" "service_completed_successfully"];+                description = dockerComposeRef "depends_on";+                default = "service_started";+              };+            };+          };+       in mkOption {+         type = either (listOf str) (attrsOf (submodule conditionsModule));+         default = [];+         description = dockerComposeRef "depends_on";+       };+    service.healthcheck = mkOption {+      description = dockerComposeRef "healthcheck";+      type = submodule ({ config, options, ...}: {+        options = {+          _out = mkOption {+            internal = true;+            default = lib.optionalAttrs (options.test.highestPrio < 1500) {+              inherit (config) test interval timeout start_period retries;+            };+          };+          test = mkOption {+            type = nullOr (listOf str);+            default = null;+            example = [ "CMD" "pg_isready" ];+            description = dockerComposeRef "healthcheck";+          };+          interval = mkOption {+            type = str;+            default = "30s";+            example = "1m";+            description = dockerComposeRef "healthcheck";+          };+          timeout = mkOption {+            type = str;+            default = "30s";+            example = "10s";+            description = dockerComposeRef "healthcheck";+          };+          start_period = mkOption {+            type = str;+            default = "0s";+            example = "30s";+            description = dockerComposeRef "healthcheck";+          };+          retries = mkOption {+            type = int;+            default = 3;+            description = dockerComposeRef "healthcheck";+          };+        };+      });     };     service.devices = mkOption {       type = listOf str;@@ -250,6 +303,8 @@     inherit (config.service) container_name;   } // lib.optionalAttrs (config.service.depends_on != []) {     inherit (config.service) depends_on;+  } // lib.optionalAttrs (options.service.healthcheck.highestPrio < 1500) {+    healthcheck = config.service.healthcheck._out;   } // lib.optionalAttrs (config.service.devices != []) {     inherit (config.service) devices;   } // lib.optionalAttrs (config.service.entrypoint != null) {
src/nix/modules/service/image.nix view
@@ -104,8 +104,8 @@     };     image.name = mkOption {       type = str;-      default = config.service.name;-      defaultText = lib.literalExample "config.service.name";+      default = "localhost/" + config.service.name;+      defaultText = lib.literalExpression or lib.literalExample ''"localhost/" + config.service.name'';       description = ''         A human readable name for the docker image.