diff --git a/CHANGELOG.md b/CHANGELOG.md index 155cb7a..7baeb3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,23 @@ ## Next +### Removed + + - NixOS 20.09 support. Its docker-compose does not support the + `networks..name` option, which is important in later versions. + ### Changed * Healthcheck-based dependencies in `service.depends_on`. +* The `project.name` option is now mandatory. ### 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 diff --git a/arion-compose.cabal b/arion-compose.cabal index 3aa58b0..6b0c14b 100644 --- a/arion-compose.cabal +++ b/arion-compose.cabal @@ -17,6 +17,7 @@ extra-source-files: CHANGELOG.md, README.asciidoc, 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 diff --git a/docs/modules/ROOT/partials/NixOSOptions.adoc b/docs/modules/ROOT/partials/NixOSOptions.adoc index 690b23b..131bf4f 100644 --- a/docs/modules/ROOT/partials/NixOSOptions.adoc +++ b/docs/modules/ROOT/partials/NixOSOptions.adoc @@ -22,6 +22,30 @@ Attribute set that will be turned into the docker-compose.yaml file, using Nix's Type:: attribute set No Default:: {blank} +No Example:: {blank} + +== enableDefaultNetwork + +Whether to define the default network: + +```nix +networks.default = { + name = config.project.name; +}; +``` + + +[discrete] +=== details + +Type:: boolean +Default:: ++ +---- +true +---- + + No Example:: {blank} == host.nixStorePrefix @@ -70,6 +94,164 @@ No Default:: {blank} No Example:: {blank} +== networks + +See link:https://docs.docker.com/compose/compose-file/#networks-top-level-element[Docker Compose#networks-top-level-element] + + +[discrete] +=== details + +Type:: lazy attribute set of submodules +No Default:: {blank} + +No Example:: {blank} + +== networks..attachable + +See link:https://docs.docker.com/compose/compose-file/#attachable[Docker Compose#attachable] + + +[discrete] +=== details + +Type:: boolean +No Default:: {blank} + +Example:: ++ +---- +true +---- + + +== networks..driver + +`"none"`, `"host"`, or a platform-specific value. +See link:https://docs.docker.com/compose/compose-file/#driver[Docker Compose#driver] + + +[discrete] +=== details + +Type:: string +No Default:: {blank} + +No Example:: {blank} + +== networks..driver_opts + +See link:https://docs.docker.com/compose/compose-file/#driver_opts[Docker Compose#driver_opts] + + +[discrete] +=== details + +Type:: lazy attribute set of raw values +No Default:: {blank} + +No Example:: {blank} + +== networks..enable_ipv6 + +Whether we've entered the 21st century yet. + +See link:https://docs.docker.com/compose/compose-file/#enable_ipv6[Docker Compose#enable_ipv6] + + +[discrete] +=== details + +Type:: boolean +No Default:: {blank} + +No Example:: {blank} + +== networks..external + +When `true`, don't create or destroy the network, but assume that it +exists. + +See link:https://docs.docker.com/compose/compose-file/#external[Docker Compose#external] + + +[discrete] +=== details + +Type:: boolean +No Default:: {blank} + +No Example:: {blank} + +== networks..internal + +Achieves "external isolation". + +See link:https://docs.docker.com/compose/compose-file/#internal[Docker Compose#internal] + + +[discrete] +=== details + +Type:: boolean +Default:: ++ +---- +false +---- + + +No Example:: {blank} + +== networks..ipam + +Manage IP addresses. + +See link:https://docs.docker.com/compose/compose-file/#ipam[Docker Compose#ipam] + + +[discrete] +=== details + +Type:: raw value +No Default:: {blank} + +No Example:: {blank} + +== networks..labels + +Metadata. + +See link:https://docs.docker.com/compose/compose-file/#labels[Docker Compose#labels] + + +[discrete] +=== details + +Type:: attribute set of strings +No Default:: {blank} + +No Example:: {blank} + +== networks..name + +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. + +See link:https://docs.docker.com/compose/compose-file/#name[Docker Compose#name] + + +[discrete] +=== details + +Type:: string +No Default:: {blank} + +No Example:: {blank} + == out.dockerComposeYaml A derivation that produces a docker-compose.yaml file for this composition. @@ -112,17 +294,14 @@ 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. + [discrete] === details -Type:: null or string -Default:: -+ ----- -null ----- - +Type:: string +No Default:: {blank} No Example:: {blank} diff --git a/examples/full-nixos/arion-compose.nix b/examples/full-nixos/arion-compose.nix index 1524e3a..94890db 100644 --- a/examples/full-nixos/arion-compose.nix +++ b/examples/full-nixos/arion-compose.nix @@ -1,4 +1,5 @@ { + project.name = "full-nixos"; services.webserver = { pkgs, lib, ... }: { nixos.useSystemd = true; nixos.configuration.boot.tmpOnTmpfs = true; diff --git a/examples/nixos-unit/arion-compose.nix b/examples/nixos-unit/arion-compose.nix index 1edd4e5..642a18e 100644 --- a/examples/nixos-unit/arion-compose.nix +++ b/examples/nixos-unit/arion-compose.nix @@ -17,6 +17,7 @@ */ { + project.name = "nixos-unit"; services.webserver = { config, pkgs, ... }: { nixos.configuration = {config, lib, options, pkgs, ...}: { diff --git a/examples/traefik/arion-compose.nix b/examples/traefik/arion-compose.nix index b6c6f7d..c43627f 100644 --- a/examples/traefik/arion-compose.nix +++ b/examples/traefik/arion-compose.nix @@ -9,7 +9,7 @@ */ { lib, pkgs, ... }: { - + config.project.name = "traefik"; config.services = { traefik = { image.command = [ diff --git a/nix/ci.nix b/nix/ci.nix index 5dacffd..1e53108 100644 --- a/nix/ci.nix +++ b/nix/ci.nix @@ -5,13 +5,6 @@ let in dimension "Nixpkgs version" { - # avoid bitrotting the docker support (as opposed to podman) - "nixos-20_09" = { - nixpkgsSource = "nixos-20.09"; - enableDoc = true; - dockerSupportsSystemd = true; - nixosHasPodmanDockerSocket = false; - }; "nixos-22_05" = { nixpkgsSource = "nixos-22.05"; enableDoc = true; diff --git a/nix/overlay.nix b/nix/overlay.nix index bbacd30..ecdb6af 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -49,6 +49,7 @@ in haskellPkgs.haskell-language-server super.docker-compose self.niv + self.nixpkgs-fmt self.releaser ]; }; diff --git a/nix/sources.json b/nix/sources.json index 6b3e284..037ef0c 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -11,18 +11,6 @@ "url": "https://github.com/nmattia/niv/archive/fad2a6cbfb2e7cdebb7cb0ad2f5cc91e2c9bc06b.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, - "nixos-20.09": { - "branch": "nixos-20.09", - "description": "Nix Packages collection", - "homepage": null, - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "0cfe5377e8993052f9b0dd56d058f8008af45bd9", - "sha256": "0i3ybddi2mrlaz3di3svdpgy93zwmdglpywih4s9rd3wj865gzn1", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/0cfe5377e8993052f9b0dd56d058f8008af45bd9.tar.gz", - "url_template": "https://github.com///archive/.tar.gz" - }, "nixos-22.05": { "branch": "nixos-22.05", "description": "Nix Packages collection", diff --git a/nix/upstreamable/default.nix b/nix/upstreamable/default.nix index a06b42d..b82bc82 100644 --- a/nix/upstreamable/default.nix +++ b/nix/upstreamable/default.nix @@ -79,9 +79,7 @@ let /* Function to derivation of the docker compose yaml file NOTE: The output will change: https://github.com/hercules-ci/arion/issues/82 - This function is particularly useful on CI. On Nixpkgs >= 20.09 this will - not store the image tarballs but executables to produce them reliably via - streamLayeredImage. + This function is particularly useful on CI. */ build = args@{...}: let composition = eval args; diff --git a/src/haskell/exe/Main.hs b/src/haskell/exe/Main.hs index eeceda1..3fa7f19 100644 --- a/src/haskell/exe/Main.hs +++ b/src/haskell/exe/Main.hs @@ -60,7 +60,7 @@ parseOptions = do <> 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 diff --git a/src/haskell/testdata/Arion/NixSpec/arion-compose.json b/src/haskell/testdata/Arion/NixSpec/arion-compose.json index 972db6f..5f644ac 100644 --- a/src/haskell/testdata/Arion/NixSpec/arion-compose.json +++ b/src/haskell/testdata/Arion/NixSpec/arion-compose.json @@ -1,4 +1,9 @@ { + "networks": { + "default": { + "name": "unit-test-data" + } + }, "services": { "webserver": { "command": [ @@ -37,7 +42,7 @@ } ], "project": { - "name": null + "name": "unit-test-data" }, "serviceInfo": { "webserver": { diff --git a/src/haskell/testdata/Arion/NixSpec/arion-compose.nix b/src/haskell/testdata/Arion/NixSpec/arion-compose.nix index dac420d..40c5907 100644 --- a/src/haskell/testdata/Arion/NixSpec/arion-compose.nix +++ b/src/haskell/testdata/Arion/NixSpec/arion-compose.nix @@ -1,4 +1,5 @@ { + project.name = "unit-test-data"; services.webserver = { pkgs, ... }: { nixos.useSystemd = true; nixos.configuration.boot.tmpOnTmpfs = true; diff --git a/src/nix/lib.nix b/src/nix/lib.nix new file mode 100644 index 0000000..1e3ede6 --- /dev/null +++ b/src/nix/lib.nix @@ -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 + ; +} diff --git a/src/nix/modules.nix b/src/nix/modules.nix index ecc73c1..8c3251a 100644 --- a/src/nix/modules.nix +++ b/src/nix/modules.nix @@ -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 ] \ No newline at end of file diff --git a/src/nix/modules/composition/composition.nix b/src/nix/modules/composition/composition.nix index 37d2854..8b53882 100644 --- a/src/nix/modules/composition/composition.nix +++ b/src/nix/modules/composition/composition.nix @@ -16,9 +16,10 @@ in 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 = { diff --git a/src/nix/modules/composition/networks.nix b/src/nix/modules/composition/networks.nix new file mode 100644 index 0000000..15435d9 --- /dev/null +++ b/src/nix/modules/composition/networks.nix @@ -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; + + }; +} diff --git a/src/nix/modules/networks/network.nix b/src/nix/modules/networks/network.nix new file mode 100644 index 0000000..16083b8 --- /dev/null +++ b/src/nix/modules/networks/network.nix @@ -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: builtins.trace k builtins.trace opt builtins.trace "" opt.isDefined) + { + inherit (options) + driver + driver_opts + attachable + enable_ipv6 + ipam + internal + labels + external + name + ; + } + ); + }; +} diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix index 6b64e84..350c215 100644 --- a/src/nix/modules/service/docker-compose-service.nix +++ b/src/nix/modules/service/docker-compose-service.nix @@ -10,10 +10,11 @@ let inherit (lib) mkOption types; 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.