Compare commits

..

2 commits

Author SHA1 Message Date
Robert Hensing
8d5371229d WIP podman-compose 2021-06-03 11:57:12 +02:00
Robert Hensing
3dcadd5e40 WIP podman 2021-06-03 11:54:07 +02:00
75 changed files with 1804 additions and 1564 deletions

2
.gitignore vendored
View file

@ -5,5 +5,3 @@ dist/
dist-newstyle/
cabal.project.local
*.swp

View file

@ -1,49 +1,5 @@
# Revision history for Arion
## 0.2.1.0 -- 2023-07-26
### Added
* `service.networks` now supports attribute set values with various options, thanks to @pedorich-n.
* `docker-compose.volumes` can now be specified in multiple modules, thanks to @qaifshaikh.
* `image.fakeRootCommands` for making modifications to the image that aren't "add a link farm".
### Fixed
* Regular maintenance fixes, including one by olebedev
## 0.2.0.0 -- 2022-12-02
### 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

View file

@ -1,7 +1,7 @@
cabal-version: 2.4
name: arion-compose
version: 0.2.1.0
version: 0.1.3.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,7 +17,6 @@ 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
@ -25,13 +24,9 @@ data-files: nix/*.nix
-- 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.99
, aeson >=2
build-depends: base >=4.12.0.0 && <4.15
, aeson
, aeson-pretty
, async
, bytestring

View file

@ -1,5 +1,5 @@
status = [
"ci/hercules/onPush/default",
"ci/hercules/derivations",
"ci/hercules/evaluation",
]
delete_merged_branches = true

View file

@ -1,11 +1,6 @@
let flake = import ./nix/compat.nix;
in
{ pkgs ? import flake.inputs.nixpkgs { }
{ pkgs ? import ./nix {}
, haskellPackages ? pkgs.haskellPackages
}:
let
pkgsWithArion = pkgs.extend flake.overlays.default;
in
{
inherit (pkgsWithArion) arion;
arion = import ./nix/arion.nix { inherit pkgs haskellPackages; };
}

View file

@ -4,4 +4,3 @@ version: 'master'
nav:
- modules/ROOT/nav.adoc
- modules/reference/nav.adoc
nix: true

View file

@ -1,31 +0,0 @@
{
perSystem = { config, pkgs, lib, ... }: {
packages.generated-option-doc-arion =
# TODO: use the render pipeline in flake-parts,
# which has support for things like {options}`foo`.
let
eval = lib.evalModules {
modules = import ../src/nix/modules.nix;
};
in
(pkgs.nixosOptionsDoc
{
options = eval.options;
}).optionsCommonMark;
packages.generated-antora-files =
pkgs.runCommand "generated-antora-files"
{
nativeBuildInputs = [ pkgs.pandoc ];
doc_arion = config.packages.generated-option-doc-arion;
}
# TODO: use the render pipeline in flake-parts,
# which has support for things like {options}`foo`.
''
mkdir -p $out/modules/ROOT/partials
pandoc --from=markdown --to=asciidoc \
< $doc_arion \
> $out/modules/ROOT/partials/arion-options.adoc
'';
};
}

View file

@ -1 +0,0 @@
../../../../../examples/full-nixos/arion-compose.nix

View file

@ -1 +0,0 @@
../../../../../examples/minimal/arion-compose.nix

View file

@ -1 +0,0 @@
../../../../../examples/nixos-unit/arion-compose.nix

View file

@ -1,3 +1,2 @@
* xref:index.adoc[Getting Started]
* xref:options.adoc[Arion Options]
* xref:deployment.adoc[Deployment]

View file

@ -1,71 +0,0 @@
= Deployment with Arion
Arion projects can be deployed in Nix-like or Docker-like ways.
== Docker images
When you disable `useHostStore`, arion will build images, which can be deployed
to any Docker host, including non-NixOS hosts.
=== Remote Docker socket
NOTE: Access to a Docker socket is equivalent to root access on the host.
Docker supports authentication via TLS client certificates.
The xref:hercules-ci-effects:ROOT:reference/nix-functions/runArion.adoc[runArion Effect] uses this technique.
Because this technique works with a single Docker host, it does not need a registry.
=== Upload to registry
You can either use `arion push` or write custom push logic using the `arion cat`
command, the `eval` function on the `arion` package, or the `lib.eval` function
on the flake to retrieve the images defined in a project.
== NixOS module
Arion projects can be deployed as part of a NixOS configuration. This ties the
project revision to the system configuration revision, which can be good or bad
thing, depending on your deployment strategy. At a low level, a benefit is that
no store paths need to be copied locally and remote NixOS deployments can use
Nix's copy-closure algorithm for efficient transfers, and transparent binary
caches rather than an inherently stateful Docker registry solution.
Extend your NixOS configuration by adding the configuration elements to an
existing configuration. You could create a new module file for it, if your
choice of `imports` allows it.
NOTE: This deployment method does NOT use an `arion-pkgs.nix` file, but reuses
the host `pkgs`.
```nix
{
imports = [
# Pick one of:
# - niv
((import ./nix/sources.nix).arion + "/nixos-module.nix")
# - or flakes (where arion is a flake input)
arion.nixosModules.arion
# - or other: copy commit hash of arion and replace HASH in:
(builtins.fetchTarball "https://github.com/hercules-ci/arion/archive/HASH.tar.gz") + "/nixos-module.nix")
];
virtualisation.arion = {
backend = "podman-socket"; # or "docker"
projects.example = {
serviceName = "example"; # optional systemd service name, defaults to arion-example in this case
settings = {
# Specify you project here, or import it from a file.
# NOTE: This does NOT use ./arion-pkgs.nix, but defaults to NixOS' pkgs.
imports = [ ./arion-compose.nix ];
};
};
};
}
```
See also:
- xref:hercules-ci-effects:ROOT:reference/nix-functions/runNixOS.adoc[runNixOS Effect]
- xref:hercules-ci-effects:ROOT:reference/nix-functions/runNixOps2.adoc[runNixOps2 Effect]

View file

@ -40,7 +40,6 @@ Arion allows to compose containers with different granularity:
* <<Docker image from DockerHub>>
Full NixOS is supported on
* docker-compose + podman with docker socket (NixOS >= 21.05)
* docker-compose + docker, before cgroupsv2 (NixOS < 21.05)
@ -113,16 +112,14 @@ Describe containers using NixOS-style modules. There are a few options:
==== Minimal: Plain command using nixpkgs
`examples/minimal/arion-compose.nix`
[,nix]
----
`examples/minimal/arion-compose.nix`:
```nix
{ pkgs, ... }:
{
project.name = "webapp";
services = {
config.services = {
webserver = {
image.enableRecommendedContents = true;
service.useHostStore = true;
service.command = [ "sh" "-c" ''
cd "$$WEB_ROOT"
@ -132,36 +129,58 @@ Describe containers using NixOS-style modules. There are a few options:
"8000:8000" # host:container
];
service.environment.WEB_ROOT = "${pkgs.nix.doc}/share/doc/nix/manual";
service.stop_signal = "SIGINT";
};
};
}
----
```
==== NixOS: run full OS
==== NixOS: run only one systemd service
`examples/full-nixos/arion-compose.nix`:
`examples/nixos-unit/arion-compose.nix`:
[,nix]
----
```nix
{
project.name = "full-nixos";
services.webserver = { pkgs, lib, ... }: {
nixos.useSystemd = true;
nixos.configuration.boot.tmp.useTmpfs = true;
nixos.configuration.services.nginx.enable = true;
nixos.configuration.services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
nixos.configuration.services.nscd.enable = false;
nixos.configuration.system.nssModules = lib.mkForce [];
nixos.configuration.systemd.services.nginx.serviceConfig.AmbientCapabilities =
lib.mkForce [ "CAP_NET_BIND_SERVICE" ];
services.webserver = { config, pkgs, ... }: {
nixos.configuration = {config, pkgs, ...}: {
boot.isContainer = true;
services.nginx.enable = true;
services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
system.build.run-nginx = pkgs.writeScript "run-nginx" ''
#!${pkgs.bash}/bin/bash
PATH='${config.systemd.services.nginx.environment.PATH}'
echo nginx:x:${toString config.users.users.nginx.uid}:${toString config.users.groups.nginx.gid}:nginx web server user:/var/empty:/bin/sh >>/etc/passwd
echo nginx:x:${toString config.users.groups.nginx.gid}:nginx >>/etc/group
${config.systemd.services.nginx.runner}
'';
};
service.command = [ config.nixos.build.run-nginx ];
service.useHostStore = true;
service.ports = [
"8000:80" # host:container
];
};
}
----
```
==== NixOS: run full OS
`examples/full-nixos/arion-compose.nix`:
```nix
{
services.webserver = { pkgs, ... }: {
nixos.useSystemd = true;
nixos.configuration.boot.tmpOnTmpfs = true;
nixos.configuration.services.nginx.enable = true;
nixos.configuration.services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
service.useHostStore = true;
service.ports = [
"8000:80" # host:container
];
};
}
```
==== Docker image from DockerHub
@ -175,11 +194,6 @@ Describe containers using NixOS-style modules. There are a few options:
}
```
==== NixOS: run only one systemd service
Running individual units from NixOS is possible using an experimental script.
See `examples/nixos-unit/arion-compose.nix`.
=== Run
Start containers and watch their logs:
@ -191,47 +205,15 @@ $ arion logs -f
You can go to `examples/*/` and run these commands to give it a quick try.
=== Inspect the config
== A full featured Nix command example
While developing an arion project, you can make use of `arion repl`, which launches
a `nix repl` on the project configuration.
To see how Arion can be used in a project, have a look at
https://github.com/nix-community/todomvc-nix/tree/master/deploy/arion[todomvc-nix].
```
$ arion repl
Launching a repl for you. To get started:
To see deployment-wide configuration
type config. and use tab completion
To bring the top-level Nixpkgs attributes into scope
type :a (config._module.args.pkgs) // { inherit config; }
Welcome to Nix. Type :? for help.
Loading '../../src/nix/eval-composition.nix'...
Added 5 variables.
nix-repl> config.services.webserver.service.command
[ "sh" "-c" "cd \"$$WEB_ROOT\"\n/nix/store/66fbv9mmx1j4hrn9y06kcp73c3yb196r-python3-3.8.9/bin/python -m http.server\n" ]
nix-repl>
```
== Build with Nix
You can build a project with `nix-build` using an expression like
```nix
arion.build { modules = [ ./arion-compose.nix ]; pkgs = import ./arion-pkgs.nix; }
```
If you deploy with xref:hercules-ci-effects:ROOT:reference/nix-functions/runArion.adoc[runArion],
and your `pkgs` variable is equivalent to `import ./arion-pkgs.nix`, you can use:
```nix
let
deployment = pkgs.effects.runArion { /* ... */ });
in deployment.prebuilt
```bash
$ git clone https://github.com/nix-community/todomvc-nix
$ cd todomvc-nix/deploy/arion
$ arion up
```
== Project Status
@ -265,7 +247,7 @@ configuration that makes the Docker Compose file do the things it needs
to do.
One of the more interesting built-in modules is the
https://github.com/hercules-ci/arion/blob/master/src/nix/modules/service/host-store.nix[host-store.nix module] which
link:src/nix/modules/service/host-store.nix[host-store.nix module] which
performs the bind mounts to make the host Nix store available in the
container.

View file

@ -1,3 +1,5 @@
# Arion Options
// To update option descriptions
// - use git grep or github search
// - or browse through src/nix/modules
include::partial$arion-options.adoc[]
include::partial$NixOSOptions.adoc[]

File diff suppressed because it is too large Load diff

View file

@ -8,12 +8,10 @@ let
options = eval.options;
};
in (pkgs.runCommand "agent-options.adoc" { } ''
cat >$out <<EOF
in (pkgs.writeText "agent-options" ''
= Arion options
EOF
cat ${options.optionsAsciiDoc} >>$out
${options.optionsAsciiDoc}
'').overrideAttrs (o: {
# Work around https://github.com/hercules-ci/hercules-ci-agent/issues/168
allowSubstitutes = true;

View file

@ -1,20 +1,9 @@
{ pkgs, ... }:
let
sh = pkgs.stdenv.mkDerivation {
name = "sh";
phases = [ "installPhase" ];
installPhase = ''
mkdir -p "$out"/bin
ln -s ${pkgs.bash}/bin/sh "$out"/bin/sh
'';
};
in{
{
config.project.name = "webapp";
config.services = {
webserver = {
image.contents = [ sh ];
service.useHostStore = true;
service.command = [ "sh" "-c" ''
cd "$$WEB_ROOT"

View file

@ -1,9 +1,9 @@
{
project.name = "full-nixos";
deployment.technology = "podman";
services.webserver = { pkgs, lib, ... }: {
nixos.useSystemd = true;
nixos.configuration.boot.tmp.useTmpfs = true;
nixos.configuration.networking.useDHCP = false;
nixos.configuration.boot.tmpOnTmpfs = true;
nixos.configuration.services.nginx.enable = true;
nixos.configuration.services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
nixos.configuration.services.nscd.enable = false;

View file

@ -1,7 +1,7 @@
{ pkgs, ... }:
{
project.name = "webapp";
services = {
config.project.name = "webapp";
config.services = {
webserver = {
image.enableRecommendedContents = true;

View file

@ -17,7 +17,6 @@
*/
{
project.name = "nixos-unit";
services.webserver = { config, pkgs, ... }: {
nixos.configuration = {config, lib, options, pkgs, ...}: {
@ -36,8 +35,8 @@
echo nginx:x:${toString config.users.groups.nginx.gid}:nginx >>/etc/group
echo 'nobody:x:65534:65534:Unprivileged account do not use:/var/empty:/run/current-system/sw/bin/nologin' >>/etc/passwd
echo 'nogroup:x:65534:' >>/etc/group
mkdir -p /var/log/nginx /run/nginx/ /var/cache/nginx /var/lib/nginx/{,logs,proxy_temp,client_body_temp,fastcgi_temp,scgi_temp,uwsgi_temp} /tmp/nginx_client_body
chown nginx /var/log/nginx /run/nginx/ /var/cache/nginx /var/lib/nginx/{,logs,proxy_temp,client_body_temp,fastcgi_temp,scgi_temp,uwsgi_temp} /tmp/nginx_client_body
mkdir -p /var/log/nginx /run/nginx/ /var/cache/nginx /var/lib/nginx/{,logs,proxy_temp,client_body_temp,fastcgi_temp,scgi_temp,uwsgi_temp}
chown nginx /var/log/nginx /run/nginx/ /var/cache/nginx /var/lib/nginx/{,logs,proxy_temp,client_body_temp,fastcgi_temp,scgi_temp,uwsgi_temp}
${config.systemd.services.nginx.runner}
'';
};

View file

@ -9,18 +9,7 @@
*/
{ lib, pkgs, ... }: {
config.project.name = "traefik";
config.networks = {
traefik-custom = {
name = "traefik-custom";
ipam = {
config = [{
subnet = "172.32.0.0/16";
gateway = "172.32.0.1";
}];
};
};
};
config.services = {
traefik = {
image.command = [
@ -35,7 +24,6 @@
stop_signal = "SIGINT";
ports = [ "80:80" "8080:8080" ];
volumes = [ "/var/run/docker.sock:/var/run/docker.sock:ro" ];
networks = [ "traefik-custom" ];
};
};
@ -46,17 +34,14 @@
${pkgs.python3}/bin/python -m http.server
''}"];
service.container_name = "simple-service";
service.ports = [
"8000:8000" # host:container
];
service.stop_signal = "SIGINT";
service.labels = {
"traefik.enable" = "true";
"traefik.http.routers.nix-docs.rule" = "Host(`nix-docs.localhost`)";
"traefik.http.routers.nix-docs.entrypoints" = "web";
"traefik.http.services.nix-docs.loadBalancer.server.port" = "8000";
};
service.networks = {
traefik-custom = {
ipv4_address = "172.32.0.5";
};
};
};
};

88
flake.lock generated
View file

@ -1,103 +1,23 @@
{
"nodes": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1722555600,
"narHash": "sha256-XOQkdLafnb/p9ij77byFQjDf5m5QYl9b2REiVClC+x4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "8471fe90ad337a8074e957b69ca4d0089218391d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"hercules-ci-effects",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"id": "flake-parts",
"type": "indirect"
}
},
"haskell-flake": {
"locked": {
"lastModified": 1675296942,
"narHash": "sha256-u1X1sblozi5qYEcLp1hxcyo8FfDHnRUVX3dJ/tW19jY=",
"owner": "srid",
"repo": "haskell-flake",
"rev": "c2cafce9d57bfca41794dc3b99c593155006c71e",
"type": "github"
},
"original": {
"owner": "srid",
"ref": "0.1.0",
"repo": "haskell-flake",
"type": "github"
}
},
"hercules-ci-effects": {
"inputs": {
"flake-parts": "flake-parts_2",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1719226092,
"narHash": "sha256-YNkUMcCUCpnULp40g+svYsaH1RbSEj6s4WdZY/SHe38=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "11e4b8dc112e2f485d7c97e1cee77f9958f498f5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1722630782,
"narHash": "sha256-hMyG9/WlUi0Ho9VkRrrez7SeNlDzLxalm9FwY7n/Noo=",
"lastModified": 1621356929,
"narHash": "sha256-lD43MQ+bDFioz6eTxMmc5/tZ2nGUQ2e26CFRKp8JlF4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d04953086551086b44b6f3c6b7eeb26294f207da",
"rev": "6be706bbe5d892de78ce2c3b757508701ac592a6",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"ref": "master",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"haskell-flake": "haskell-flake",
"hercules-ci-effects": "hercules-ci-effects",
"nixpkgs": "nixpkgs"
}
}

126
flake.nix
View file

@ -1,98 +1,44 @@
{
description = "Arion - use Docker Compose via Nix";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
haskell-flake.url = "github:srid/haskell-flake/0.1.0";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
hercules-ci-effects.url = "github:hercules-ci/hercules-ci-effects";
hercules-ci-effects.inputs.nixpkgs.follows = "nixpkgs";
};
inputs.nixpkgs.url = "github:NixOS/nixpkgs/master";
outputs = inputs@{ self, flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } ({ config, lib, extendModules, ... }: {
imports = [
inputs.haskell-flake.flakeModule
inputs.hercules-ci-effects.flakeModule
inputs.flake-parts.flakeModules.easyOverlay
./docs/flake-module.nix
./tests/flake-module.nix
];
systems = inputs.nixpkgs.lib.systems.flakeExposed;
perSystem = { config, self', inputs', pkgs, system, final, ... }:
let h = pkgs.haskell.lib.compose; in
{
overlayAttrs = {
inherit (config.packages) arion;
arionTestingFlags = {
dockerSupportsSystemd = false;
};
};
packages.default = config.packages.arion;
packages.overlay-test = final.arion;
packages.arion = import ./nix/arion.nix { inherit pkgs; };
haskellProjects.haskell-package = {
# not autodetected: https://github.com/srid/haskell-flake/issues/49
packages.arion-compose.root = ./.;
outputs = { self, nixpkgs }:
let
lib = import (nixpkgs + "/lib");
systems = [
"aarch64-linux"
"x86_64-darwin"
"x86_64-linux"
];
arionFromPkgs = pkgs: import ./nix/arion.nix { inherit pkgs; };
in {
overrides =
self: super: {
arion-compose =
lib.pipe super.arion-compose [
(h.addBuildTools [ pkgs.nix ])
(h.overrideCabal (o: {
src = pkgs.lib.sourceByRegex ./. [
".*[.]cabal"
"LICENSE"
"src/?.*"
"README.asciidoc"
"CHANGELOG.md"
];
preCheck = ''
export NIX_LOG_DIR=$TMPDIR
export NIX_STATE_DIR=$TMPDIR
export NIX_PATH=nixpkgs=${pkgs.path}
'';
}))
];
};
};
devShells.default = config.devShells.haskell-package.overrideAttrs (o: {
nativeBuildInputs = o.nativeBuildInputs or [ ] ++ [
pkgs.docker-compose
pkgs.nixpkgs-fmt
config.haskellProjects.haskell-package.haskellPackages.releaser
];
});
};
# The overlay is currently the recommended way to integrate arion,
# because its arion attribute behaves just like Nixpkgs.
overlay = final: prev: {
arion = arionFromPkgs final;
};
hercules-ci.flake-update = {
enable = true;
autoMergeMethod = "merge";
when = {
hour = [ 2 ];
dayOfMonth = [ 5 ];
};
};
herculesCI.ciSystems = [
# "aarch64-darwin"
# "aarch64-linux"
"x86_64-darwin"
"x86_64-linux"
];
flake = {
debug = { inherit inputs config lib; };
lib = {
eval = import ./src/nix/eval-composition.nix;
build = args@{ ... }:
let composition = self.lib.eval args;
in composition.config.out.dockerComposeYaml;
};
nixosModules.arion = ./nixos-module.nix;
};
packages = lib.genAttrs systems (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
arion = arionFromPkgs pkgs;
});
# Does not include the eval and build functions like you may expect from Nixpkgs.
defaultPackage = lib.genAttrs systems (system:
self.packages.${system}.arion
);
lib = {
eval = import ./src/nix/eval-composition.nix;
build = args@{...}:
let composition = self.lib.eval args;
in composition.config.out.dockerComposeYaml;
};
};
}

49
nix/ci.nix Normal file
View file

@ -0,0 +1,49 @@
let
sources = import ./sources.nix;
lib = import (sources."nixos-unstable" + "/lib");
inherit (import (sources."project.nix" + "/lib/dimension.nix") { inherit lib; }) dimension;
in
dimension "Nixpkgs version" {
"nixos-20_09" = {
nixpkgsSource = "nixos-20.09";
enableDoc = true;
dockerSupportsSystemd = true;
nixosHasPodmanDockerSocket = false;
};
"nixos-21_05" = {
nixpkgsSource = "nixos-21.05";
enableDoc = true;
};
"nixos-unstable" = {
nixpkgsSource = "nixos-unstable";
isReferenceNixpkgs = true; # match ./default.nix
enableDoc = true;
};
} (
_name: { nixpkgsSource, isReferenceNixpkgs ? false, enableDoc ? true,
dockerSupportsSystemd ? false, nixosHasPodmanDockerSocket ? true }:
dimension "System" {
"x86_64-linux" = { isReferenceTarget = isReferenceNixpkgs; };
"x86_64-darwin" = { enableNixOSTests = false; };
} (
system: { isReferenceTarget ? false, enableNixOSTests ? true }:
let
pkgs = import ./. {
inherit system dockerSupportsSystemd nixosHasPodmanDockerSocket;
nixpkgsSrc = sources.${nixpkgsSource};
};
in
{
inherit (pkgs) arion;
} // lib.optionalAttrs enableNixOSTests {
inherit (pkgs) tests;
} // lib.optionalAttrs enableDoc {
inherit (pkgs) doc doc-options doc-options-check;
} // lib.optionalAttrs isReferenceTarget {
inherit (pkgs.arion-project.haskellPkgs) arion-compose-checked;
}
)
)

View file

@ -1,10 +0,0 @@
(import
(
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/009399224d5e398d03b22badca40a37ac85412a1.tar.gz";
sha256 = "sha256:0xcr9fibnapa12ywzcnlf54wrmbqqb96fmmv8043zhsycws7bpqy";
}
)
{ src = ../.; }
).defaultNix

23
nix/default.nix Normal file
View file

@ -0,0 +1,23 @@
{ sources ? import ./sources.nix
, nixpkgsName ? "nixos-unstable" # match ./ci.nix isReferenceNixpkgs
, nixpkgsSrc ? sources.${nixpkgsName}
, system ? builtins.currentSystem
, dockerSupportsSystemd ? false
, nixosHasPodmanDockerSocket ? true
, ...
}:
import nixpkgsSrc ({
# Makes the config pure as well. See <nixpkgs>/top-level/impure.nix:
config = {
};
overlays = [
(_: _: {
arionTestingFlags = {
inherit dockerSupportsSystemd nixosHasPodmanDockerSocket;
};
})
(import ./overlay.nix)
];
inherit system;
})

View file

@ -6,12 +6,6 @@ let
inherit (pkgs.haskell.lib) overrideCabal addBuildTools;
in
overrideCabal (addBuildTools (haskellPackages.callCabal2nix "arion-compose" ./.. {}) [pkgs.nix]) (o: o // {
src = pkgs.lib.sourceByRegex ../. [
".*[.]cabal"
"LICENSE"
"src/?.*"
"README.asciidoc"
];
preCheck = ''
export NIX_LOG_DIR=$TMPDIR
export NIX_STATE_DIR=$TMPDIR

16
nix/haskell-overlay.nix Normal file
View file

@ -0,0 +1,16 @@
self: super: hself: hsuper:
{
arion-compose = import ./haskell-arion-compose.nix { pkgs = self; haskellPackages = hself; };
arion-compose-checked =
let pkg = super.haskell.lib.buildStrictly hself.arion-compose;
checked = super.haskell.lib.overrideCabal pkg (o: {
postConfigure = ''${o.postConfigure or ""}
if ! ${hsuper.cabal-install}/bin/cabal check;
then
echo 1>&2 ERROR: cabal file is invalid. Above warnings were errors.
exit 1
fi
'';
});
in checked;
}

68
nix/overlay.nix Normal file
View file

@ -0,0 +1,68 @@
self: super:
let
inherit (self.arion-project) haskellPkgs;
inherit (super) lib;
sources = import ./sources.nix;
fakeRepo = src: super.runCommand "source" { inherit src; nativeBuildInputs = [super.git]; } ''
cp -r --no-preserve=mode $src $out
git init
cp -r .git $out
'';
in
{
inherit (import ./.. { pkgs = self; }) arion;
tests = super.callPackage ../tests {};
doc-options = import ../docs/options.nix {};
doc-options-check = self.runCommand "doc-options-check" {} ''
if diff --color -u ${../docs/modules/ROOT/partials/NixOSOptions.adoc} ${self.doc-options}; then
touch $out
else
echo 1>&2 "The doc options have changed and need to be added."
echo 1>&2 "Please run ./update-options in the root of your arion clone."
exit 1
fi
'';
doc = self.stdenv.mkDerivation {
name = "arion-documentation";
nativeBuildInputs = [super.antora];
src = fakeRepo ../.;
HOME = ".";
buildPhase = "antora antora-playbook";
installPhase = ''
mkdir $out
mv public/* $out/
'';
};
arion-project = super.recurseIntoAttrs {
haskellPkgs = super.haskellPackages.extend (import ./haskell-overlay.nix self super);
shell = haskellPkgs.shellFor {
packages = p: [p.arion-compose];
nativeBuildInputs = [
haskellPkgs.cabal-install
haskellPkgs.ghcid
haskellPkgs.haskell-language-server
self.docker-compose
self.podman
self.podman-compose
self.niv
self.releaser
];
};
};
podman-compose = super.podman-compose.overrideAttrs(o: {
src = ~/h/podman-compose;
# patches = (o.patches or []) ++ [
# ./podman-compose-stop_signal.patch
# ];
});
inherit (import (sources.niv) {}) niv;
releaser = self.haskellPackages.callCabal2nix "releaser" sources.releaser {};
}

75
nix/sources.json Normal file
View file

@ -0,0 +1,75 @@
{
"niv": {
"branch": "master",
"description": "Easy dependency management for Nix projects",
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "fad2a6cbfb2e7cdebb7cb0ad2f5cc91e2c9bc06b",
"sha256": "0mghc1j0rd15spdjx81bayjqr0khc062cs25y5dcfzlxk4ynyc6m",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/fad2a6cbfb2e7cdebb7cb0ad2f5cc91e2c9bc06b.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.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/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixos-21.05": {
"branch": "nixos-21.05",
"description": "Nix Packages collection",
"homepage": null,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "297970378b9437541c065f3fef26871397edd2d4",
"sha256": "1q5dnylr4w1xqn3qxx7hn0pn01pcwdmsy70cjs01dn8b50ppc93g",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/297970378b9437541c065f3fef26871397edd2d4.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixos-unstable": {
"branch": "master",
"description": "A read-only mirror of NixOS/nixpkgs tracking the released channels. Send issues and PRs to",
"homepage": "https://github.com/NixOS/nixpkgs",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "97c3d70a39070547a8342f7ee6f5c4a560282179",
"sha256": "1pkagmf42n3v4bjk8jr23hcwpa5qy21w0psi0jbdrbsgpp6rchqa",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/97c3d70a39070547a8342f7ee6f5c4a560282179.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz",
"version": ""
},
"project.nix": {
"branch": "master",
"description": "A configuration manager for your projects",
"homepage": null,
"owner": "hercules-ci",
"repo": "project.nix",
"rev": "2e598501e7fda6993d2a1a281aa296b26d01e10c",
"sha256": "1rkzpzxpg69px6qwchdlg4xf5irv0snrzk2l6vrs9rsx48gqax9j",
"type": "tarball",
"url": "https://github.com/hercules-ci/project.nix/archive/2e598501e7fda6993d2a1a281aa296b26d01e10c.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"releaser": {
"branch": "master",
"description": "Automation of Haskell package release process.",
"homepage": null,
"owner": "domenkozar",
"repo": "releaser",
"rev": "52a2bb0b2ce0bc15d4e7b11d8761a28d82c0c083",
"sha256": "178lv0a0qxd8six0rm83j7wjwlsad1hysdrk4mb38fagbb8csagb",
"type": "tarball",
"url": "https://github.com/domenkozar/releaser/archive/52a2bb0b2ce0bc15d4e7b11d8761a28d82c0c083.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}
}

171
nix/sources.nix Normal file
View file

@ -0,0 +1,171 @@
# This file has been generated by Niv.
let
#
# The fetchers. fetch_<type> fetches specs of type <type>.
#
fetch_file = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; name = name'; }
else
pkgs.fetchurl { inherit (spec) url sha256; name = name'; };
fetch_tarball = pkgs: name: spec:
let
name' = sanitizeName name + "-src";
in
if spec.builtin or true then
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
else
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
fetch_git = name: spec:
let
ref =
if spec ? ref then spec.ref else
if spec ? branch then "refs/heads/${spec.branch}" else
if spec ? tag then "refs/tags/${spec.tag}" else
abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!";
in
builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; };
fetch_local = spec: spec.path;
fetch_builtin-tarball = name: throw
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=tarball -a builtin=true'';
fetch_builtin-url = name: throw
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=file -a builtin=true'';
#
# Various helpers
#
# https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695
sanitizeName = name:
(
concatMapStrings (s: if builtins.isList s then "-" else s)
(
builtins.split "[^[:alnum:]+._?=-]+"
((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name)
)
);
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources: system:
let
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; };
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
in
if builtins.hasAttr "nixpkgs" sources
then sourcesNixpkgs
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
import <nixpkgs> {}
else
abort
''
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
add a package called "nixpkgs" to your sources.json.
'';
# The actual fetching function.
fetch = pkgs: name: spec:
if ! builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then fetch_file pkgs name spec
else if spec.type == "tarball" then fetch_tarball pkgs name spec
else if spec.type == "git" then fetch_git name spec
else if spec.type == "local" then fetch_local spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
else if spec.type == "builtin-url" then fetch_builtin-url name
else
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# If the environment variable NIV_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
replace = name: drv:
let
saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
in
if ersatz == "" then drv else ersatz;
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
mapAttrs = builtins.mapAttrs or (
f: set: with builtins;
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatMapStrings = f: list: concatStrings (map f list);
concatStrings = builtins.concatStringsSep "";
# https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331
optionalAttrs = cond: as: if cond then as else {};
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchTarball;
in
if lessThan nixVersion "1.12" then
fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
else
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, name ? null, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchurl;
in
if lessThan nixVersion "1.12" then
fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; }))
else
fetchurl attrs;
# Create the final "sources" from the config
mkSources = config:
mapAttrs (
name: spec:
if builtins.hasAttr "outPath" spec
then abort
"The values in sources.json should not have an 'outPath' attribute"
else
spec // { outPath = replace name (fetch config.pkgs name spec); }
) config.sources;
# The "config" used by the fetchers
mkConfig =
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
, sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
, system ? builtins.currentSystem
, pkgs ? mkPkgs sources system
}: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
};
in
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }

View file

@ -57,7 +57,7 @@ let
mv $out/bin/arion $out/libexec
makeWrapper $out/libexec/arion $out/bin/arion \
--unset PYTHONPATH \
--prefix PATH : ${lib.makeBinPath [ pkgs.docker-compose ]} \
--prefix PATH : ${lib.makeBinPath [ pkgs.docker-compose pkgs.podman pkgs.podman-compose ]} \
;
'';
};
@ -79,7 +79,9 @@ 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.
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.
*/
build = args@{...}:
let composition = eval args;

View file

@ -1,118 +0,0 @@
{ config, lib, options, pkgs, ... }:
let
inherit (lib)
attrValues
mkIf
mkOption
mkMerge
types
;
cfg = config.virtualisation.arion;
projectType = types.submoduleWith {
modules = [ projectModule ];
};
projectModule = { config, name, ... }: {
options = {
settings = mkOption {
description = ''
Arion project definition, otherwise known as arion-compose.nix contents.
See <link xlink:href="https://docs.hercules-ci.com/arion/options/">https://docs.hercules-ci.com/arion/options/</link>.
'';
type = arionSettingsType name;
visible = "shallow";
};
_systemd = mkOption { internal = true; };
serviceName = mkOption {
description = "The name of the Arion project's systemd service";
type = types.str;
default = "arion-${name}";
};
};
config = {
_systemd.services.${config.serviceName} = {
wantedBy = [ "multi-user.target" ];
after = [ "sockets.target" ];
path = [
cfg.package
cfg.docker.client.package
];
environment.ARION_PREBUILT = config.settings.out.dockerComposeYaml;
script = ''
echo 1>&2 "docker compose file: $ARION_PREBUILT"
arion --prebuilt-file "$ARION_PREBUILT" up
'';
};
};
};
arionSettingsType = name:
(cfg.package.eval { modules = [{ project.name = lib.mkDefault name; }]; }).type or (
throw "lib.evalModules did not produce a type. Please upgrade Nixpkgs to nixos-unstable or >=nixos-21.11"
);
in
{
disabledModules = [ "virtualisation/arion.nix" ];
options = {
virtualisation.arion = {
backend = mkOption {
type = types.enum [ "podman-socket" "docker" ];
description = ''
Which container implementation to use.
'';
};
package = mkOption {
type = types.package;
default = (import ./. { inherit pkgs; }).arion;
description = ''
Arion package to use. This will provide <literal>arion</literal>
executable that starts the project.
It also must provide the arion <literal>eval</literal> function as
an attribute.
'';
};
docker.client.package = mkOption {
type = types.package;
internal = true;
};
projects = mkOption {
type = types.attrsOf projectType;
default = { };
description = ''
Arion projects to be run as a service.
'';
};
};
};
config = mkIf (cfg.projects != { }) (
mkMerge [
{
systemd = mkMerge (map (p: p._systemd) (attrValues cfg.projects));
}
(mkIf (cfg.backend == "podman-socket") {
virtualisation.docker.enable = false;
virtualisation.podman.enable = true;
virtualisation.podman.dockerSocket.enable = true;
virtualisation.podman.defaultNetwork =
if options?virtualisation.podman.defaultNetwork.settings
then { settings.dns_enabled = true; } # since 2023-01 https://github.com/NixOS/nixpkgs/pull/199965
else { dnsname.enable = true; }; # compat <2023
virtualisation.arion.docker.client.package = pkgs.docker-client;
})
(mkIf (cfg.backend == "docker") {
virtualisation.docker.enable = true;
virtualisation.arion.docker.client.package = pkgs.docker;
})
]
);
}

View file

@ -3,4 +3,4 @@
# For manual testing of a hacked arion built via Nix.
# Works when called from outside the project directory.
exec nix run -f "$(dirname ${BASH_SOURCE[0]})" arion "$@"
exec nix run -f "$(dirname ${BASH_SOURCE[0]})" arion -c arion "$@"

View file

@ -1 +1 @@
(builtins.getFlake ("git+file://" + toString ./.)).devShells.${builtins.currentSystem}.default
args@{...}: (import ./nix args).arion-project.shell

View file

@ -10,7 +10,7 @@ import Arion.Aeson
import Arion.Images (loadImages)
import qualified Arion.DockerCompose as DockerCompose
import Arion.Services (getDefaultExec)
import Arion.ExtendedInfo (loadExtendedInfoFromPath, ExtendedInfo(images, projectName))
import Arion.ExtendedInfo (loadExtendedInfoFromPath, ExtendedInfo(images, projectName), technology, Technology(..))
import Options.Applicative
import Control.Monad.Fail
@ -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. Specify before command.")
<> help "Causes Nix to print out a stack trace in case of Nix expression evaluation errors.")
-- 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
@ -147,6 +147,7 @@ runDC cmd (DockerComposeArgs args) _opts = do
DockerCompose.run DockerCompose.Args
{ files = []
, otherArgs = [cmd] ++ args
, isPodman = True -- FIXME
}
runBuildAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
@ -160,11 +161,13 @@ runEvalAndDC cmd dopts opts = do
callDC :: Text -> DockerComposeArgs -> CommonOptions -> Bool -> FilePath -> IO ()
callDC cmd dopts opts shouldLoadImages path = do
extendedInfo <- loadExtendedInfoFromPath path
when shouldLoadImages $ loadImages (images extendedInfo)
let is_podman = technology extendedInfo == Podman
when shouldLoadImages $ loadImages is_podman (images extendedInfo)
let firstOpts = projectArgs extendedInfo <> commonArgs opts
DockerCompose.run DockerCompose.Args
{ files = [path]
, otherArgs = firstOpts ++ [cmd] ++ unDockerComposeArgs dopts
, isPodman = is_podman
}
projectArgs :: ExtendedInfo -> [Text]
@ -299,6 +302,7 @@ runExec detach privileged user noTTY index envs workDir service commandAndArgs o
[] -> ["/bin/sh"]
x -> x
let is_podman = technology extendedInfo == Podman
let args = concat
[ ["exec"]
, ("--detach" <$ guard detach :: [Text])
@ -314,6 +318,7 @@ runExec detach privileged user noTTY index envs workDir service commandAndArgs o
DockerCompose.run DockerCompose.Args
{ files = [path]
, otherArgs = projectArgs extendedInfo <> commonArgs opts <> args
, isPodman = is_podman
}
main :: IO ()

View file

@ -8,6 +8,7 @@ import System.Process
data Args = Args
{ files :: [FilePath]
, otherArgs :: [Text]
, isPodman :: Bool
}
run :: Args -> IO ()
@ -15,9 +16,9 @@ run args = do
let fileArgs = files args >>= \f -> ["--file", f]
allArgs = fileArgs ++ map toS (otherArgs args)
procSpec = proc "docker-compose" allArgs
-- hPutStrLn stderr ("Running docker-compose with " <> show allArgs :: Text)
exeName = if isPodman args then "podman-compose" else "docker-compose"
procSpec =
proc exeName allArgs
withCreateProcess procSpec $ \_in _out _err procHandle -> do
@ -27,4 +28,4 @@ run args = do
ExitSuccess -> pass
ExitFailure 1 -> exitFailure
ExitFailure {} -> do
throwIO $ FatalError $ "docker-compose failed with " <> show exitCode
throwIO $ FatalError $ toS exeName <> " failed with status " <> show exitCode

View file

@ -22,9 +22,13 @@ data Image = Image
, imageTag :: Text
} deriving (Eq, Show, Generic, Aeson.ToJSON, Aeson.FromJSON)
data Technology = Docker | Podman
deriving (Eq, Show)
data ExtendedInfo = ExtendedInfo {
projectName :: Maybe Text,
images :: [Image]
images :: [Image],
technology :: Technology
} deriving (Eq, Show)
loadExtendedInfoFromPath :: FilePath -> IO ExtendedInfo
@ -33,5 +37,10 @@ loadExtendedInfoFromPath fp = do
pure ExtendedInfo {
-- TODO: use aeson derived instance?
projectName = v ^? key "x-arion" . key "project" . key "name" . _String,
images = (v :: Aeson.Value) ^.. key "x-arion" . key "images" . _Array . traverse . _JSON
images = (v :: Aeson.Value) ^.. key "x-arion" . key "images" . _Array . traverse . _JSON,
technology =
case v ^? key "x-arion" . key "technology" . _String of
Just "podman" -> Podman
Just "docker" -> Docker
_ -> panic "Unknown x-arion.technology" -- Shouldn't happen
}

View file

@ -16,36 +16,40 @@ import Arion.ExtendedInfo (Image(..))
type TaggedImage = Text
-- | Subject to change
loadImages :: [Image] -> IO ()
loadImages requestedImages = do
loadImages :: Bool -> [Image] -> IO ()
loadImages isPodman requestedImages = do
loaded <- getDockerImages
loaded <- getDockerImages isPodman
let
isNew i =
-- On docker, the image name is unmodified
(imageName i <> ":" <> imageTag i) `notElem` loaded
-- On podman, you used to automatically get a localhost prefix
-- however, since NixOS 22.05, this expected to be part of the name instead
-- -- On podman, you automatically get a localhost prefix
&& ("localhost/" <> imageName i <> ":" <> imageTag i) `notElem` loaded
traverse_ loadImage . filter isNew $ requestedImages
traverse_ (loadImage isPodman) . filter isNew $ requestedImages
loadImage :: Image -> IO ()
loadImage Image { image = Just imgPath, imageName = name } =
exeName :: IsString p => Bool -> p
exeName _isPodman@True = "podman"
exeName _isPodman@False = "docker"
loadImage :: Bool -> Image -> IO ()
loadImage isPodman Image { image = Just imgPath, imageName = name } =
withFile (toS imgPath) ReadMode $ \fileHandle -> do
let procSpec = (Process.proc "docker" [ "load" ]) {
let procSpec = (Process.proc (exeName isPodman) [ "load" ]) {
Process.std_in = Process.UseHandle fileHandle
}
print procSpec
Process.withCreateProcess procSpec $ \_in _out _err procHandle -> do
e <- Process.waitForProcess procHandle
case e of
ExitSuccess -> pass
ExitFailure code ->
panic $ "docker load failed with exit code " <> show code <> " for image " <> name <> " from path " <> imgPath
panic $ exeName isPodman <> " load failed with exit code " <> show code <> " for image " <> name <> " from path " <> imgPath
loadImage Image { imageExe = Just imgExe, imageName = name } = do
let loadSpec = (Process.proc "docker" [ "load" ]) { Process.std_in = Process.CreatePipe }
loadImage isPodman Image { imageExe = Just imgExe, imageName = name } = do
let loadSpec = (Process.proc (exeName isPodman) [ "load" ]) { Process.std_in = Process.CreatePipe }
Process.withCreateProcess loadSpec $ \(Just inHandle) _out _err loadProcHandle -> do
let streamSpec = Process.proc (toS imgExe) []
Process.withCreateProcess streamSpec { Process.std_out = Process.UseHandle inHandle } $ \_ _ _ streamProcHandle ->
@ -58,15 +62,15 @@ loadImage Image { imageExe = Just imgExe, imageName = name } = do
Left _ -> pass
loadExit <- wait loadExitAsync
case loadExit of
ExitFailure code -> panic $ "docker load failed with exit code " <> show code <> " for image " <> name <> " produced by executable " <> imgExe
ExitFailure code -> panic $ exeName isPodman <> " load failed with exit code " <> show code <> " for image " <> name <> " produced by executable " <> imgExe
_ -> pass
pass
loadImage Image { imageName = name } = do
loadImage _isPodman Image { imageName = name } = do
panic $ "image " <> name <> " doesn't specify an image file or imageExe executable"
getDockerImages :: IO [TaggedImage]
getDockerImages = do
let procSpec = Process.proc "docker" [ "images", "--filter", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}" ]
getDockerImages :: Bool -> IO [TaggedImage]
getDockerImages isPodman = do
let procSpec = Process.proc (exeName isPodman) [ "images", "--filter", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}" ]
map toS . T.lines . toS <$> Process.readCreateProcess procSpec ""

View file

@ -1,7 +1,6 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
module Arion.Services
( getDefaultExec
) where
@ -10,28 +9,15 @@ import Prelude()
import Protolude hiding (to)
import qualified Data.Aeson as Aeson
#if MIN_VERSION_lens_aeson(1,2,0)
import qualified Data.Aeson.Key as AK
#endif
import Arion.Aeson (decodeFile)
import Control.Lens
import Data.Aeson.Lens
#if MIN_VERSION_lens_aeson(1,2,0)
type Key = AK.Key
mkKey :: Text -> Key
mkKey = AK.fromText
#else
type Key = Text
mkKey :: Text -> Key
mkKey = identity
#endif
-- | Subject to change
getDefaultExec :: FilePath -> Text -> IO [Text]
getDefaultExec fp service = do
v <- decodeFile fp
pure ((v :: Aeson.Value) ^.. key "x-arion" . key "serviceInfo" . key (mkKey service) . key "defaultExec" . _Array . traverse . _String)
pure ((v :: Aeson.Value) ^.. key "x-arion" . key "serviceInfo" . key service . key "defaultExec" . _Array . traverse . _String)

View file

@ -13,34 +13,19 @@ import qualified Data.Text as T
import qualified Data.Text.IO as T
spec :: Spec
spec = describe "evaluateComposition" $ do
it "matches an example" $ do
x <- Arion.Nix.evaluateComposition EvaluationArgs
{ evalUid = 123
, evalModules = NEL.fromList
["src/haskell/testdata/Arion/NixSpec/arion-compose.nix"]
, evalPkgs = "import <nixpkgs> { system = \"x86_64-linux\"; }"
, evalWorkDir = Nothing
, evalMode = ReadOnly
, evalUserArgs = ["--show-trace"]
}
let actual = pretty x
expected <- T.readFile "src/haskell/testdata/Arion/NixSpec/arion-compose.json"
censorPaths actual `shouldBe` censorPaths expected
it "matches an build.context example" $ do
x <- Arion.Nix.evaluateComposition EvaluationArgs
{ evalUid = 1234
, evalModules = NEL.fromList
["src/haskell/testdata/Arion/NixSpec/arion-context-compose.nix"]
, evalPkgs = "import <nixpkgs> { system = \"x86_64-linux\"; }"
, evalWorkDir = Nothing
, evalMode = ReadOnly
, evalUserArgs = ["--show-trace"]
}
let actual = pretty x
expected <- T.readFile "src/haskell/testdata/Arion/NixSpec/arion-context-compose.json"
censorPaths actual `shouldBe` censorPaths expected
spec = describe "evaluateComposition" $ it "matches an example" $ do
x <- Arion.Nix.evaluateComposition EvaluationArgs
{ evalUid = 123
, evalModules = NEL.fromList
["src/haskell/testdata/Arion/NixSpec/arion-compose.nix"]
, evalPkgs = "import <nixpkgs> { system = \"x86_64-linux\"; }"
, evalWorkDir = Nothing
, evalMode = ReadOnly
, evalUserArgs = ["--show-trace"]
}
let actual = pretty x
expected <- T.readFile "src/haskell/testdata/Arion/NixSpec/arion-compose.json"
censorPaths actual `shouldBe` censorPaths expected
censorPaths :: Text -> Text
censorPaths = censorImages . censorStorePaths

View file

@ -9,4 +9,3 @@ import qualified Arion.NixSpec
spec :: Spec
spec = do
describe "Arion.Nix" Arion.NixSpec.spec

View file

@ -1,9 +1,4 @@
{
"networks": {
"default": {
"name": "unit-test-data"
}
},
"services": {
"webserver": {
"command": [
@ -14,7 +9,7 @@
"PATH": "/usr/bin:/run/current-system/sw/bin/",
"container": "docker"
},
"image": "localhost/webserver:<HASH>",
"image": "webserver:<HASH>",
"ports": [
"8000:80"
],
@ -33,17 +28,16 @@
}
},
"version": "3.4",
"volumes": {},
"x-arion": {
"images": [
{
"imageExe": "<STOREPATH>",
"imageName": "localhost/webserver",
"imageName": "webserver",
"imageTag": "<HASH>"
}
],
"project": {
"name": "unit-test-data"
"name": null
},
"serviceInfo": {
"webserver": {
@ -52,6 +46,7 @@
"-l"
]
}
}
},
"technology": "docker"
}
}

View file

@ -1,8 +1,7 @@
{
project.name = "unit-test-data";
services.webserver = { pkgs, ... }: {
nixos.useSystemd = true;
nixos.configuration.boot.tmp.useTmpfs = true;
nixos.configuration.boot.tmpOnTmpfs = true;
nixos.configuration.services.nginx.enable = true;
nixos.configuration.services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
service.useHostStore = true;

View file

@ -1,41 +0,0 @@
{
"networks": {
"default": {
"name": "unit-test-data"
}
},
"services": {
"webserver": {
"build": {
"context": "<STOREPATH>"
},
"environment": {},
"ports": [
"8080:80"
],
"sysctls": {},
"volumes": []
}
},
"version": "3.4",
"volumes": {},
"x-arion": {
"images": [
{
"imageExe": "<STOREPATH>",
"imageName": "localhost/webserver",
"imageTag": "<HASH>"
}
],
"project": {
"name": "unit-test-data"
},
"serviceInfo": {
"webserver": {
"defaultExec": [
"/bin/sh"
]
}
}
}
}

View file

@ -1,9 +0,0 @@
{
project.name = "unit-test-data";
services.webserver.service = {
build.context = "${./build-context}";
ports = [
"8080:80"
];
};
}

View file

@ -1,4 +0,0 @@
FROM nginx
RUN echo this is a dockerfile to be built

View file

@ -12,6 +12,7 @@ let
inherit (pkgs) lib;
composition = lib.evalModules {
check = true;
modules = builtinModules ++ modules;
};
@ -23,7 +24,6 @@ let
_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 @@ in
composition // {
# throw in lib and pkgs for repl convenience
inherit lib;
inherit (composition._module.args) pkgs;
inherit (composition.config._module.args) pkgs;
}

View file

@ -1,21 +0,0 @@
{ lib }:
let
link = url: text: ''[${text}](${url})'';
composeSpecRev = "55b450aee50799a2f33cc99e1d714518babe305e";
serviceRef = fragment:
''See ${link "https://github.com/compose-spec/compose-spec/blob/${composeSpecRev}/05-services.md#${fragment}" "Compose Spec Services #${fragment}"}'';
networkRef = fragment:
''See ${link "https://github.com/compose-spec/compose-spec/blob/${composeSpecRev}/06-networks.md#${fragment}" "Compose Spec Networks #${fragment}"}'';
in
{
inherit
link
networkRef
serviceRef
;
}

View file

@ -2,7 +2,9 @@
./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
]
./modules/composition/deployment.nix
./modules/composition/deployment/docker.nix
./modules/composition/deployment/podman.nix
]

View file

@ -3,23 +3,19 @@ let
inherit (lib) types mkOption;
link = url: text:
''[${text}](${url})'';
''link:${url}[${text}]'';
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.str;
type = types.nullOr types.str;
default = null;
};
};
config = {

View file

@ -0,0 +1,15 @@
{ config, lib, ... }:
let
inherit (lib) mkOption types;
in
{
options = {
deployment.technology = mkOption {
description = "Which container technology to use.";
type = types.enum [];
};
};
config = {
docker-compose.raw.x-arion.technology = config.deployment.technology;
};
}

View file

@ -0,0 +1,12 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
options = {
deployment.technology = mkOption {
type = types.enum ["docker"];
default = "docker";
};
};
}

View file

@ -0,0 +1,11 @@
{ lib, ... }:
let
inherit (lib) mkOption types;
in
{
options = {
deployment.technology = mkOption {
type = types.enum ["podman"];
};
};
}

View file

@ -29,6 +29,20 @@ let
config.service.name = name;
};
json.type = with lib.types; let
valueType = nullOr (oneOf [
bool
int
float
str
path # extra
package # extra
(attrsOf valueType)
(listOf valueType)
]) // {
description = "JSON value";
};
in valueType;
in
{
imports = [
@ -52,22 +66,17 @@ in
readOnly = true;
};
docker-compose.raw = lib.mkOption {
type = lib.types.attrs;
type = json.type;
description = "Attribute set that will be turned into the docker-compose.yaml file, using Nix's toJSON builtin.";
};
docker-compose.extended = lib.mkOption {
type = lib.types.attrs;
type = json.type;
description = "Attribute set that will be turned into the x-arion section of the docker-compose.yaml file.";
};
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.";
};
docker-compose.volumes = lib.mkOption {
type = lib.types.attrsOf lib.types.unspecified;
description = "A attribute set of volume configurations.";
default = {};
};
};
config = {
out.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.out.dockerComposeYamlText;
@ -78,7 +87,6 @@ in
version = "3.4";
services = lib.mapAttrs (k: c: c.out.service) config.services;
x-arion = config.docker-compose.extended;
volumes = config.docker-compose.volumes;
};
};
}

View file

@ -23,9 +23,9 @@
stored at an alternate location without altering the format of
store paths.
For example: instead of mounting the host's `/nix/store` as the
container's `/nix/store`, this will mount `/mnt/foo/nix/store`
as the container's `/nix/store`.
For example: instead of mounting the host's /nix/store as the
container's /nix/store, this will mount /mnt/foo/nix/store
as the container's /nix/store.
'';
};

View file

@ -36,7 +36,7 @@ in
build.imagesToLoad = lib.mkOption {
type = listOf unspecified;
internal = true;
description = "List of `dockerTools` image derivations.";
description = "List of dockerTools image derivations.";
};
};
config = {

View file

@ -1,53 +0,0 @@
{ config, lib, ... }:
let
inherit (lib)
mkOption
optionalAttrs
types
;
inherit (import ../../lib.nix { inherit lib; })
link
;
in
{
options = {
networks = mkOption {
type = types.lazyAttrsOf (types.submoduleWith {
modules = [
../networks/network.nix
];
});
description = ''
See ${link "https://docs.docker.com/compose/compose-file/06-networks/" "Docker Compose Networks"}
'';
};
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;
};
}

View file

@ -1,131 +0,0 @@
{ config, lib, options, ... }:
let
inherit (lib)
mkOption
optionalAttrs
types
;
inherit (import ../../lib.nix { inherit lib; })
networkRef
;
in
{
options = {
driver = mkOption {
description = ''
`"none"`, `"host"`, or a platform-specific value.
${networkRef "driver"}
'';
type = types.str;
};
driver_opts = mkOption {
description = ''
${networkRef "driver_opts"}
'';
type = types.lazyAttrsOf types.raw or types.unspecified;
};
attachable = mkOption {
description = ''
${networkRef "attachable"}
'';
type = types.bool;
example = true;
};
enable_ipv6 = mkOption {
description = ''
Whether we've entered the 21st century yet.
${networkRef "enable_ipv6"}
'';
type = types.bool;
};
ipam = mkOption {
# TODO model sub-options
description = ''
Manage IP addresses.
${networkRef "ipam"}
'';
type = types.raw or types.unspecified;
};
internal = mkOption {
description = ''
Achieves "external isolation".
${networkRef "internal"}
'';
defaultText = false;
type = types.bool;
};
labels = mkOption {
description = ''
Metadata.
${networkRef "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.
${networkRef "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.
${networkRef "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
;
}
);
};
}

View file

@ -4,16 +4,21 @@
the user-facing options service.image, service.volumes, etc.
*/
{ pkgs, lib, config, options, ... }:
{ pkgs, lib, config, ... }:
let
inherit (lib) mkOption types;
inherit (types) listOf nullOr attrsOf str either int bool submodule enum;
inherit (types) listOf nullOr attrsOf str either int bool;
inherit (import ../../lib.nix { inherit lib; })
link
serviceRef
;
link = url: text:
''link:${url}[${text}]'';
dockerComposeRef = fragment:
''See ${link "https://docs.docker.com/compose/compose-file/#${fragment}" "Docker Compose#${fragment}"}'';
dockerComposeKitchenSink = ''
Analogous to the `docker run` counterpart.
${dockerComposeRef "domainname-hostname-ipc-mac_address-privileged-read_only-shm_size-stdin_open-tty-user-working_dir"}
'';
cap_add = lib.attrNames (lib.filterAttrs (name: value: value == true) config.service.capabilities);
cap_drop = lib.attrNames (lib.filterAttrs (name: value: value == false) config.service.capabilities);
@ -50,12 +55,12 @@ in
service.volumes = mkOption {
type = listOf types.unspecified;
default = [];
description = serviceRef "volumes";
description = dockerComposeRef "volumes";
};
service.tmpfs = mkOption {
type = listOf types.str;
default = [];
description = serviceRef "tmpfs";
description = dockerComposeRef "tmpfs";
};
service.build.context = mkOption {
type = nullOr str;
@ -63,115 +68,42 @@ in
description = ''
Locates a Dockerfile to use for creating an image to use in this service.
https://docs.docker.com/compose/compose-file/build/#context
'';
};
service.build.dockerfile = mkOption {
type = nullOr str;
default = null;
description = ''
Sets an alternate Dockerfile. A relative path is resolved from the build context.
https://docs.docker.com/compose/compose-file/build/#dockerfile
'';
};
service.build.target = mkOption {
type = nullOr str;
default = null;
description = ''
Defines the stage to build as defined inside a multi-stage Dockerfile.
https://docs.docker.com/compose/compose-file/build/#target
${dockerComposeRef "context"}
'';
};
service.hostname = mkOption {
type = nullOr str;
default = null;
description = ''
${serviceRef "hostname"}
'';
description = dockerComposeKitchenSink;
};
service.tty = mkOption {
type = nullOr bool;
default = null;
description = ''
${serviceRef "tty"}
'';
description = dockerComposeKitchenSink;
};
service.environment = mkOption {
type = attrsOf (either str int);
default = {};
description = serviceRef "environment";
description = dockerComposeRef "environment";
};
service.image = mkOption {
type = nullOr str;
default = null;
description = serviceRef "image";
type = str;
description = dockerComposeRef "image";
};
service.command = mkOption {
type = nullOr types.unspecified;
default = null;
description = serviceRef "command";
description = dockerComposeRef "command";
};
service.container_name = mkOption {
type = nullOr types.str;
default = null;
description = serviceRef "container_name";
description = dockerComposeRef "container_name";
};
service.depends_on =
let conditionsModule = {
options = {
condition = mkOption {
type = enum ["service_started" "service_healthy" "service_completed_successfully"];
description = serviceRef "depends_on";
default = "service_started";
};
};
};
in mkOption {
type = either (listOf str) (attrsOf (submodule conditionsModule));
default = [];
description = serviceRef "depends_on";
};
service.healthcheck = mkOption {
description = serviceRef "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 = serviceRef "healthcheck";
};
interval = mkOption {
type = str;
default = "30s";
example = "1m";
description = serviceRef "healthcheck";
};
timeout = mkOption {
type = str;
default = "30s";
example = "10s";
description = serviceRef "healthcheck";
};
start_period = mkOption {
type = str;
default = "0s";
example = "30s";
description = serviceRef "healthcheck";
};
retries = mkOption {
type = int;
default = 3;
description = serviceRef "healthcheck";
};
};
});
service.depends_on = mkOption {
type = listOf str;
default = [];
description = dockerComposeRef "depends_on";
};
service.devices = mkOption {
type = listOf str;
@ -180,14 +112,14 @@ in
See ${link "https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities"
"`docker run --device` documentation"}
${serviceRef "devices"}
${dockerComposeRef "devices"}
'';
};
service.dns = mkOption {
type = listOf str;
default = [];
example = [ "8.8.8.8" "8.8.4.4" ];
description = serviceRef "dns";
description = dockerComposeRef "dns";
};
service.labels = mkOption {
type = attrsOf str;
@ -198,58 +130,47 @@ in
"traefik.http.routers.my-service.rule" = "Host(`my-service.localhost`)";
"traefik.http.routers.my-service.entrypoints" = "web";
};
description = serviceRef "labels";
description = dockerComposeRef "labels";
};
service.links = mkOption {
type = listOf str;
default = [];
description = serviceRef "links";
description = dockerComposeRef "links";
};
service.external_links = mkOption {
type = listOf str;
default = [];
description = serviceRef "external_links";
};
service.profiles = mkOption {
type = listOf str;
default = [];
description = serviceRef "profiles";
description = dockerComposeRef "external_links";
};
service.extra_hosts = mkOption {
type = listOf str;
default = [];
description = serviceRef "extra_hosts";
description = dockerComposeRef "extra_hosts";
};
service.working_dir = mkOption {
type = nullOr str;
default = null;
description = ''
${serviceRef "working_dir"}
'';
description = dockerComposeKitchenSink;
};
service.privileged = mkOption {
type = nullOr bool;
default = null;
description = ''
${serviceRef "privileged"}
'';
description = dockerComposeKitchenSink;
};
service.entrypoint = mkOption {
type = nullOr str;
default = null;
description = serviceRef "entrypoint";
description = dockerComposeRef "entrypoint";
};
service.restart = mkOption {
type = nullOr str;
default = null;
description = serviceRef "restart";
description = dockerComposeRef "restart";
};
service.user = mkOption {
type = nullOr str;
default = null;
description = ''
${serviceRef "user"}
'';
description = dockerComposeKitchenSink;
};
service.ports = mkOption {
type = listOf types.unspecified;
@ -257,76 +178,38 @@ in
description = ''
Expose ports on host. "host:container" or structured.
${serviceRef "ports"}
${dockerComposeRef "ports"}
'';
};
service.expose = mkOption {
type = listOf str;
default = [];
description = serviceRef "expose";
description = dockerComposeRef "expose";
};
service.env_file = mkOption {
type = listOf str;
default = [];
description = serviceRef "env_file";
description = dockerComposeRef "env_file";
};
service.network_mode = mkOption {
type = nullOr str;
default = null;
description = serviceRef "network_mode";
description = dockerComposeRef "network_mode";
};
service.networks = mkOption {
type = nullOr (listOf types.str);
default = null;
description = dockerComposeRef "networks";
};
service.networks =
let
networksModule = submodule ({ config, options, ...}: {
options = {
_out = mkOption {
internal = true;
readOnly = true;
default = lib.mapAttrs (k: opt: opt.value) (lib.filterAttrs (_: opt: opt.isDefined) { inherit (options) aliases ipv4_address ipv6_address link_local_ips priority; });
};
aliases = mkOption {
type = listOf str;
description = serviceRef "aliases";
default = [ ];
};
ipv4_address = mkOption {
type = str;
description = serviceRef "ipv4_address-ipv6_address";
};
ipv6_address = mkOption {
type = str;
description = serviceRef "ipv4_address-ipv6_address";
};
link_local_ips = mkOption {
type = listOf str;
description = serviceRef "link_local_ips";
};
priority = mkOption {
type = int;
description = serviceRef "priority";
};
};
});
in
mkOption {
type = either (listOf str) (attrsOf networksModule);
default = [];
description = serviceRef "networks";
};
service.stop_signal = mkOption {
type = nullOr str;
default = null;
description = serviceRef "stop_signal";
};
service.stop_grace_period = mkOption {
type = nullOr str;
default = null;
description = serviceRef "stop_grace_period";
description = dockerComposeRef "stop_signal";
};
service.sysctls = mkOption {
type = attrsOf (either str int);
default = {};
description = serviceRef "sysctls";
description = dockerComposeRef "sysctls";
};
service.capabilities = mkOption {
type = attrsOf (nullOr bool);
@ -337,15 +220,13 @@ in
Setting a capability to `true` means that it will be
"added". Setting it to `false` means that it will be "dropped".
${dockerComposeRef "cap_add-cap_drop"}
Omitted and `null` capabilities will therefore be set
according to Docker's ${
link "https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities"
"default list of capabilities."
}
${serviceRef "cap_add"}
${serviceRef "cap_drop"}
'';
};
};
@ -355,11 +236,10 @@ in
volumes
environment
sysctls
image
;
} // lib.optionalAttrs (config.service.image != null) {
inherit (config.service) image;
} // lib.optionalAttrs (config.service.build.context != null ) {
build = lib.filterAttrs (n: v: v != null) config.service.build;
} // lib.optionalAttrs (config.service.build.context != null) {
inherit (config.service) build;
} // lib.optionalAttrs (cap_add != []) {
inherit cap_add;
} // lib.optionalAttrs (cap_drop != []) {
@ -370,8 +250,6 @@ in
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) {
@ -398,16 +276,12 @@ in
inherit (config.service) privileged;
} // lib.optionalAttrs (config.service.network_mode != null) {
inherit (config.service) network_mode;
} // lib.optionalAttrs (config.service.networks != [] && config.service.networks != {}) {
networks =
if (builtins.isAttrs config.service.networks) then builtins.mapAttrs (_: v: v._out) config.service.networks
else config.service.networks;
} // lib.optionalAttrs (config.service.networks != null) {
inherit (config.service) networks;
} // lib.optionalAttrs (config.service.restart != null) {
inherit (config.service) restart;
} // lib.optionalAttrs (config.service.stop_signal != null) {
inherit (config.service) stop_signal;
} // lib.optionalAttrs (config.service.stop_grace_period != null) {
inherit (config.service) stop_grace_period;
} // lib.optionalAttrs (config.service.tmpfs != []) {
inherit (config.service) tmpfs;
} // lib.optionalAttrs (config.service.tty != null) {
@ -416,7 +290,5 @@ in
inherit (config.service) working_dir;
} // lib.optionalAttrs (config.service.user != null) {
inherit (config.service) user;
} // lib.optionalAttrs (config.service.profiles != []) {
inherit (config.service) profiles;
};
}

View file

@ -12,7 +12,7 @@ in
type = attrsOf unspecified;
description = ''
Information about a service to include in the Docker Compose file,
but that will not be used by the `docker-compose` command
but that will not be used by the `docker-compose`> command
itself.
It will be inserted in `x-arion.serviceInfo.<service.name>`.

View file

@ -20,7 +20,7 @@ in
service.hostStoreAsReadOnly = mkOption {
type = types.bool;
default = true;
description = "Adds a `:ro` (read-only) access mode to the host nix store bind mount.";
description = "Adds a ':ro' (read-only) access mode to the host nix store bind mount.";
};
service.useHostNixDaemon = mkOption {
type = types.bool;

View file

@ -30,7 +30,6 @@ let
{
name = null; tag = null; contents = null; config = null;
created = null; extraCommands = null; maxLayers = null;
fakeRootCommands = null;
}
args;
acceptedArgs = functionArgs dockerTools.streamLayeredImage;
@ -68,8 +67,6 @@ let
ln -s $i nix/var/nix/gcroots/docker/$(basename $i)
done;
'';
fakeRootCommands = config.image.fakeRootCommands;
};
priorityIsDefault = option: option.highestPrio >= (lib.mkDefault true).priority;
@ -79,18 +76,18 @@ in
build.image = mkOption {
type = nullOr package;
description = ''
Docker image derivation to be `docker load`-ed.
Docker image derivation to be `docker load`ed.
'';
internal = true;
};
build.imageName = mkOption {
type = str;
description = "Derived from `build.image`";
description = "Derived from build.image";
internal = true;
};
build.imageTag = mkOption {
type = str;
description = "Derived from `build.image`";
description = "Derived from build.image";
internal = true;
};
image.nixBuild = mkOption {
@ -107,8 +104,8 @@ in
};
image.name = mkOption {
type = str;
default = "localhost/" + config.service.name;
defaultText = lib.literalExpression or lib.literalExample ''"localhost/" + config.service.name'';
default = config.service.name;
defaultText = lib.literalExample "config.service.name";
description = ''
A human readable name for the docker image.
@ -123,22 +120,13 @@ in
Top level paths in the container.
'';
};
image.fakeRootCommands = mkOption {
type = types.lines;
default = "";
description = ''
Commands that build the root of the container in the current working directory.
See [`dockerTools.buildLayeredImage`](https://nixos.org/manual/nixpkgs/stable/#ssec-pkgs-dockerTools-buildLayeredImage).
'';
};
image.includeStorePaths = mkOption {
type = bool;
default = true;
internal = true;
description = ''
Include all referenced store paths. You generally want this in your
image, unless you load store paths via some other means, like `useHostStore = true`;
image, unless you load store paths via some other means, like useHostStore = true;
'';
};
image.rawConfig = mkOption {
@ -152,8 +140,8 @@ in
Please use the specific `image` options instead.
Run-time configuration of the container. A full list of the
options is available in the [Docker Image Specification
v1.2.0](https://github.com/moby/moby/blob/master/image/spec/v1.2.md#image-json-field-descriptions).
options is available in the https://github.com/moby/moby/blob/master/image/spec/v1.2.md#image-json-field-descriptions[Docker Image Specification
v1.2.0].
'';
};
image.command = mkOption {
@ -163,19 +151,17 @@ in
'';
};
};
config = lib.mkMerge [{
build.image = builtImage;
build.imageName = config.build.image.imageName;
build.imageTag =
if config.build.image.imageTag != ""
then config.build.image.imageTag
else lib.head (lib.strings.splitString "-" (baseNameOf config.build.image.outPath));
image.rawConfig.Cmd = config.image.command;
image.nixBuild = lib.mkDefault (priorityIsDefault options.service.image);
}
( lib.mkIf (config.service.build.context == null)
{
service.image = lib.mkDefault "${config.build.imageName}:${config.build.imageTag}";
})
];
config = {
build.image = builtImage;
build.imageName = config.build.image.imageName;
build.imageTag =
if config.build.image.imageTag != ""
then config.build.image.imageTag
else lib.head (lib.strings.splitString "-" (baseNameOf config.build.image.outPath));
service.image = lib.mkDefault "${config.build.imageName}:${config.build.imageTag}";
image.rawConfig.Cmd = config.image.command;
image.nixBuild = lib.mkDefault (priorityIsDefault options.service.image);
};
}

View file

@ -39,7 +39,7 @@ in
service.tmpfs = [
"/run" # noexec is fine because exes should be symlinked from elsewhere anyway
"/run/wrappers" # noexec breaks this intentionally
] ++ lib.optional (config.nixos.evaluatedConfig.boot.tmp.useTmpfs) "/tmp:exec,mode=777";
] ++ lib.optional (config.nixos.evaluatedConfig.boot.tmpOnTmpfs) "/tmp:exec,mode=777";
service.stop_signal = "SIGRTMIN+3";
service.tty = true;

View file

@ -1,4 +1,4 @@
{ usePodman ? false, pkgs, lib ? pkgs.lib, ... }:
{ usePodman ? false, pkgs, lib, ... }:
let
# To make some prebuilt derivations available in the vm
@ -8,19 +8,16 @@ let
};
inherit (lib)
concatMapStringsSep
optionalAttrs
optionalString
;
haveSystemd = usePodman || pkgs.arionTestingFlags.dockerSupportsSystemd;
concatPathLines = paths: concatMapStringsSep "\n" (x: "${x}") paths;
in
{
name = "arion-test";
nodes.machine = { pkgs, lib, ... }: {
machine = { pkgs, lib, ... }: {
environment.systemPackages = [
pkgs.arion
] ++ lib.optional usePodman pkgs.docker;
@ -29,13 +26,20 @@ in
enable = true;
dockerSocket.enable = true;
};
# no caches, because no internet
nix.settings.substituters = lib.mkForce [];
nix.binaryCaches = lib.mkForce [];
# FIXME: Sandbox seems broken with current version of NixOS test
# w/ writable store. Error:
# machine# error: linking '/nix/store/7r8z2zvhwda85pgpdn5hzzz6hs1njklc-stdenv-linux.drv.chroot/nix/store/6v3y7s4q4wd16hsw393gjpxvcf9159bv-patch-shebangs.sh' to '/nix/store/6v3y7s4q4wd16hsw393gjpxvcf9159bv-patch-shebangs.sh': Operation not permitted
#
# There should be no reason why arion can't run without
# sandboxing, so please re-enable.
nix.useSandbox = false;
virtualisation.writableStore = true;
# Switch to virtualisation.additionalPaths when dropping all NixOS <= 21.05.
environment.etc."extra-paths-for-test".text = concatPathLines [
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.out.dockerComposeYaml
@ -45,7 +49,7 @@ in
pkgs.stdenv
];
virtualisation.memorySize = 2048;
virtualisation.memorySize = 1024;
virtualisation.diskSize = 8000;
};
testScript = ''

25
tests/default.nix Normal file
View file

@ -0,0 +1,25 @@
{ pkgs ? import ../pkgs.nix, arionTestingFlags ? {} }:
let
inherit (pkgs) nixosTest recurseIntoAttrs arion;
in
recurseIntoAttrs {
test = nixosTest ./arion-test;
testWithPodman =
if arionTestingFlags.nixosHasPodmanDockerSocket
then nixosTest (pkgs.callPackage ./arion-test { usePodman = true; })
else {};
testBuild = arion.build {
# To be more accurately, you can do
# pkgs = import ../examples/minimal/arion-pkgs.nix;
# but this is quite efficient:
inherit pkgs;
modules = [ ../examples/minimal/arion-compose.nix ];
};
}

View file

@ -1,36 +0,0 @@
{
perSystem = { pkgs, final, ... }:
let
inherit (final) nixosTest arion lib;
in
{
checks = lib.optionalAttrs pkgs.stdenv.isLinux {
test = nixosTest ./arion-test;
nixosModuleWithDocker =
import ./nixos-virtualization-arion-test/test.nix final {
virtualisation.arion.backend = "docker";
};
# Currently broken; kafka can't reach zookeeper
# nixosModuleWithPodman =
# import ./nixos-virtualization-arion-test/test.nix final {
# virtualisation.arion.backend = "podman-socket";
# };
testWithPodman =
nixosTest (import ./arion-test { usePodman = true; pkgs = final; });
testBuild = arion.build {
# To be more accurate, we could do
# pkgs = import ../examples/minimal/arion-pkgs.nix;
# But let's avoid re-evaluating Nixpkgs
pkgs = final;
modules = [ ../examples/minimal/arion-compose.nix ];
};
};
};
}

View file

@ -1,6 +0,0 @@
# NixOS module test
This tests the NixOS module.
The images used here are experimental and not meant for production.

View file

@ -1,62 +0,0 @@
{ pkgs, ... }: {
project.name = "whale";
docker-compose.raw = {
volumes.zookeeper = { };
volumes.kafka = { };
};
services.kafka = {
service.useHostStore = true;
# service.volumes = [
# {
# type = "volume";
# source = "kafka";
# target = "/data";
# # volume.nocopy = true;
# }
# ];
service.ports = [ "9092:9092" ];
service.depends_on = [ "zookeeper" ];
image.name = "localhost/kafka";
image.contents = [
(pkgs.runCommand "root" { } ''
mkdir -p $out/bin
ln -s ${pkgs.runtimeShell} $out/bin/sh
'')
];
image.command = [
"${pkgs.apacheKafka}/bin/kafka-server-start.sh"
"${./kafka/server.properties}"
];
};
services.zookeeper = {
service.useHostStore = true;
service.ports = [ "2181:2181" ];
# service.volumes = [
# {
# type = "volume";
# source = "zookeeper";
# target = "/data";
# # volume.nocopy = true;
# }
# ];
image.name = "localhost/zookeeper";
image.contents = [
(pkgs.buildEnv {
name = "root";
paths = [
# pkgs.sed
pkgs.busybox
];
})
];
image.command = [
"${pkgs.zookeeper}/bin/zkServer.sh"
"--config"
"${./zookeeper}"
"start-foreground"
];
};
}

View file

@ -1,6 +0,0 @@
# NOTE: This isn't used in the module!
import <nixpkgs> {
# We specify the architecture explicitly. Use a Linux remote builder when
# calling arion from other platforms.
system = "x86_64-linux";
}

View file

@ -1,141 +0,0 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# see kafka.server.KafkaConfig for additional details and defaults
############################# Server Basics #############################
# The id of the broker. This must be set to a unique integer for each broker.
broker.id=0
############################# Socket Server Settings #############################
# The address the socket server listens on. It will get the value returned from
# java.net.InetAddress.getCanonicalHostName() if not configured.
# FORMAT:
# listeners = listener_name://host_name:port
# EXAMPLE:
# listeners = PLAINTEXT://your.host.name:9092
listeners=LOCALHOST://0.0.0.0:9092,SERVICE://kafka:9093
# Hostname and port the broker will advertise to producers and consumers. If not set,
# it uses the value for "listeners" if configured. Otherwise, it will use the value
# returned from java.net.InetAddress.getCanonicalHostName().
# advertised.listeners=PLAINTEXT://whale_kafka_1:9092
advertised.listeners=LOCALHOST://localhost:9092,SERVICE://kafka:9093
# ???
inter.broker.listener.name=LOCALHOST
# Maps listener names to security protocols, the default is for them to be the same. See the config documentation for more details
#listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL
listener.security.protocol.map=LOCALHOST:PLAINTEXT,SERVICE:PLAINTEXT
# The number of threads that the server uses for receiving requests from the network and sending responses to the network
num.network.threads=3
# The number of threads that the server uses for processing requests, which may include disk I/O
num.io.threads=8
# The send buffer (SO_SNDBUF) used by the socket server
socket.send.buffer.bytes=102400
# The receive buffer (SO_RCVBUF) used by the socket server
socket.receive.buffer.bytes=102400
# The maximum size of a request that the socket server will accept (protection against OOM)
socket.request.max.bytes=104857600
############################# Log Basics #############################
# A comma separated list of directories under which to store log files
log.dirs=/data/kafka
# The default number of log partitions per topic. More partitions allow greater
# parallelism for consumption, but this will also result in more files across
# the brokers.
num.partitions=1
# The number of threads per data directory to be used for log recovery at startup and flushing at shutdown.
# This value is recommended to be increased for installations with data dirs located in RAID array.
num.recovery.threads.per.data.dir=1
############################# Internal Topic Settings #############################
# The replication factor for the group metadata internal topics "__consumer_offsets" and "__transaction_state"
# For anything other than development testing, a value greater than 1 is recommended to ensure availability such as 3.
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
############################# Log Flush Policy #############################
# Messages are immediately written to the filesystem but by default we only fsync() to sync
# the OS cache lazily. The following configurations control the flush of data to disk.
# There are a few important trade-offs here:
# 1. Durability: Unflushed data may be lost if you are not using replication.
# 2. Latency: Very large flush intervals may lead to latency spikes when the flush does occur as there will be a lot of data to flush.
# 3. Throughput: The flush is generally the most expensive operation, and a small flush interval may lead to excessive seeks.
# The settings below allow one to configure the flush policy to flush data after a period of time or
# every N messages (or both). This can be done globally and overridden on a per-topic basis.
# The number of messages to accept before forcing a flush of data to disk
#log.flush.interval.messages=10000
# The maximum amount of time a message can sit in a log before we force a flush
#log.flush.interval.ms=1000
############################# Log Retention Policy #############################
# The following configurations control the disposal of log segments. The policy can
# be set to delete segments after a period of time, or after a given size has accumulated.
# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens
# from the end of the log.
# The minimum age of a log file to be eligible for deletion due to age
log.retention.hours=168
# A size-based retention policy for logs. Segments are pruned from the log unless the remaining
# segments drop below log.retention.bytes. Functions independently of log.retention.hours.
#log.retention.bytes=1073741824
# The maximum size of a log segment file. When this size is reached a new log segment will be created.
log.segment.bytes=1073741824
# The interval at which log segments are checked to see if they can be deleted according
# to the retention policies
log.retention.check.interval.ms=300000
############################# Zookeeper #############################
# Zookeeper connection string (see zookeeper docs for details).
# This is a comma separated host:port pairs, each corresponding to a zk
# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002".
# You can also append an optional chroot string to the urls to specify the
# root directory for all kafka znodes.
zookeeper.connect=zookeeper:2181
# Timeout in ms for connecting to zookeeper
zookeeper.connection.timeout.ms=18000
############################# Group Coordinator Settings #############################
# The following configuration specifies the time, in milliseconds, that the GroupCoordinator will delay the initial consumer rebalance.
# The rebalance will be further delayed by the value of group.initial.rebalance.delay.ms as new members join the group, up to a maximum of max.poll.interval.ms.
# The default value for this is 3 seconds.
# We override this to 0 here as it makes for a better out-of-the-box experience for development and testing.
# However, in production environments the default value of 3 seconds is more suitable as this will help to avoid unnecessary, and potentially expensive, rebalances during application startup.
group.initial.rebalance.delay.ms=0

View file

@ -1,40 +0,0 @@
pkgs: module:
pkgs.nixosTest {
name = "test-basic-arion-kafka";
nodes = {
machine = { ... }: {
virtualisation.memorySize = 4096;
virtualisation.diskSize = 10000;
imports = [
../../nixos-module.nix
module
];
virtualisation.arion.projects.whale.settings = {
imports = [ ./arion-compose.nix ];
};
};
};
testScript = ''
machine.wait_for_unit("sockets.target")
machine.wait_for_unit("arion-whale.service")
machine.succeed("""
(echo "hello"; echo "world") \
| ${pkgs.apacheKafka}/bin/kafka-console-producer.sh \
--topic thetopic --bootstrap-server localhost:9092
""")
machine.succeed("""
(
set +o pipefail # we only care for head's exit code
( ${pkgs.apacheKafka}/bin/kafka-console-consumer.sh \
--topic thetopic --from-beginning --bootstrap-server localhost:9092 & \
echo $! >pid
) | grep --line-buffered hello | { read; kill $(<pid); rm pid; }
) 2>/dev/console
""")
'';
}

View file

@ -1,82 +0,0 @@
# Copyright 2012 The Apache Software Foundation
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Define some default values that can be overridden by system properties
zookeeper.root.logger=INFO, CONSOLE
zookeeper.console.threshold=INFO
zookeeper.log.dir=.
zookeeper.log.file=zookeeper.log
zookeeper.log.threshold=INFO
zookeeper.log.maxfilesize=256MB
zookeeper.log.maxbackupindex=20
# zookeeper.tracelog.dir=${zookeeper.log.dir}
# zookeeper.tracelog.file=zookeeper_trace.log
log4j.rootLogger=${zookeeper.root.logger}
#
# console
# Add "console" to rootlogger above if you want to use this
#
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=${zookeeper.console.threshold}
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
# #
# # Add ROLLINGFILE to rootLogger to get log file output
# #
# log4j.appender.ROLLINGFILE=org.apache.log4j.RollingFileAppender
# log4j.appender.ROLLINGFILE.Threshold=${zookeeper.log.threshold}
# log4j.appender.ROLLINGFILE.File=${zookeeper.log.dir}/${zookeeper.log.file}
# log4j.appender.ROLLINGFILE.MaxFileSize=${zookeeper.log.maxfilesize}
# log4j.appender.ROLLINGFILE.MaxBackupIndex=${zookeeper.log.maxbackupindex}
# log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
# log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
# #
# # Add TRACEFILE to rootLogger to get log file output
# # Log TRACE level and above messages to a log file
# #
# log4j.appender.TRACEFILE=org.apache.log4j.FileAppender
# log4j.appender.TRACEFILE.Threshold=TRACE
# log4j.appender.TRACEFILE.File=${zookeeper.tracelog.dir}/${zookeeper.tracelog.file}
# log4j.appender.TRACEFILE.layout=org.apache.log4j.PatternLayout
# ### Notice we are including log4j's NDC here (%x)
# log4j.appender.TRACEFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L][%x] - %m%n
# #
# # zk audit logging
# #
# zookeeper.auditlog.file=zookeeper_audit.log
# zookeeper.auditlog.threshold=INFO
# audit.logger=INFO, CONSOLE
# log4j.logger.org.apache.zookeeper.audit.Log4jAuditLogger=${audit.logger}
# log4j.additivity.org.apache.zookeeper.audit.Log4jAuditLogger=false
# log4j.appender.RFAAUDIT=org.apache.log4j.RollingFileAppender
# log4j.appender.RFAAUDIT.File=${zookeeper.log.dir}/${zookeeper.auditlog.file}
# log4j.appender.RFAAUDIT.layout=org.apache.log4j.PatternLayout
# log4j.appender.RFAAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
# log4j.appender.RFAAUDIT.Threshold=${zookeeper.auditlog.threshold}
# # Max log file size of 10MB
# log4j.appender.RFAAUDIT.MaxFileSize=10MB
# log4j.appender.RFAAUDIT.MaxBackupIndex=10

View file

@ -1,3 +0,0 @@
tickTime=2000
dataDir=/data
clientPort=2181

9
update-options Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env nix-shell
#!nix-shell -i bash
set -eu -o pipefail
cd "$(dirname ${BASH_SOURCE[0]})"
doc_options="$(nix-build nix -A doc-options)"
cat "$doc_options" >docs/modules/ROOT/partials/NixOSOptions.adoc