Compare commits
335 commits
host-store
...
main
Author | SHA1 | Date | |
---|---|---|---|
ec4f2a836d | |||
|
90bc855327 | ||
|
16c4d4d8b8 | ||
|
236f9dd82d | ||
|
ab9bdaf08f | ||
|
555e7ba634 | ||
|
01777136c6 | ||
|
8f0549b434 | ||
|
c24c185e67 | ||
|
d917218d05 | ||
|
e9945eb6cd | ||
|
0449d31ffb | ||
|
7e7aa3dfc6 | ||
|
efa008e12f | ||
|
df306b74bc | ||
|
add0e67d2b | ||
|
c8c61a3c67 | ||
|
1886d25075 | ||
|
c2cc3dae34 | ||
|
d2d48c9ec3 | ||
|
c1597ef64b | ||
|
2b1fa9a8e9 | ||
|
4fb872dc07 | ||
|
39ee2bc7f7 | ||
|
245fec68a2 | ||
|
9e5caa2b48 | ||
|
5321799830 | ||
|
e92e133563 | ||
|
6ad33828e7 | ||
|
f295eabd25 | ||
|
a27295cbf5 | ||
|
b181b822f8 | ||
|
49bc39d860 | ||
|
91e67df844 | ||
|
da2141cd93 | ||
|
14b8d91ce0 | ||
|
2d546f6372 | ||
|
f68888200d | ||
|
39030b9566 | ||
|
493fa1e575 | ||
|
172e69d563 | ||
|
6881b440b6 | ||
|
28902d3488 | ||
|
57516c38fa | ||
|
e9ebb6f79f | ||
|
51ed7054c1 | ||
|
3588b01e13 | ||
|
638c4b8e55 | ||
|
a8d9725e6c | ||
|
f0436c8478 | ||
|
8868689d3f | ||
|
408841513b | ||
|
22ef4649d8 | ||
|
0e7dc62ccf | ||
|
9ba47f9fbb | ||
|
16f9888732 | ||
|
b175f45613 | ||
|
f8359746cc | ||
|
2ef502c912 | ||
|
daf4aebad7 | ||
|
6a1f03329c | ||
|
7e98b7af10 | ||
|
5ba2990f72 | ||
|
cb13795408 | ||
|
0f27ae484f | ||
|
399c8c0b36 | ||
|
de9930171a | ||
|
e67a5d3049 | ||
|
a38db89ef8 | ||
|
591036ae85 | ||
|
1b65892ea6 | ||
|
76a6bdbdb2 | ||
|
379724cdcd | ||
|
54079bfbc3 | ||
|
09ef2d1377 | ||
|
4ea9760991 | ||
|
f7391f3e17 | ||
|
6cbf0860ca | ||
|
d1cc2b2a7d | ||
|
3ac9c63a01 | ||
|
50bf4fe6c5 | ||
|
ce3e96e212 | ||
|
c946f1ecc2 | ||
|
0e27a7acd1 | ||
|
48b4787a5a | ||
|
0f5f229425 | ||
|
2dc6bbe049 | ||
|
1a174e2eaa | ||
|
cabcbcacca | ||
|
d0b8e02c28 | ||
|
6851553d2b | ||
|
295e8698bd | ||
|
261d1507d6 | ||
|
cfec8ff678 | ||
|
683a79dfcc | ||
|
92e17b7fe0 | ||
|
935c32afa3 | ||
|
b9525cef7c | ||
|
7987c7ec0d | ||
|
890f9d9428 | ||
|
4aff7e3a11 | ||
|
8159c4faa3 | ||
|
06266c155c | ||
|
e5fb978143 | ||
|
9b6418810d | ||
|
8d9824c622 | ||
|
5b0126a55d | ||
|
c519be7211 | ||
|
52c8798dbf | ||
|
6a0846c41b | ||
|
037c87837f | ||
|
ca785e548b | ||
|
c8944a2871 | ||
|
42146bb0b2 | ||
|
fa48a5241e | ||
|
c24a87f429 | ||
|
f4cd854c8e | ||
|
84d77a2002 | ||
|
6d2e07173c | ||
|
d90c7dc326 | ||
|
5ffaa4104a | ||
|
bd3e2fe4e3 | ||
|
fb5ab7b76f | ||
|
69b9109dea | ||
|
cf20442a7a | ||
|
ec8ef96d52 | ||
|
cd962c840e | ||
|
0ee76740d0 | ||
|
72293ef5f6 | ||
|
3dba169791 | ||
|
40fd74e71f | ||
|
fbb56568e9 | ||
|
83bc14fba8 | ||
|
2c10b297ad | ||
|
161ee3aaf9 | ||
|
cb0d5ed7fd | ||
|
ad9e564308 | ||
|
baa515d88e | ||
|
72efe2145d | ||
|
56f5c5d5a0 | ||
|
db6d4d7490 | ||
|
a7c545074b | ||
|
e263614045 | ||
|
ee331fa1cd | ||
|
600aa2c8ac | ||
|
ff2b9bbc44 | ||
|
ac20241a41 | ||
|
ab13e0a3f4 | ||
|
f5a3f299c6 | ||
|
3374cbec26 | ||
|
65b2106d2a | ||
|
af8257eb66 | ||
|
a1a2475be5 | ||
|
b30c76822d | ||
|
01f359b8f6 | ||
|
f94dc40f05 | ||
|
cedf8be896 | ||
|
4a38050a05 | ||
|
3171cf1c21 | ||
|
40394f4822 | ||
|
b045fba6f1 | ||
|
865055787a | ||
|
a7c7ec3a03 | ||
|
07998216c4 | ||
|
2b46a9b5f6 | ||
|
e73710caf9 | ||
|
dcc5b1e3ce | ||
|
9dd1ab0568 | ||
|
144864d61c | ||
|
a2f5c9415c | ||
|
1a24fe9639 | ||
|
286d56a83c | ||
|
e0e7531f7d | ||
|
48d3d4b0d7 | ||
|
ad41d1e39b | ||
|
b83cf51efd | ||
|
e1f7840780 | ||
|
3fb8782296 | ||
|
525b598ce3 | ||
|
39249c5956 | ||
|
8cb231fa89 | ||
|
7340d37636 | ||
|
29599f529b | ||
|
5ed7b893bd | ||
|
1da9c00cd5 | ||
|
127a5babaa | ||
|
e44c2c95ac | ||
|
aed2c40e77 | ||
|
0417272e0e | ||
|
44bd4b34c6 | ||
|
c605eac50f | ||
|
bb23a55c8a | ||
|
3d9f19b630 | ||
|
648230492d | ||
|
3bad85064b | ||
|
cfa65c56a6 | ||
|
32b00781b4 | ||
|
ee2f71327d | ||
|
9a523b45d7 | ||
|
35cb7adfb5 | ||
|
38048ada2c | ||
|
86e420d15c | ||
|
7b27176274 | ||
|
3cbb40281c | ||
|
ed42a5c708 | ||
|
bddf4d919d | ||
|
a96df5d7c1 | ||
|
896316ce29 | ||
|
700297748d | ||
|
b959ab492d | ||
|
92c389fab5 | ||
|
2de4188b9d | ||
|
df0ec2eb50 | ||
|
427a3b0e3c | ||
|
067ce26177 | ||
|
88c361c81c | ||
|
fa06bc80dc | ||
|
b4c17aac7c | ||
|
7c20fa9a11 | ||
|
9dabd9bb92 | ||
|
f7d2f2d93c | ||
|
5df15b33a7 | ||
|
3e3c1754a5 | ||
|
ed2bc14032 | ||
|
97df92183d | ||
|
fd41e1e7de | ||
|
b2e2aad1d5 | ||
|
90c2637947 | ||
|
7609d3a88d | ||
|
4afa8694f6 | ||
|
3b86679399 | ||
|
eeed0577be | ||
|
313bf21228 | ||
|
7d6ddb960f | ||
|
5670a09cac | ||
|
d92eb8ada5 | ||
|
5c318409cc | ||
|
265f6a29ce | ||
|
fc2ec12ead | ||
|
52dfbeccb1 | ||
|
5bd7ea2aa3 | ||
|
41d3fb490c | ||
|
1778d76117 | ||
|
77b89cb424 | ||
|
0a8f8e7fb8 | ||
|
24503bcbe5 | ||
|
697b3a27bb | ||
|
8f2b953701 | ||
|
c38c374fa6 | ||
|
dda66e104e | ||
|
4cc75b7cc5 | ||
|
3580fef52e | ||
|
1ed2161bcd | ||
|
4a12286e92 | ||
|
da83692996 | ||
|
60f3f34527 | ||
|
2856bbbe7f | ||
|
09f324bfe0 | ||
|
5140cf0a09 | ||
|
4444cf1856 | ||
|
9fedd0e152 | ||
|
cc07068beb | ||
|
e21b1b4e75 | ||
|
0ab8ad6bdd | ||
|
7749eb2ef9 | ||
|
41d4fefd64 | ||
|
80a4dbe8b9 | ||
|
6fb0b1ec66 | ||
|
535feae97a | ||
|
c8f7f5a6d3 | ||
|
c3a5f8c13f | ||
|
6abcc26a76 | ||
|
5f56a0846a | ||
|
d0815c4393 | ||
|
83a9d4668f | ||
|
8d3e68c167 | ||
|
c88d2bb9cf | ||
|
02d319acf6 | ||
|
a90190fc9e | ||
|
286d0ae084 | ||
|
adc2e34deb | ||
|
3918799b9a | ||
|
0474544d0b | ||
|
02c0f80b02 | ||
|
77c492fa86 | ||
|
1fe10c076d | ||
|
fcf270c80c | ||
|
b9488b7f49 | ||
|
c0e995043a | ||
|
44df36673c | ||
|
81887ba633 | ||
|
9d7eb01c73 | ||
|
81a51ae5ea | ||
|
0f0f976eab | ||
|
520c80d927 | ||
|
cc9e70a2cc | ||
|
89e5f4a90f | ||
|
0f85d7b03c | ||
|
13a702968d | ||
|
2d079e4f41 | ||
|
466d8e3af3 | ||
|
c46404cb86 | ||
|
695785f5e7 | ||
|
52a0fe5d95 | ||
|
dfe1b63c4b | ||
|
a356daaa86 | ||
|
6d6361e7e8 | ||
|
381c3ec37f | ||
|
662042a2bf | ||
|
c5fb4177b8 | ||
|
c2bdf8e047 | ||
|
36e48776cf | ||
|
6882a92e56 | ||
|
77eadf4c41 | ||
|
93b34dec1a | ||
|
60cb5cb5c3 | ||
|
9b047987ae | ||
|
01f73e486b | ||
|
61cc348281 | ||
|
ba6fa62c4a | ||
|
9443fe8410 | ||
|
221bccd7f1 | ||
|
aee39bbb15 | ||
|
47081ccd41 | ||
|
a95066a844 | ||
|
f3bf1ba181 | ||
|
6c7170c365 | ||
|
c94542bf78 | ||
|
e800372155 | ||
|
871db29671 | ||
|
9f896017ee | ||
|
3964ac2f1e | ||
|
14d6fb5d96 | ||
|
47ca9c485d | ||
|
f4948f2fde |
123 changed files with 3798 additions and 1441 deletions
7
.envrc
Normal file
7
.envrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
HOST_XDG_DATA_DIRS="${XDG_DATA_DIRS:-}"
|
||||||
|
eval "$(lorri direnv)"
|
||||||
|
export XDG_DATA_DIRS="${XDG_DATA_DIRS}:${HOST_XDG_DATA_DIRS}"
|
||||||
|
|
||||||
|
# Use system PKI
|
||||||
|
unset SSL_CERT_FILE
|
||||||
|
unset NIX_SSL_CERT_FILE
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,2 +1,9 @@
|
||||||
result
|
result
|
||||||
result-*
|
result-*
|
||||||
|
|
||||||
|
dist/
|
||||||
|
dist-newstyle/
|
||||||
|
cabal.project.local
|
||||||
|
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
|
100
CHANGELOG.md
Normal file
100
CHANGELOG.md
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
* `useHostStore` now uses an image derived from the `image.*` options. You may
|
||||||
|
need to enable `enableRecommendedContents` because with this change, files
|
||||||
|
like `/bin/sh` aren't added by default anymore.
|
||||||
|
|
||||||
|
* Drop obsolete NixOS 19.03, 19.09 and 20.03 from CI.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
* NixOS-based containers can now run on Podman when it is configured to provide a docker socket. See the [installation docs](https://docs.hercules-ci.com/arion/#_nixos).
|
||||||
|
|
||||||
|
* Support `service.dns`, for overriding the DNS servers used by containers.
|
||||||
|
|
||||||
|
* Support `service.labels`, which is useful for autodiscovery among other things.
|
||||||
|
|
||||||
|
* Add a tested example for Traefik with label-based routing.
|
||||||
|
|
||||||
|
* Add a `flake.nix` and an experimental flake example
|
||||||
|
|
||||||
|
* Add a warning when systemd `DynamicUser` is used but not available to the
|
||||||
|
container.
|
||||||
|
|
||||||
|
* CI with NixOS 21.05
|
||||||
|
|
||||||
|
## 0.1.2.0 -- 2020-03-05
|
||||||
|
|
||||||
|
* Support use of prebuilt `docker-compose.yaml`.
|
||||||
|
Separates build and execution without duplicating evaluation.
|
||||||
|
|
||||||
|
* Avoid storing tarballs (wasting store space) by using
|
||||||
|
`dockerTools.streamLayeredImage` if available.
|
||||||
|
|
||||||
|
* Project name is now configurable via the `project.name` option
|
||||||
|
|
||||||
|
* Support --no-ansi, --compatibility, --log-level options
|
||||||
|
|
||||||
|
## 0.1.1.1 -- 2020-03-20
|
||||||
|
|
||||||
|
* Fix ambiguous import of `lines`
|
||||||
|
* Improve base version constraint
|
||||||
|
* Fix warnings
|
||||||
|
|
||||||
|
## 0.1.1.0 -- 2020-03-19
|
||||||
|
|
||||||
|
* Support Nixpkgs 20.03
|
||||||
|
* Fixes for macOS
|
||||||
|
|
||||||
|
## 0.1.0.0 -- 2019-10-04
|
||||||
|
|
||||||
|
* First released version. Released on an unsuspecting world.
|
||||||
|
|
31
HACKING.md
Normal file
31
HACKING.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
# Hacking on the modules
|
||||||
|
|
||||||
|
## Easiest option
|
||||||
|
|
||||||
|
The module system does not distinguish between modules and configurations.
|
||||||
|
This mean you can prototype any feature by factoring out functionality in a real-world project.
|
||||||
|
|
||||||
|
## Changing the built-in modules
|
||||||
|
|
||||||
|
If your change is not just an addition or if it's better implemented by refactoring, you'll want to fork and edit arion sources directly.
|
||||||
|
|
||||||
|
For a fast iteration cycle (but possibly outdated arion command logic):
|
||||||
|
|
||||||
|
~/src/arion/run-arion-quick up -d
|
||||||
|
|
||||||
|
To update the arion command logic on the next run
|
||||||
|
|
||||||
|
rm ~/src/arion/result-run-arion-quick
|
||||||
|
|
||||||
|
|
||||||
|
# Hacking on the arion command
|
||||||
|
|
||||||
|
The arion command is written in Haskell. Anyone can make small changes to the code.
|
||||||
|
Experience with Haskell tooling is not required. You can use the nixified scripts in the root of the repo for common tasks.
|
||||||
|
- `build` or `live-check` for typechecking
|
||||||
|
- `live-unit-tests` (only the test suite is "live" though)
|
||||||
|
- `repl` for a Haskell REPL
|
||||||
|
- `run-arion` to run an incrementally built arion
|
||||||
|
- `run-arion-via-nix` to run a nix-built arion
|
||||||
|
- ~~`run-arion-quick`~~ *not for command hacking;* use stale command. See previous section.
|
2
LICENSE
2
LICENSE
|
@ -187,7 +187,7 @@
|
||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
Copyright 2019 Hercules Labs OÜ
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
7
README.asciidoc
Normal file
7
README.asciidoc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
# https://docs.hercules-ci.com/arion/[Intro and Documentation]
|
135
README.md
135
README.md
|
@ -1,135 +0,0 @@
|
||||||
|
|
||||||
# Run docker-compose without images with Nix
|
|
||||||
|
|
||||||
*Wait, what?*
|
|
||||||
|
|
||||||
With Arion you can fire up containers without creating images for each
|
|
||||||
service. It can use a mostly empty image, and makes the host's Nix
|
|
||||||
store available in the container, allowing the container to run
|
|
||||||
programs without having to re-package them into a docker image.
|
|
||||||
|
|
||||||
Arion is configured using Nix with modules, like those in
|
|
||||||
NixOS. Similar to `docker-compose` it can therefore combine
|
|
||||||
configuration from multiple files. For managing the network and
|
|
||||||
containers it delegates to the `docker-compose` command.
|
|
||||||
|
|
||||||
# Project Status
|
|
||||||
|
|
||||||
This project was born out of a process supervision need for local
|
|
||||||
development environments while working
|
|
||||||
on [Hercules CI](https://www.hercules-ci.com). (It was also born out
|
|
||||||
of ancient Greek deities disguised as horses. More on that later.)
|
|
||||||
|
|
||||||
If you do want to use Arion for production environments, you'll
|
|
||||||
probably want to either build normal container images or manage
|
|
||||||
garbage collection roots if you control the deployment host. Neither
|
|
||||||
scenario is made easier by arion at this time.
|
|
||||||
|
|
||||||
Support for other Linux than NixOS is untested.
|
|
||||||
|
|
||||||
# Install
|
|
||||||
|
|
||||||
Have [Nix](https://nixos.org/nix/) and Docker installed. Ubuntu users:
|
|
||||||
make sure you've installed via `apt-get install docker.io`. The Docker snap
|
|
||||||
is broken and apparently unsupported by Canonical.
|
|
||||||
|
|
||||||
git clone git@github.com:hercules-ci/arion.git
|
|
||||||
cd arion
|
|
||||||
nix-env -iA arion -f .
|
|
||||||
|
|
||||||
# Example `arion-compose.nix`
|
|
||||||
|
|
||||||
This Nix expression serves the Nix manual at host port 8000 when launched with `arion up`. It is a function from a package set (`pkgs`) to a configuration.
|
|
||||||
|
|
||||||
```nix
|
|
||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
config.docker-compose.services = {
|
|
||||||
|
|
||||||
webserver = {
|
|
||||||
service.useHostStore = true;
|
|
||||||
# service.depends_on = [ "backend" ];
|
|
||||||
service.command = [ "sh" "-c" ''
|
|
||||||
cd "$$WEB_ROOT"
|
|
||||||
${pkgs.python3}/bin/python -m http.server
|
|
||||||
'' ];
|
|
||||||
service.ports = [
|
|
||||||
"8000:8000" # host:container
|
|
||||||
];
|
|
||||||
service.environment.WEB_ROOT = "${pkgs.nix.doc}/share/doc/nix/manual";
|
|
||||||
};
|
|
||||||
|
|
||||||
# backend = { ... }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `pkgs` argument comes from a file called `arion-pkgs.nix`. It can be as simple as `import <nixpkgs> {}` to use the Nixpkgs from your `$NIX_PATH`, or you can use it to pin a specific Nixpkgs version.
|
|
||||||
|
|
||||||
# A full featured example
|
|
||||||
|
|
||||||
To see how Arion can be used in a project, have a look at [todomvc-nix](https://github.com/nix-community/todomvc-nix/tree/master/deploy/arion).
|
|
||||||
|
|
||||||
git clone git@github.com:nix-community/todomvc-nix.git
|
|
||||||
cd todomvc-nix/deploy/arion
|
|
||||||
arion up
|
|
||||||
|
|
||||||
# How it works
|
|
||||||
|
|
||||||
Arion is essentially a thin wrapper around Nix and docker-compose.
|
|
||||||
When it runs, it does the following:
|
|
||||||
|
|
||||||
- Evaluate the configuration using Nix, producing a `docker-compose.yaml` and a garbage collection root
|
|
||||||
- Invoke `docker-compose`
|
|
||||||
- Clean up the garbage collection root
|
|
||||||
|
|
||||||
Most of the interesting stuff happens in Arion's Nix expressions,
|
|
||||||
where it runs the module system (known from NixOS) and provides the configuration that makes the Docker Compose file do the things it needs to do.
|
|
||||||
|
|
||||||
One of the more interesting built-in modules is the [host-store.nix module](src/nix/modules/service/host-store.nix) which performs the bind mounts to make the host Nix store available in the container.
|
|
||||||
|
|
||||||
# FAQ
|
|
||||||
|
|
||||||
### Do I need to use Hercules CI?
|
|
||||||
|
|
||||||
Nope, it's just Nix and Docker Compose under the hood.
|
|
||||||
|
|
||||||
|
|
||||||
### Does Arion support Docker images?
|
|
||||||
|
|
||||||
Yes, you can also specify a normal Docker image. For example:
|
|
||||||
|
|
||||||
postgres = {
|
|
||||||
service.image = "postgres:10";
|
|
||||||
service.volumes = [ "${toString ./.}/postgres-data:/var/lib/postgresql/data" ];
|
|
||||||
service.environment.POSTGRES_PASSWORD = "mydefaultpass";
|
|
||||||
};
|
|
||||||
|
|
||||||
### What about garbage collection?
|
|
||||||
|
|
||||||
Arion removes the need for garbage collecting docker images,
|
|
||||||
delegating this task to Nix.
|
|
||||||
|
|
||||||
Arion creates a garbage collection root and cleans it up after
|
|
||||||
completing the command. This means that `arion up` without `-d` is
|
|
||||||
safe with respect to garbage collection. A deployment that is more
|
|
||||||
serious than local development must leave a GC root on the deployment
|
|
||||||
host. This use case is not supported as of now.
|
|
||||||
|
|
||||||
### What is messing with my environment variables?
|
|
||||||
|
|
||||||
Docker Compose performs its own environment variable
|
|
||||||
substitution. This can be a little annoying in `services.command` for
|
|
||||||
example. Either reference a script from `pkgs.writeScript` or escape
|
|
||||||
the dollar sign as `$$`.
|
|
||||||
|
|
||||||
### Why "Arion"?
|
|
||||||
|
|
||||||
Arion comes from Greek mythology. Poseidon, the god of ~Docker~ the
|
|
||||||
seas had his eye on Demeter. Demeter tried to trick him by disguising
|
|
||||||
as a horse, but Poseidon saw through the deception and they had Arion.
|
|
||||||
|
|
||||||
So Arion is a super fast divine horse; the result of some weird
|
|
||||||
mixing. Also it talks.
|
|
||||||
|
|
||||||
(And we feel morally obliged to name our stuff after Greek mythology)
|
|
2
Setup.hs
Normal file
2
Setup.hs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import Distribution.Simple
|
||||||
|
main = defaultMain
|
13
antora-playbook.yml
Normal file
13
antora-playbook.yml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# This is not used when the docs are imported into the docs site.
|
||||||
|
# It is only here for development preview.
|
||||||
|
content:
|
||||||
|
sources:
|
||||||
|
- url: .
|
||||||
|
start_path: docs
|
||||||
|
branches: HEAD
|
||||||
|
ui:
|
||||||
|
bundle:
|
||||||
|
url: ./docs/ui-bundle.zip
|
||||||
|
output:
|
||||||
|
dir: ./public
|
||||||
|
|
90
arion-compose.cabal
Normal file
90
arion-compose.cabal
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
cabal-version: 2.4
|
||||||
|
|
||||||
|
name: arion-compose
|
||||||
|
version: 0.2.1.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
|
||||||
|
-- bug-reports:
|
||||||
|
license: Apache-2.0
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Robert Hensing
|
||||||
|
maintainer: robert@hercules-ci.com
|
||||||
|
-- copyright:
|
||||||
|
category: Distribution, Network, Cloud, Distributed Computing
|
||||||
|
extra-source-files: CHANGELOG.md, README.asciidoc,
|
||||||
|
src/haskell/testdata/**/*.nix
|
||||||
|
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
|
||||||
|
|
||||||
|
-- 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
|
||||||
|
, aeson-pretty
|
||||||
|
, async
|
||||||
|
, bytestring
|
||||||
|
, directory
|
||||||
|
, lens
|
||||||
|
, lens-aeson
|
||||||
|
, process
|
||||||
|
, temporary
|
||||||
|
, text
|
||||||
|
, protolude >= 0.2
|
||||||
|
, unix
|
||||||
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
flag ghci
|
||||||
|
default: False
|
||||||
|
manual: True
|
||||||
|
|
||||||
|
library
|
||||||
|
import: common
|
||||||
|
exposed-modules: Arion.Nix
|
||||||
|
Arion.Aeson
|
||||||
|
Arion.DockerCompose
|
||||||
|
Arion.ExtendedInfo
|
||||||
|
Arion.Images
|
||||||
|
Arion.Services
|
||||||
|
other-modules: Paths_arion_compose
|
||||||
|
autogen-modules: Paths_arion_compose
|
||||||
|
-- other-extensions:
|
||||||
|
hs-source-dirs: src/haskell/lib
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
executable arion
|
||||||
|
import: common
|
||||||
|
main-is: Main.hs
|
||||||
|
-- other-modules:
|
||||||
|
-- other-extensions:
|
||||||
|
build-depends: optparse-applicative
|
||||||
|
, arion-compose
|
||||||
|
hs-source-dirs: src/haskell/exe
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
test-suite arion-unit-tests
|
||||||
|
import: common
|
||||||
|
if flag(ghci)
|
||||||
|
hs-source-dirs: src/haskell/lib
|
||||||
|
ghc-options: -Wno-missing-home-modules
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
main-is: TestMain.hs
|
||||||
|
other-modules: Spec
|
||||||
|
, Arion.NixSpec
|
||||||
|
-- other-extensions:
|
||||||
|
build-depends: arion-compose
|
||||||
|
, hspec
|
||||||
|
, QuickCheck
|
||||||
|
hs-source-dirs: src/haskell/test
|
||||||
|
default-language: Haskell2010
|
38
arion.nix
38
arion.nix
|
@ -1,38 +0,0 @@
|
||||||
{ stdenv, lib
|
|
||||||
, coreutils, docker_compose, jq
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
|
|
||||||
arion = stdenv.mkDerivation {
|
|
||||||
name = "arion";
|
|
||||||
src = ./src;
|
|
||||||
unpackPhase = "";
|
|
||||||
buildPhase = "";
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/bin $out/share/arion
|
|
||||||
cp -a nix $out/share/arion/
|
|
||||||
cp -a arion-image $out/share/arion/
|
|
||||||
tar -czf $out/share/arion/arion-image/tarball.tar.gz -C arion-image/tarball .
|
|
||||||
substitute arion $out/bin/arion \
|
|
||||||
--subst-var-by path ${lib.makeBinPath [jq coreutils docker_compose]} \
|
|
||||||
--subst-var-by nix_dir $out/share/arion/nix \
|
|
||||||
;
|
|
||||||
chmod a+x $out/bin/arion
|
|
||||||
'';
|
|
||||||
inherit passthru;
|
|
||||||
};
|
|
||||||
|
|
||||||
passthru = {
|
|
||||||
inherit eval build;
|
|
||||||
};
|
|
||||||
|
|
||||||
eval = import "${nix_dir}/eval-composition.nix";
|
|
||||||
|
|
||||||
build = args@{...}:
|
|
||||||
let composition = eval args;
|
|
||||||
in composition.config.build.dockerComposeYaml;
|
|
||||||
|
|
||||||
nix_dir = "${arion.outPath}/share/arion/nix";
|
|
||||||
|
|
||||||
in
|
|
||||||
arion
|
|
5
bors.toml
Normal file
5
bors.toml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
status = [
|
||||||
|
"ci/hercules/onPush/default",
|
||||||
|
"ci/hercules/evaluation",
|
||||||
|
]
|
||||||
|
delete_merged_branches = true
|
6
build
Executable file
6
build
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell -i bash
|
||||||
|
|
||||||
|
# Build the Haskell package via cabal, outside Nix
|
||||||
|
|
||||||
|
cabal new-build --write-ghc-environment-files=never
|
1
cabal.project
Normal file
1
cabal.project
Normal file
|
@ -0,0 +1 @@
|
||||||
|
packages: .
|
13
default.nix
13
default.nix
|
@ -1,6 +1,11 @@
|
||||||
args@{ pkgs ? import ./nix args, ... }:
|
let flake = import ./nix/compat.nix;
|
||||||
|
in
|
||||||
|
{ pkgs ? import flake.inputs.nixpkgs { }
|
||||||
|
, haskellPackages ? pkgs.haskellPackages
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
pkgsWithArion = pkgs.extend flake.overlays.default;
|
||||||
|
in
|
||||||
{
|
{
|
||||||
inherit (pkgs) arion tests;
|
inherit (pkgsWithArion) arion;
|
||||||
doc = pkgs.recurseIntoAttrs (import ./doc { inherit pkgs; });
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
{ pkgs ? import ../nix {} }:
|
|
||||||
let
|
|
||||||
inherit (pkgs) recurseIntoAttrs callPackage;
|
|
||||||
in
|
|
||||||
|
|
||||||
recurseIntoAttrs {
|
|
||||||
manual = callPackage ./manual {};
|
|
||||||
}
|
|
5
doc/manual/.gitignore
vendored
5
doc/manual/.gitignore
vendored
|
@ -1,5 +0,0 @@
|
||||||
introduction.xml
|
|
||||||
installation.xml
|
|
||||||
manual.html
|
|
||||||
options-composition.xml
|
|
||||||
options-service.xml
|
|
|
@ -1,35 +0,0 @@
|
||||||
xsltproc = xsltproc --nonet \
|
|
||||||
--param section.autolabel 0 \
|
|
||||||
--param section.label.includes.component.label 0 \
|
|
||||||
--param chapter.autolabel 0 \
|
|
||||||
--param chapter.label.includes.component.label 0 \
|
|
||||||
--param appendix.autolabel 0 \
|
|
||||||
--param appendix.label.includes.component.label 0 \
|
|
||||||
--param generate.toc "'book toc,title chapter nop section nop sect1 nop sect2 nop sect3 nop sect4 nop sect5 nop'" \
|
|
||||||
--param html.stylesheet \'style.css\' \
|
|
||||||
--param xref.with.number.and.title 0 \
|
|
||||||
--param toc.section.depth 3 \
|
|
||||||
--param admon.style \'\' \
|
|
||||||
--param callout.graphics.extension \'.gif\' \
|
|
||||||
--param contrib.inline.enabled 0
|
|
||||||
|
|
||||||
docbookxsl = http://docbook.sourceforge.net/release/xsl/current
|
|
||||||
|
|
||||||
all: manual.html
|
|
||||||
|
|
||||||
manual.html: manual.xml introduction.xml installation.xml options-composition.xml options-service.xml
|
|
||||||
$(xsltproc) --xinclude --stringparam profile.condition manual \
|
|
||||||
$(docbookxsl)/profiling/profile.xsl manual.xml | \
|
|
||||||
$(xsltproc) --output manual.html $(docbookxsl)/xhtml/docbook.xsl -
|
|
||||||
|
|
||||||
# -e 's_<book lang="en">__' -e 's_</book>__'
|
|
||||||
%.xml: %.asciidoc
|
|
||||||
asciidoctor --backend docbook45 --doctype article $<
|
|
||||||
sed -e 's/<!DOCTYPE.*//' -e 's/<?asciidoc-[a-z]*?>//' -i $@
|
|
||||||
|
|
||||||
options-composition.xml options-service.xml:
|
|
||||||
echo "options-composition.xml and options-service.xml should be written by the derivation. Are you running in 'nix-shell -A manual'?"; exit 1; fi
|
|
||||||
|
|
||||||
install: all
|
|
||||||
mkdir -p $(docdir)
|
|
||||||
cp manual.html style.css $(docdir)
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
nix-shell -A manual --run 'patchPhase && make'
|
|
|
@ -1,132 +0,0 @@
|
||||||
{ pkgs ? import ../../nix {}
|
|
||||||
, version ? "none"
|
|
||||||
# Default sourceUrl is for local development. Works with
|
|
||||||
# nix-build -A doc.manual
|
|
||||||
# For release, use: https://github.com/hercules-ci/arion/blob/${version}
|
|
||||||
, sourceUrl ? "../../.."
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
inherit (pkgs) recurseIntoAttrs callPackage runCommand lib stdenv ;
|
|
||||||
|
|
||||||
nixosManualPath = s: "${pkgs.path}/nixos/doc/manual/${s}";
|
|
||||||
|
|
||||||
# NixOS module system options in JSON format.
|
|
||||||
options = { moduleType, description, /*optionsList*/ optionsExpr }: recurseIntoAttrs rec {
|
|
||||||
optionsXML =
|
|
||||||
# builtins.toFile "options.xml" (builtins.toXML optionsList);
|
|
||||||
pkgs.runCommand "options.xml" {
|
|
||||||
buildInputs = [pkgs.nix pkgs.jq];
|
|
||||||
inherit optionsExpr;
|
|
||||||
} ''
|
|
||||||
export NIX_LOG_DIR=$PWD
|
|
||||||
export NIX_STATE_DIR=$PWD
|
|
||||||
nix-instantiate \
|
|
||||||
--option sandbox false \
|
|
||||||
--readonly-mode \
|
|
||||||
--eval \
|
|
||||||
--expr "$optionsExpr" \
|
|
||||||
--xml \
|
|
||||||
--strict \
|
|
||||||
--show-trace \
|
|
||||||
>$out
|
|
||||||
'';
|
|
||||||
|
|
||||||
optionsDocBook = runCommand "options-db.xml" {} ''
|
|
||||||
optionsXML=${optionsXML}
|
|
||||||
${pkgs.buildPackages.libxslt.bin}/bin/xsltproc \
|
|
||||||
--stringparam revision '${version}' \
|
|
||||||
--stringparam sourceUrl '${sourceUrl}' \
|
|
||||||
-o intermediate.xml ${./options-to-docbook.xsl} $optionsXML
|
|
||||||
${pkgs.buildPackages.libxslt.bin}/bin/xsltproc \
|
|
||||||
-o "$out" ${nixosManualPath "postprocess-option-descriptions.xsl"} intermediate.xml
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
compositionOptions = options {
|
|
||||||
moduleType = "composition";
|
|
||||||
description = "List of Arion composition-level options in JSON format";
|
|
||||||
optionsExpr = let
|
|
||||||
src = ../../src/nix;
|
|
||||||
in ''
|
|
||||||
let pkgs = import ${pkgs.path} {};
|
|
||||||
fixPaths = opt: opt // {
|
|
||||||
declarations = map (d: "src/nix" + (lib.strings.removePrefix (toString ${src}) (toString d))) opt.declarations;
|
|
||||||
};
|
|
||||||
inherit (pkgs) lib;
|
|
||||||
composition = import ${src}/eval-composition.nix { inherit pkgs; };
|
|
||||||
in map fixPaths (lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList composition.options))
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
serviceOptions = options {
|
|
||||||
moduleType = "service";
|
|
||||||
description = "List of Arion service-level options in JSON format";
|
|
||||||
optionsExpr = let
|
|
||||||
src = ../../src/nix;
|
|
||||||
in ''
|
|
||||||
let pkgs = import ${pkgs.path} {};
|
|
||||||
fixPaths = opt: opt // {
|
|
||||||
declarations = map (d: "src/nix" + (lib.strings.removePrefix (toString ${src}) (toString d))) opt.declarations;
|
|
||||||
};
|
|
||||||
inherit (pkgs) lib;
|
|
||||||
composition = pkgs.callPackage ${src}/eval-service.nix {} { modules = []; host = {}; name = abort "The manual's service options section must not depend on the service name."; };
|
|
||||||
in map fixPaths (lib.filter (opt: opt.visible && !opt.internal) (lib.optionAttrSetToDocList composition.options))
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
generatedDocBook = runCommand "generated-docbook" {} ''
|
|
||||||
mkdir $out
|
|
||||||
ln -s ${compositionOptions.optionsDocBook} $out/options-composition.xml
|
|
||||||
ln -s ${serviceOptions.optionsDocBook} $out/options-service.xml
|
|
||||||
'';
|
|
||||||
|
|
||||||
manual = stdenv.mkDerivation {
|
|
||||||
src = lib.sourceByRegex ./. [
|
|
||||||
"Makefile$"
|
|
||||||
".*\.asciidoc$"
|
|
||||||
".*\.xsl$"
|
|
||||||
".*\.css$"
|
|
||||||
"^manual.xml$"
|
|
||||||
"^manual.xml$"
|
|
||||||
];
|
|
||||||
name = "arion-manual";
|
|
||||||
version = version;
|
|
||||||
buildInputs = [
|
|
||||||
(pkgs.libxslt.bin or pkgs.libxslt)
|
|
||||||
pkgs.asciidoctor
|
|
||||||
];
|
|
||||||
XML_CATALOG_FILES = "${pkgs.docbook_xsl}/xml/xsl/docbook/catalog.xml";
|
|
||||||
inherit generatedDocBook;
|
|
||||||
configurePhase = ''
|
|
||||||
export docdir=$out/doc
|
|
||||||
'';
|
|
||||||
postPatch = ''
|
|
||||||
substituteInPlace manual.xml --subst-var version
|
|
||||||
'';
|
|
||||||
prePatch = ''
|
|
||||||
cp ${generatedDocBook}/* .
|
|
||||||
substituteInPlace options-service.xml \
|
|
||||||
--replace 'xml:id="appendix-configuration-options"' 'xml:id="appendix-service-options"' \
|
|
||||||
--replace '<title>Configuration Options</title>' '<title>Service Options</title>' \
|
|
||||||
--replace 'xml:id="configuration-variable-list"' 'xml:id="service-variable-list"' \
|
|
||||||
;
|
|
||||||
substituteInPlace options-composition.xml \
|
|
||||||
--replace 'xml:id="appendix-configuration-options"' 'xml:id="appendix-composition-options"' \
|
|
||||||
--replace '<title>Configuration Options</title>' '<title>Composition Options</title>' \
|
|
||||||
--replace 'xml:id="configuration-variable-list"' 'xml:id="composition-variable-list"' \
|
|
||||||
;
|
|
||||||
'';
|
|
||||||
shellHook = ''
|
|
||||||
live-build() {
|
|
||||||
patchPhase
|
|
||||||
inotifywait -e MODIFY -m -r . | while read; do
|
|
||||||
make
|
|
||||||
done
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
passthru = {
|
|
||||||
inherit generatedDocBook;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
manual
|
|
|
@ -1,39 +0,0 @@
|
||||||
|
|
||||||
= Installation
|
|
||||||
|
|
||||||
== Imperative installation, bleeding edge
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone git@github.com:hercules-ci/arion.git
|
|
||||||
cd arion
|
|
||||||
nix-env -iA arion -f .
|
|
||||||
```
|
|
||||||
|
|
||||||
// TODO: replace the above with something like below
|
|
||||||
////
|
|
||||||
|
|
||||||
== Not installing: use it in a project
|
|
||||||
|
|
||||||
TODO: describe: using nix-shell or in a script, building images as
|
|
||||||
part of nix-build, pinning, see also todomvc-nix.
|
|
||||||
|
|
||||||
TODO: exposed Nix functions: arion.build, arion.eval (a bit of IFD)
|
|
||||||
|
|
||||||
== Mac or traditional Linux
|
|
||||||
|
|
||||||
```
|
|
||||||
nix-env -iA nixpkgs.arion
|
|
||||||
```
|
|
||||||
|
|
||||||
== NixOS
|
|
||||||
|
|
||||||
Add this module to your NixOS configuration:
|
|
||||||
|
|
||||||
```
|
|
||||||
{ pkgs, ... } {
|
|
||||||
virtualisation.docker.enable = true;
|
|
||||||
environment.systemPackages = [ pkgs.arion ];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
////
|
|
|
@ -1,33 +0,0 @@
|
||||||
|
|
||||||
= Introduction
|
|
||||||
|
|
||||||
Arion is a tool for building and running applications that
|
|
||||||
consist of multiple docker containers. It has special support
|
|
||||||
for docker images that are built with Nix, for a smooth
|
|
||||||
development experience and improved performance.
|
|
||||||
|
|
||||||
It is built on top of https://docs.docker.com/compose/overview/[Docker
|
|
||||||
Compose], which implements the container orchestration functionality.
|
|
||||||
|
|
||||||
Instead of configuring the compositions in YAML files like
|
|
||||||
`docker-compose.yaml`, Arion uses the https://nixos.org/nix/[Nix]
|
|
||||||
language to declare the compositions. Because of this, Arion gives you
|
|
||||||
the ability to declare your deployments, configuration and packaging
|
|
||||||
in the same language. By replacing multiple tools with a single
|
|
||||||
language, you decrease your mental load and you can more easily
|
|
||||||
refactor and maintain your configurations.
|
|
||||||
|
|
||||||
Although Arion can be used as a Docker Compose with an improved
|
|
||||||
configuration front end, there is more to be gained from integrating
|
|
||||||
with Nix. In particular, the more structured approach of Nix compared
|
|
||||||
to Dockerfiles allows the following:
|
|
||||||
|
|
||||||
* Build components of your image in _parallel, automatically_.
|
|
||||||
* Share packages between images, regardless of the order they were
|
|
||||||
added.
|
|
||||||
* Improve performance by _skipping_ container
|
|
||||||
image creation.
|
|
||||||
* Work with _structured data_ instead of strings,
|
|
||||||
templates and a multitude of expression languages.
|
|
||||||
* Refactor across deployments, configuration and
|
|
||||||
packaging.
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/usr/bin/env nix-shell
|
|
||||||
#!nix-shell -A manual
|
|
||||||
#!nix-shell --run live-build
|
|
|
@ -1,25 +0,0 @@
|
||||||
<book xmlns="http://docbook.org/ns/docbook"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
|
||||||
|
|
||||||
<info>
|
|
||||||
|
|
||||||
<title>Arion Manual</title>
|
|
||||||
<subtitle>Version none</subtitle>
|
|
||||||
|
|
||||||
<author>
|
|
||||||
<affiliation>
|
|
||||||
<orgname>Hercules Labs and other Arion contributors</orgname>
|
|
||||||
</affiliation>
|
|
||||||
<contrib>Author</contrib>
|
|
||||||
</author>
|
|
||||||
|
|
||||||
</info>
|
|
||||||
|
|
||||||
<xi:include href="introduction.xml" xpointer="xpointer(/article)" />
|
|
||||||
<xi:include href="installation.xml" xpointer="xpointer(/article)" />
|
|
||||||
|
|
||||||
<xi:include href="options-composition.xml" />
|
|
||||||
<xi:include href="options-service.xml" />
|
|
||||||
|
|
||||||
</book>
|
|
|
@ -1,224 +0,0 @@
|
||||||
<?xml version="1.0"?>
|
|
||||||
|
|
||||||
<xsl:stylesheet version="1.0"
|
|
||||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
|
||||||
xmlns:str="http://exslt.org/strings"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
xmlns:nixos="tag:nixos.org"
|
|
||||||
xmlns="http://docbook.org/ns/docbook"
|
|
||||||
extension-element-prefixes="str"
|
|
||||||
>
|
|
||||||
|
|
||||||
<xsl:output method='xml' encoding="UTF-8" />
|
|
||||||
|
|
||||||
<xsl:param name="revision" />
|
|
||||||
<xsl:param name="program" />
|
|
||||||
<xsl:param name="sourceUrl" />
|
|
||||||
<xsl:param name="nixPathKey" />
|
|
||||||
|
|
||||||
<xsl:template match="/expr/list">
|
|
||||||
<appendix xml:id="appendix-configuration-options">
|
|
||||||
<title>Configuration Options</title>
|
|
||||||
<variablelist xml:id="configuration-variable-list">
|
|
||||||
<xsl:for-each select="attrs">
|
|
||||||
<xsl:variable name="id" select="concat('opt-', str:replace(str:replace(str:replace(str:replace(attr[@name = 'name']/string/@value, '*', '_'), '<', '_'), '>', '_'), '?', '_'))" />
|
|
||||||
<varlistentry>
|
|
||||||
<term xlink:href="#{$id}">
|
|
||||||
<xsl:attribute name="xml:id"><xsl:value-of select="$id"/></xsl:attribute>
|
|
||||||
<option>
|
|
||||||
<xsl:value-of select="attr[@name = 'name']/string/@value" />
|
|
||||||
</option>
|
|
||||||
</term>
|
|
||||||
|
|
||||||
<listitem>
|
|
||||||
|
|
||||||
<nixos:option-description>
|
|
||||||
<para>
|
|
||||||
<xsl:value-of disable-output-escaping="yes"
|
|
||||||
select="attr[@name = 'description']/string/@value" />
|
|
||||||
</para>
|
|
||||||
</nixos:option-description>
|
|
||||||
|
|
||||||
<xsl:if test="attr[@name = 'type']">
|
|
||||||
<para>
|
|
||||||
<emphasis>Type:</emphasis>
|
|
||||||
<xsl:text> </xsl:text>
|
|
||||||
<xsl:value-of select="attr[@name = 'type']/string/@value"/>
|
|
||||||
<xsl:if test="attr[@name = 'readOnly']/bool/@value = 'true'">
|
|
||||||
<xsl:text> </xsl:text>
|
|
||||||
<emphasis>(read only)</emphasis>
|
|
||||||
</xsl:if>
|
|
||||||
</para>
|
|
||||||
</xsl:if>
|
|
||||||
|
|
||||||
<xsl:if test="attr[@name = 'default']">
|
|
||||||
<para>
|
|
||||||
<emphasis>Default:</emphasis>
|
|
||||||
<xsl:text> </xsl:text>
|
|
||||||
<xsl:apply-templates select="attr[@name = 'default']" mode="top" />
|
|
||||||
</para>
|
|
||||||
</xsl:if>
|
|
||||||
|
|
||||||
<xsl:if test="attr[@name = 'example']">
|
|
||||||
<para>
|
|
||||||
<emphasis>Example:</emphasis>
|
|
||||||
<xsl:text> </xsl:text>
|
|
||||||
<xsl:choose>
|
|
||||||
<xsl:when test="attr[@name = 'example']/attrs[attr[@name = '_type' and string[@value = 'literalExample']]]">
|
|
||||||
<programlisting><xsl:value-of select="attr[@name = 'example']/attrs/attr[@name = 'text']/string/@value" /></programlisting>
|
|
||||||
</xsl:when>
|
|
||||||
<xsl:otherwise>
|
|
||||||
<xsl:apply-templates select="attr[@name = 'example']" mode="top" />
|
|
||||||
</xsl:otherwise>
|
|
||||||
</xsl:choose>
|
|
||||||
</para>
|
|
||||||
</xsl:if>
|
|
||||||
|
|
||||||
<xsl:if test="attr[@name = 'relatedPackages']">
|
|
||||||
<para>
|
|
||||||
<emphasis>Related packages:</emphasis>
|
|
||||||
<xsl:text> </xsl:text>
|
|
||||||
<xsl:value-of disable-output-escaping="yes"
|
|
||||||
select="attr[@name = 'relatedPackages']/string/@value" />
|
|
||||||
</para>
|
|
||||||
</xsl:if>
|
|
||||||
|
|
||||||
<xsl:if test="count(attr[@name = 'declarations']/list/*) != 0">
|
|
||||||
<para>
|
|
||||||
<emphasis>Declared by:</emphasis>
|
|
||||||
</para>
|
|
||||||
<xsl:apply-templates select="attr[@name = 'declarations']" />
|
|
||||||
</xsl:if>
|
|
||||||
|
|
||||||
<xsl:if test="count(attr[@name = 'definitions']/list/*) != 0">
|
|
||||||
<para>
|
|
||||||
<emphasis>Defined by:</emphasis>
|
|
||||||
</para>
|
|
||||||
<xsl:apply-templates select="attr[@name = 'definitions']" />
|
|
||||||
</xsl:if>
|
|
||||||
|
|
||||||
</listitem>
|
|
||||||
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
</xsl:for-each>
|
|
||||||
|
|
||||||
</variablelist>
|
|
||||||
</appendix>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="*" mode="top">
|
|
||||||
<xsl:choose>
|
|
||||||
<xsl:when test="string[contains(@value, '
')]">
|
|
||||||
<programlisting>
|
|
||||||
<xsl:text>''
|
|
||||||
</xsl:text><xsl:value-of select='str:replace(string/@value, "${", "''${")' /><xsl:text>''</xsl:text></programlisting>
|
|
||||||
</xsl:when>
|
|
||||||
<xsl:otherwise>
|
|
||||||
<literal><xsl:apply-templates /></literal>
|
|
||||||
</xsl:otherwise>
|
|
||||||
</xsl:choose>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="null">
|
|
||||||
<xsl:text>null</xsl:text>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="string">
|
|
||||||
<xsl:choose>
|
|
||||||
<xsl:when test="(contains(@value, '"') or contains(@value, '\')) and not(contains(@value, '
'))">
|
|
||||||
<xsl:text>''</xsl:text><xsl:value-of select='str:replace(@value, "${", "''${")' /><xsl:text>''</xsl:text>
|
|
||||||
</xsl:when>
|
|
||||||
<xsl:otherwise>
|
|
||||||
<xsl:text>"</xsl:text><xsl:value-of select="str:replace(str:replace(str:replace(str:replace(@value, '\', '\\'), '"', '\"'), '
', '\n'), '$', '\$')" /><xsl:text>"</xsl:text>
|
|
||||||
</xsl:otherwise>
|
|
||||||
</xsl:choose>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="int">
|
|
||||||
<xsl:value-of select="@value" />
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="bool[@value = 'true']">
|
|
||||||
<xsl:text>true</xsl:text>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="bool[@value = 'false']">
|
|
||||||
<xsl:text>false</xsl:text>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="list">
|
|
||||||
[
|
|
||||||
<xsl:for-each select="*">
|
|
||||||
<xsl:apply-templates select="." />
|
|
||||||
<xsl:text> </xsl:text>
|
|
||||||
</xsl:for-each>
|
|
||||||
]
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="attrs[attr[@name = '_type' and string[@value = 'literalExample']]]">
|
|
||||||
<xsl:value-of select="attr[@name = 'text']/string/@value" />
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="attrs">
|
|
||||||
{
|
|
||||||
<xsl:for-each select="attr">
|
|
||||||
<xsl:value-of select="@name" />
|
|
||||||
<xsl:text> = </xsl:text>
|
|
||||||
<xsl:apply-templates select="*" /><xsl:text>; </xsl:text>
|
|
||||||
</xsl:for-each>
|
|
||||||
}
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="derivation">
|
|
||||||
<replaceable>(build of <xsl:value-of select="attr[@name = 'name']/string/@value" />)</replaceable>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
<xsl:template match="attr[@name = 'declarations' or @name = 'definitions']">
|
|
||||||
<simplelist>
|
|
||||||
<xsl:for-each select="list/string">
|
|
||||||
<member><filename>
|
|
||||||
<!-- Hyperlink the filename either to the NixOS Subversion
|
|
||||||
repository (if it’s a module and we have a revision number),
|
|
||||||
or to the local filesystem. -->
|
|
||||||
<xsl:choose>
|
|
||||||
<xsl:when test="not(starts-with(@value, '/'))">
|
|
||||||
<xsl:attribute name="xlink:href"><xsl:value-of select="$sourceUrl"/>/<xsl:value-of select="@value"/></xsl:attribute>
|
|
||||||
</xsl:when>
|
|
||||||
<xsl:otherwise>
|
|
||||||
<xsl:attribute name="xlink:href">file://<xsl:value-of select="@value"/></xsl:attribute>
|
|
||||||
</xsl:otherwise>
|
|
||||||
</xsl:choose>
|
|
||||||
<!-- Print the filename and make it user-friendly by replacing the
|
|
||||||
/nix/store/<hash> prefix by the default location of nixos
|
|
||||||
sources. -->
|
|
||||||
<xsl:choose>
|
|
||||||
<xsl:when test="$nixPathKey != ''">
|
|
||||||
<<xsl:value-of select="$nixPathKey"/>/<xsl:value-of select="@value"/>>
|
|
||||||
</xsl:when>
|
|
||||||
<xsl:otherwise>
|
|
||||||
<xsl:value-of select="@value" />
|
|
||||||
</xsl:otherwise>
|
|
||||||
</xsl:choose>
|
|
||||||
</filename></member>
|
|
||||||
</xsl:for-each>
|
|
||||||
</simplelist>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
<xsl:template match="function">
|
|
||||||
<xsl:text>λ</xsl:text>
|
|
||||||
</xsl:template>
|
|
||||||
|
|
||||||
|
|
||||||
</xsl:stylesheet>
|
|
|
@ -1,159 +0,0 @@
|
||||||
|
|
||||||
hr { color: #ddd; margin-top: 3ex; }
|
|
||||||
h1, h2, h3, h4 { font-weight: bold; }
|
|
||||||
h1 { font-size: 200%; margin-top: 5ex; }
|
|
||||||
h2 { font-size: 160%; margin-top: 4ex; }
|
|
||||||
h3,h4 { font-size: 120%; margin-top: 3ex; }
|
|
||||||
|
|
||||||
/* From Semantic UI */
|
|
||||||
body {
|
|
||||||
font-family: Lato,'Helvetica Neue',Arial,Helvetica,sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.4285em;
|
|
||||||
color: rgba(0,0,0,.87);
|
|
||||||
}
|
|
||||||
|
|
||||||
code.literal {
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
background-color:transparent;
|
|
||||||
-webkit-text-decoration-skip:objects
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color:#4183c4;
|
|
||||||
text-decoration:none
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color:#1e70bf;
|
|
||||||
text-decoration:none
|
|
||||||
}
|
|
||||||
::-webkit-selection {
|
|
||||||
background-color:#cce2ff;
|
|
||||||
color:rgba(0,0,0,.87)
|
|
||||||
}
|
|
||||||
::-moz-selection {
|
|
||||||
background-color:#cce2ff;
|
|
||||||
color:rgba(0,0,0,.87)
|
|
||||||
}
|
|
||||||
::selection {
|
|
||||||
background-color:#cce2ff;
|
|
||||||
color:rgba(0,0,0,.87)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* toc menu */
|
|
||||||
@media screen and (min-width: 60em) {
|
|
||||||
body {
|
|
||||||
margin-left: 16.5em;
|
|
||||||
}
|
|
||||||
div.toc {
|
|
||||||
position: fixed;
|
|
||||||
top: 0pt;
|
|
||||||
left: 1em;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
width: 15.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 90em) {
|
|
||||||
div.toc {
|
|
||||||
left: 5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* hide per chapter toc */
|
|
||||||
div.chapter div.toc {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* From Nixpkgs: */
|
|
||||||
|
|
||||||
div.book
|
|
||||||
{
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.book > div
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* based on https://medium.com/@zkareemz/golden-ratio-62b3b6d4282a
|
|
||||||
* we do 70 characters per line to fit code listings better
|
|
||||||
* 70 * (font-size / 1.618)
|
|
||||||
* expression for emacs:
|
|
||||||
* (* 70 (/ 1 1.618))
|
|
||||||
*/
|
|
||||||
max-width: 43.2em;
|
|
||||||
text-align: left;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
|
||||||
Special elements:
|
|
||||||
***************************************************************************/
|
|
||||||
|
|
||||||
.term
|
|
||||||
{
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
div.variablelist dd p, div.glosslist dd p
|
|
||||||
{
|
|
||||||
margin-top: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.variablelist dd, div.glosslist dd
|
|
||||||
{
|
|
||||||
margin-left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.glosslist dt
|
|
||||||
{
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.varname
|
|
||||||
{
|
|
||||||
color: #004000;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.command strong
|
|
||||||
{
|
|
||||||
font-weight: normal;
|
|
||||||
color: #004000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.calloutlist table
|
|
||||||
{
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table
|
|
||||||
{
|
|
||||||
border-collapse: collapse;
|
|
||||||
box-shadow: 0.4em 0.4em 0.5em #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.simplelist
|
|
||||||
{
|
|
||||||
text-align: left;
|
|
||||||
color: #005aa0;
|
|
||||||
border: 0;
|
|
||||||
padding: 5px;
|
|
||||||
background: #fffff5;
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: italic;
|
|
||||||
box-shadow: none;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.navheader table, div.navfooter table {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.affiliation
|
|
||||||
{
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
4
docs/README.md
Normal file
4
docs/README.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
# Documentation
|
||||||
|
|
||||||
|
Please refer to the [**rendered documentation**](https://docs.hercules-ci.com/arion), which includes the [**options.**](https://docs.hercules-ci.com/arion/options/)
|
7
docs/antora.yml
Normal file
7
docs/antora.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
name: arion
|
||||||
|
title: Arion Documentation
|
||||||
|
version: 'master'
|
||||||
|
nav:
|
||||||
|
- modules/ROOT/nav.adoc
|
||||||
|
- modules/reference/nav.adoc
|
||||||
|
nix: true
|
31
docs/flake-module.nix
Normal file
31
docs/flake-module.nix
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
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
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
1
docs/modules/ROOT/examples/full-nixos/arion-compose.nix
Symbolic link
1
docs/modules/ROOT/examples/full-nixos/arion-compose.nix
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../../../../examples/full-nixos/arion-compose.nix
|
1
docs/modules/ROOT/examples/minimal/arion-compose.nix
Symbolic link
1
docs/modules/ROOT/examples/minimal/arion-compose.nix
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../../../../examples/minimal/arion-compose.nix
|
1
docs/modules/ROOT/examples/nixos-unit/arion-compose.nix
Symbolic link
1
docs/modules/ROOT/examples/nixos-unit/arion-compose.nix
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../../../../examples/nixos-unit/arion-compose.nix
|
3
docs/modules/ROOT/nav.adoc
Normal file
3
docs/modules/ROOT/nav.adoc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
* xref:index.adoc[Getting Started]
|
||||||
|
* xref:options.adoc[Arion Options]
|
||||||
|
* xref:deployment.adoc[Deployment]
|
71
docs/modules/ROOT/pages/deployment.adoc
Normal file
71
docs/modules/ROOT/pages/deployment.adoc
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
= 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]
|
312
docs/modules/ROOT/pages/index.adoc
Normal file
312
docs/modules/ROOT/pages/index.adoc
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
= Welcome to Arion documentation
|
||||||
|
|
||||||
|
== Introduction
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
It is built on top of https://docs.docker.com/compose/overview/[Docker
|
||||||
|
Compose], which implements the container orchestration functionality.
|
||||||
|
|
||||||
|
Instead of configuring the compositions in YAML files like
|
||||||
|
`docker-compose.yaml`, Arion uses the https://nixos.org/nix/[Nix]
|
||||||
|
language to declare the compositions. Because of this, Arion gives you
|
||||||
|
the ability to declare your deployments, configuration and packaging
|
||||||
|
in the same language. By replacing multiple tools with a single
|
||||||
|
language, you decrease your mental load and you can more easily
|
||||||
|
refactor and maintain your configurations.
|
||||||
|
|
||||||
|
Although Arion can be used as a Docker Compose with an improved
|
||||||
|
configuration front end, there is more to be gained from integrating
|
||||||
|
with Nix. In particular, the more structured approach of Nix compared
|
||||||
|
to Dockerfiles allows the following:
|
||||||
|
|
||||||
|
* Build components of your image in *parallel, automatically*
|
||||||
|
* *Share packages between images*, regardless of the order they were
|
||||||
|
added
|
||||||
|
* Improve performance by *skipping container
|
||||||
|
image creation*
|
||||||
|
* Work with *structured data instead of strings*,
|
||||||
|
templates and a multitude of expression languages
|
||||||
|
* Refactor across deployments, configuration and packaging
|
||||||
|
|
||||||
|
Arion allows to compose containers with different granularity:
|
||||||
|
|
||||||
|
* <<Minimal: Plain command using nixpkgs>>
|
||||||
|
* <<NixOS: run only one systemd service>>
|
||||||
|
* <<NixOS: run full OS>>
|
||||||
|
* <<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)
|
||||||
|
|
||||||
|
`podman-compose` support is currently WIP on a separate branch.
|
||||||
|
|
||||||
|
== Installation
|
||||||
|
|
||||||
|
=== Nix
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ nix-env -iA arion -f https://github.com/hercules-ci/arion/tarball/master
|
||||||
|
```
|
||||||
|
|
||||||
|
=== NixOS
|
||||||
|
|
||||||
|
Add this module to your NixOS configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ pkgs, ... }: {
|
||||||
|
environment.systemPackages = [
|
||||||
|
pkgs.arion
|
||||||
|
|
||||||
|
# Do install the docker CLI to talk to podman.
|
||||||
|
# Not needed when virtualisation.docker.enable = true;
|
||||||
|
pkgs.docker-client
|
||||||
|
];
|
||||||
|
|
||||||
|
# Arion works with Docker, but for NixOS-based containers, you need Podman
|
||||||
|
# since NixOS 21.05.
|
||||||
|
virtualisation.docker.enable = false;
|
||||||
|
virtualisation.podman.enable = true;
|
||||||
|
virtualisation.podman.dockerSocket.enable = true;
|
||||||
|
virtualisation.podman.defaultNetwork.dnsname.enable = true;
|
||||||
|
|
||||||
|
# Use your username instead of `myuser`
|
||||||
|
users.extraUsers.myuser.extraGroups = ["podman"];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
== Not installing: use it in a project
|
||||||
|
|
||||||
|
TODO: describe: using nix-shell or in a script, building images as
|
||||||
|
part of nix-build, pinning, see also todomvc-nix.
|
||||||
|
|
||||||
|
TODO: exposed Nix functions: arion.build, arion.eval (a bit of IFD)
|
||||||
|
|
||||||
|
|
||||||
|
////
|
||||||
|
|
||||||
|
|
||||||
|
== Usage
|
||||||
|
|
||||||
|
Arion is configured declaratively with two files:
|
||||||
|
|
||||||
|
=== arion-pkgs.nix
|
||||||
|
|
||||||
|
Arion needs `arion-pkgs.nix` to import nixpkgs, for example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
import <nixpkgs> { system = "x86_64-linux"; }
|
||||||
|
```
|
||||||
|
|
||||||
|
or more sophisticated (recommended) setup with https://github.com/nmattia/niv[Niv].
|
||||||
|
|
||||||
|
=== arion-compose.nix
|
||||||
|
|
||||||
|
Describe containers using NixOS-style modules. There are a few options:
|
||||||
|
|
||||||
|
==== Minimal: Plain command using nixpkgs
|
||||||
|
|
||||||
|
`examples/minimal/arion-compose.nix`
|
||||||
|
[,nix]
|
||||||
|
----
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
project.name = "webapp";
|
||||||
|
services = {
|
||||||
|
|
||||||
|
webserver = {
|
||||||
|
image.enableRecommendedContents = true;
|
||||||
|
service.useHostStore = true;
|
||||||
|
service.command = [ "sh" "-c" ''
|
||||||
|
cd "$$WEB_ROOT"
|
||||||
|
${pkgs.python3}/bin/python -m http.server
|
||||||
|
'' ];
|
||||||
|
service.ports = [
|
||||||
|
"8000:8000" # host:container
|
||||||
|
];
|
||||||
|
service.environment.WEB_ROOT = "${pkgs.nix.doc}/share/doc/nix/manual";
|
||||||
|
service.stop_signal = "SIGINT";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
==== NixOS: run full OS
|
||||||
|
|
||||||
|
`examples/full-nixos/arion-compose.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" ];
|
||||||
|
service.useHostStore = true;
|
||||||
|
service.ports = [
|
||||||
|
"8000:80" # host:container
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
==== Docker image from DockerHub
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
services.postgres = {
|
||||||
|
service.image = "postgres:10";
|
||||||
|
service.volumes = [ "${toString ./.}/postgres-data:/var/lib/postgresql/data" ];
|
||||||
|
service.environment.POSTGRES_PASSWORD = "mydefaultpass";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
==== 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ arion up -d
|
||||||
|
$ arion logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
You can go to `examples/*/` and run these commands to give it a quick try.
|
||||||
|
|
||||||
|
=== Inspect the config
|
||||||
|
|
||||||
|
While developing an arion project, you can make use of `arion repl`, which launches
|
||||||
|
a `nix repl` on the project configuration.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ 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
|
||||||
|
```
|
||||||
|
|
||||||
|
== Project Status
|
||||||
|
|
||||||
|
This project was born out of a process supervision need for local
|
||||||
|
development environments while working on
|
||||||
|
https://www.hercules-ci.com[Hercules CI]. (It was also born out of
|
||||||
|
ancient Greek deities disguised as horses. More on that later.)
|
||||||
|
|
||||||
|
Arion can be used for simple single host deployments, using Docker's TLS
|
||||||
|
client verification, or https://search.nixos.org/options?channel=unstable&show=virtualisation.podman.networkSocket.enable&query=virtualisation.podman[`virtualisation.podman.networkSocket` options].
|
||||||
|
Remote deployments do not support `useHostStore`, although an SSH-based deployment method could support this.
|
||||||
|
Docker Swarm is not currently supported.
|
||||||
|
|
||||||
|
Arion has run successfully on Linux distributions other than NixOS, but we only perform CI for Arion on NixOS.
|
||||||
|
|
||||||
|
|
||||||
|
== How it works
|
||||||
|
|
||||||
|
Arion is essentially a thin wrapper around Nix and docker-compose. When
|
||||||
|
it runs, it does the following:
|
||||||
|
|
||||||
|
* Evaluate the configuration using Nix, producing a
|
||||||
|
`docker-compose.yaml` and a garbage collection root
|
||||||
|
* Invoke `docker-compose`
|
||||||
|
* Clean up the garbage collection root
|
||||||
|
|
||||||
|
Most of the interesting stuff happens in Arion’s Nix expressions, where
|
||||||
|
it runs the module system (known from NixOS) and provides the
|
||||||
|
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
|
||||||
|
performs the bind mounts to make the host Nix store available in the
|
||||||
|
container.
|
||||||
|
|
||||||
|
== FAQ
|
||||||
|
|
||||||
|
=== Do I need to use Hercules CI?
|
||||||
|
|
||||||
|
Nope, it’s just Nix and Docker Compose under the hood.
|
||||||
|
|
||||||
|
It does xref:hercules-ci-effects:ROOT:reference/nix-functions/runArion.adoc[integrate] nicely though.
|
||||||
|
|
||||||
|
=== What about garbage collection?
|
||||||
|
|
||||||
|
Arion removes the need for garbage collecting docker images, delegating
|
||||||
|
this task to Nix when using `service.useHostStore`.
|
||||||
|
|
||||||
|
Arion creates a garbage collection root that it cleans up after completing
|
||||||
|
the command. This means that `arion up -d` should not be used with `useHostStore`
|
||||||
|
in production. Instead, disable `useHostStore`, which will use `dockerTools` to
|
||||||
|
generate images that can be used in production.
|
||||||
|
|
||||||
|
=== Why is my container not running latest code?
|
||||||
|
|
||||||
|
Rebuild the image using `arion up -d --always-recreate-deps <name>` or simply `arion up -d`.
|
||||||
|
|
||||||
|
Like `docker-compose restart`, `arion restart` does not update the image before starting.
|
||||||
|
|
||||||
|
=== What is messing with my environment variables?
|
||||||
|
|
||||||
|
Docker Compose performs its own environment variable substitution. This
|
||||||
|
can be a little annoying in `services.command` for example. Either
|
||||||
|
reference a script from `pkgs.writeScript` or escape the dollar sign as
|
||||||
|
`$$`.
|
||||||
|
|
||||||
|
=== Why name it ``Arion``?
|
||||||
|
|
||||||
|
Arion comes from Greek mythology. Poseidon, the god of Docker -- I mean the seas --
|
||||||
|
had his eye on Demeter. Demeter tried to trick him by disguising as a
|
||||||
|
horse, but Poseidon saw through the deception and they had Arion.
|
||||||
|
|
||||||
|
So Arion is a super fast divine horse; the result of some weird mixing.
|
||||||
|
Also it talks.
|
||||||
|
|
||||||
|
(And we felt morally obliged to name our stuff after Greek mythology)
|
3
docs/modules/ROOT/pages/options.adoc
Normal file
3
docs/modules/ROOT/pages/options.adoc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Arion Options
|
||||||
|
|
||||||
|
include::partial$arion-options.adoc[]
|
20
docs/options.nix
Normal file
20
docs/options.nix
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{ pkgs ? import ../nix {} }:
|
||||||
|
|
||||||
|
let
|
||||||
|
eval = pkgs.lib.evalModules {
|
||||||
|
modules = import ../src/nix/modules.nix;
|
||||||
|
};
|
||||||
|
options = pkgs.nixosOptionsDoc {
|
||||||
|
options = eval.options;
|
||||||
|
};
|
||||||
|
|
||||||
|
in (pkgs.runCommand "agent-options.adoc" { } ''
|
||||||
|
cat >$out <<EOF
|
||||||
|
= Arion options
|
||||||
|
|
||||||
|
EOF
|
||||||
|
cat ${options.optionsAsciiDoc} >>$out
|
||||||
|
'').overrideAttrs (o: {
|
||||||
|
# Work around https://github.com/hercules-ci/hercules-ci-agent/issues/168
|
||||||
|
allowSubstitutes = true;
|
||||||
|
})
|
BIN
docs/ui-bundle.zip
Normal file
BIN
docs/ui-bundle.zip
Normal file
Binary file not shown.
30
examples/flake/arion-compose.nix
Normal file
30
examples/flake/arion-compose.nix
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{ 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"
|
||||||
|
${pkgs.python3}/bin/python -m http.server
|
||||||
|
'' ];
|
||||||
|
service.ports = [
|
||||||
|
"8000:8000" # host:container
|
||||||
|
];
|
||||||
|
service.environment.WEB_ROOT = "${pkgs.nix.doc}/share/doc/nix/manual";
|
||||||
|
service.stop_signal = "SIGINT";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
13
examples/flake/arion-pkgs.nix
Normal file
13
examples/flake/arion-pkgs.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
let
|
||||||
|
flake = if builtins ? getFlake
|
||||||
|
then (builtins.getFlake (toString ./.)).pkgs
|
||||||
|
else (import flake-compat { src = ./.; }).defaultNix;
|
||||||
|
# NB: this is lazy
|
||||||
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||||
|
inherit (lock.nodes.flake-compat.locked) owner repo rev narHash;
|
||||||
|
flake-compat = builtins.fetchTarball {
|
||||||
|
url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz";
|
||||||
|
sha256 = narHash;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
flake.pkgs
|
44
examples/flake/flake.lock
Normal file
44
examples/flake/flake.lock
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-compat": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1606424373,
|
||||||
|
"narHash": "sha256-oq8d4//CJOrVj+EcOaSXvMebvuTkmBJuT5tzlfewUnQ=",
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"rev": "99f1c2157fba4bfe6211a321fd0ee43199025dbf",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "edolstra",
|
||||||
|
"repo": "flake-compat",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1618853290,
|
||||||
|
"narHash": "sha256-K4fddnrGOcKL+6CEchRrVmepiwvwvHxB87goqBTI5Bs=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "9a1672105db0eebe8ef59f310397435f2d0298d0",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-20.09",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
19
examples/flake/flake.nix
Normal file
19
examples/flake/flake.nix
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
description = "A very basic flake";
|
||||||
|
|
||||||
|
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-20.09";
|
||||||
|
inputs.flake-compat.url = "github:edolstra/flake-compat";
|
||||||
|
inputs.flake-compat.flake = false;
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, ... }: {
|
||||||
|
|
||||||
|
pkgs = nixpkgs.legacyPackages.x86_64-linux;
|
||||||
|
# # alternative:
|
||||||
|
# pkgs = import nixpkgs { config = { }; overlays = [ ]; system = "x86_64-linux"; };
|
||||||
|
|
||||||
|
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
|
||||||
|
|
||||||
|
defaultPackage.x86_64-linux = self.packages.x86_64-linux.hello;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,9 +1,15 @@
|
||||||
{
|
{
|
||||||
docker-compose.services.webserver = { pkgs, ... }: {
|
project.name = "full-nixos";
|
||||||
|
services.webserver = { pkgs, lib, ... }: {
|
||||||
nixos.useSystemd = true;
|
nixos.useSystemd = true;
|
||||||
nixos.configuration.boot.tmpOnTmpfs = true;
|
nixos.configuration.boot.tmp.useTmpfs = true;
|
||||||
|
nixos.configuration.networking.useDHCP = false;
|
||||||
nixos.configuration.services.nginx.enable = true;
|
nixos.configuration.services.nginx.enable = true;
|
||||||
nixos.configuration.services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
|
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" ];
|
||||||
service.useHostStore = true;
|
service.useHostStore = true;
|
||||||
service.ports = [
|
service.ports = [
|
||||||
"8000:80" # host:container
|
"8000:80" # host:container
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
|
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
|
||||||
import <nixpkgs> {}
|
import <nixpkgs> {
|
||||||
|
# We specify the architecture explicitly. Use a Linux remote builder when
|
||||||
|
# calling arion from other platforms.
|
||||||
|
system = "x86_64-linux";
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
{ pkgs, ... }:
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
config.docker-compose.services = {
|
project.name = "webapp";
|
||||||
|
services = {
|
||||||
|
|
||||||
webserver = {
|
webserver = {
|
||||||
|
image.enableRecommendedContents = true;
|
||||||
service.useHostStore = true;
|
service.useHostStore = true;
|
||||||
# service.depends_on = [ "backend" ];
|
|
||||||
service.command = [ "sh" "-c" ''
|
service.command = [ "sh" "-c" ''
|
||||||
cd "$$WEB_ROOT"
|
cd "$$WEB_ROOT"
|
||||||
${pkgs.python3}/bin/python -m http.server
|
${pkgs.python3}/bin/python -m http.server
|
||||||
|
@ -13,9 +14,7 @@
|
||||||
"8000:8000" # host:container
|
"8000:8000" # host:container
|
||||||
];
|
];
|
||||||
service.environment.WEB_ROOT = "${pkgs.nix.doc}/share/doc/nix/manual";
|
service.environment.WEB_ROOT = "${pkgs.nix.doc}/share/doc/nix/manual";
|
||||||
|
service.stop_signal = "SIGINT";
|
||||||
};
|
};
|
||||||
|
|
||||||
# backend = { ... }
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
|
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
|
||||||
import <nixpkgs> {}
|
import <nixpkgs> {
|
||||||
|
# We specify the architecture explicitly. Use a Linux remote builder when
|
||||||
|
# calling arion from other platforms.
|
||||||
|
system = "x86_64-linux";
|
||||||
|
}
|
||||||
|
|
|
@ -17,17 +17,27 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
{
|
{
|
||||||
docker-compose.services.webserver = { config, pkgs, ... }: {
|
project.name = "nixos-unit";
|
||||||
|
services.webserver = { config, pkgs, ... }: {
|
||||||
|
|
||||||
nixos.configuration = {config, pkgs, ...}: {
|
nixos.configuration = {config, lib, options, pkgs, ...}: {
|
||||||
boot.isContainer = true;
|
boot.isContainer = true;
|
||||||
services.nginx.enable = true;
|
services.nginx = {
|
||||||
services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
|
enable = true;
|
||||||
|
virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
|
||||||
|
} // lib.optionalAttrs (options?services.nginx.stateDir) {
|
||||||
|
# Work around a problem in NixOS 20.03
|
||||||
|
stateDir = "/var/lib/nginx";
|
||||||
|
};
|
||||||
system.build.run-nginx = pkgs.writeScript "run-nginx" ''
|
system.build.run-nginx = pkgs.writeScript "run-nginx" ''
|
||||||
#!${pkgs.bash}/bin/bash
|
#!${pkgs.bash}/bin/bash
|
||||||
PATH='${config.systemd.services.nginx.environment.PATH}'
|
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.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
|
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
|
||||||
${config.systemd.services.nginx.runner}
|
${config.systemd.services.nginx.runner}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,2 +1,6 @@
|
||||||
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
|
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
|
||||||
import <nixpkgs> {}
|
import <nixpkgs> {
|
||||||
|
# We specify the architecture explicitly. Use a Linux remote builder when
|
||||||
|
# calling arion from other platforms.
|
||||||
|
system = "x86_64-linux";
|
||||||
|
}
|
||||||
|
|
64
examples/traefik/arion-compose.nix
Normal file
64
examples/traefik/arion-compose.nix
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
An example of
|
||||||
|
- traefik HTTP reverse proxy
|
||||||
|
- minimal images
|
||||||
|
- routing via docker labels
|
||||||
|
|
||||||
|
Run `arion up -d` and open http://nix-docs.localhost/
|
||||||
|
|
||||||
|
*/
|
||||||
|
{ 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 = [
|
||||||
|
"${pkgs.traefik}/bin/traefik"
|
||||||
|
"--api.insecure=true"
|
||||||
|
"--providers.docker=true"
|
||||||
|
"--providers.docker.exposedbydefault=false"
|
||||||
|
"--entrypoints.web.address=:80"
|
||||||
|
];
|
||||||
|
service = {
|
||||||
|
container_name = "traefik";
|
||||||
|
stop_signal = "SIGINT";
|
||||||
|
ports = [ "80:80" "8080:8080" ];
|
||||||
|
volumes = [ "/var/run/docker.sock:/var/run/docker.sock:ro" ];
|
||||||
|
networks = [ "traefik-custom" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nix-docs = {
|
||||||
|
image.command = ["${pkgs.writeScript "entrypoint" ''
|
||||||
|
#!${pkgs.bash}/bin/bash
|
||||||
|
cd ${pkgs.nix.doc}/share/doc/nix/manual
|
||||||
|
${pkgs.python3}/bin/python -m http.server
|
||||||
|
''}"];
|
||||||
|
service.container_name = "simple-service";
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
6
examples/traefik/arion-pkgs.nix
Normal file
6
examples/traefik/arion-pkgs.nix
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
|
||||||
|
import <nixpkgs> {
|
||||||
|
# We specify the architecture explicitly. Use a Linux remote builder when
|
||||||
|
# calling arion from other platforms.
|
||||||
|
system = "x86_64-linux";
|
||||||
|
}
|
107
flake.lock
Normal file
107
flake.lock
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
{
|
||||||
|
"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=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "d04953086551086b44b6f3c6b7eeb26294f207da",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"haskell-flake": "haskell-flake",
|
||||||
|
"hercules-ci-effects": "hercules-ci-effects",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
98
flake.nix
Normal file
98
flake.nix
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
{
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = ./.;
|
||||||
|
|
||||||
|
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
|
||||||
|
];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
13
live-unit-tests
Executable file
13
live-unit-tests
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell ./shell.nix
|
||||||
|
#!nix-shell -i bash
|
||||||
|
set -eux -o pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
ghcid \
|
||||||
|
--command 'cabal v2-repl arion-compose:arion-unit-tests --flags ghci --write-ghc-environment-files=never' \
|
||||||
|
--test=Main.main \
|
||||||
|
--reload=src/haskell \
|
||||||
|
--restart=arion-compose.cabal \
|
||||||
|
;
|
13
nix/arion.nix
Normal file
13
nix/arion.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Like the upstreamable expression but wired up for the local arion.
|
||||||
|
{ pkgs ? import ./. {}
|
||||||
|
, lib ? pkgs.lib
|
||||||
|
, haskell ? pkgs.haskell
|
||||||
|
, haskellPackages ? pkgs.haskellPackages
|
||||||
|
, arion-compose ? import ./haskell-arion-compose.nix { inherit pkgs haskellPackages; }
|
||||||
|
, runCommand ? pkgs.runCommand
|
||||||
|
}:
|
||||||
|
import ./upstreamable/default.nix {
|
||||||
|
inherit pkgs lib haskell runCommand;
|
||||||
|
haskellPackages = haskellPackages // { inherit arion-compose; };
|
||||||
|
evalSrc = ./..;
|
||||||
|
}
|
10
nix/compat.nix
Normal file
10
nix/compat.nix
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
(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
|
|
@ -1,13 +0,0 @@
|
||||||
/**
|
|
||||||
* This is the entry-point for all nix execution in this project.
|
|
||||||
*/
|
|
||||||
{ nixpkgsSrc ? ./nixpkgs.nix, ... }:
|
|
||||||
import (import ./nixpkgs.nix) {
|
|
||||||
# Makes the config pure as well. See <nixpkgs>/top-level/impure.nix:
|
|
||||||
config = {
|
|
||||||
};
|
|
||||||
overlays = [
|
|
||||||
# all the packages are defined there:
|
|
||||||
(import ./overlay.nix)
|
|
||||||
];
|
|
||||||
}
|
|
20
nix/haskell-arion-compose.nix
Normal file
20
nix/haskell-arion-compose.nix
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
# NOTE: This file produces a haskell library, not the arion package!
|
||||||
|
|
||||||
|
{ pkgs ? import ./default.nix {}, haskellPackages ? pkgs.haskellPackages }:
|
||||||
|
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
|
||||||
|
export NIX_PATH=nixpkgs=${pkgs.path}
|
||||||
|
'';
|
||||||
|
})
|
|
@ -1,5 +0,0 @@
|
||||||
# to update: $ nix-prefetch-url --unpack url
|
|
||||||
builtins.fetchTarball {
|
|
||||||
url = "https://github.com/NixOS/nixpkgs/archive/be445a9074f139d63e704fa82610d25456562c3d.tar.gz";
|
|
||||||
sha256 = "15dc7gdspimavcwyw9nif4s59v79gk18rwsafylffs9m1ld2dxwa";
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
self: super: {
|
|
||||||
arion = super.callPackage ../arion.nix {};
|
|
||||||
tests = super.callPackage ../tests {};
|
|
||||||
doc = super.callPackage ../doc {};
|
|
||||||
}
|
|
88
nix/upstreamable/default.nix
Normal file
88
nix/upstreamable/default.nix
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
args@
|
||||||
|
{ pkgs
|
||||||
|
, lib
|
||||||
|
, haskellPackages
|
||||||
|
, haskell
|
||||||
|
, runCommand
|
||||||
|
|
||||||
|
# Allow this expression file to be used more efficiently in situations where
|
||||||
|
# the sources are more readily available. Unpacking haskellPackages.arion-compose.src
|
||||||
|
# is not always the best choice for arion.eval.
|
||||||
|
, evalSrc ? null
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
/* This derivation builds the arion tool.
|
||||||
|
|
||||||
|
It is based on the arion-compose Haskell package, but adapted and extended to
|
||||||
|
- have the correct name
|
||||||
|
- have a smaller closure size
|
||||||
|
- have functions to use Arion from inside Nix: arion.eval and arion.build
|
||||||
|
- make it self-contained by including docker-compose
|
||||||
|
*/
|
||||||
|
arion =
|
||||||
|
justStaticExecutables (
|
||||||
|
overrideCabal
|
||||||
|
arion-compose
|
||||||
|
cabalOverrides
|
||||||
|
);
|
||||||
|
|
||||||
|
inherit (haskell.lib) justStaticExecutables overrideCabal;
|
||||||
|
|
||||||
|
inherit (haskellPackages) arion-compose;
|
||||||
|
|
||||||
|
cabalOverrides = o: {
|
||||||
|
buildTools = (o.buildTools or []) ++ [pkgs.makeWrapper];
|
||||||
|
passthru = (o.passthru or {}) // {
|
||||||
|
inherit eval build;
|
||||||
|
};
|
||||||
|
# Patch away the arion-compose name. Unlike the Haskell library, the program
|
||||||
|
# is called arion (arion was already taken on hackage).
|
||||||
|
pname = "arion";
|
||||||
|
src = arion-compose.src;
|
||||||
|
|
||||||
|
# PYTHONPATH
|
||||||
|
#
|
||||||
|
# We close off the python module search path!
|
||||||
|
#
|
||||||
|
# Accepting directories from the environment into the search path
|
||||||
|
# tends to break things. Docker Compose does not have a plugin
|
||||||
|
# system as far as I can tell, so I don't expect this to break a
|
||||||
|
# feature, but rather to make the program more robustly self-
|
||||||
|
# contained.
|
||||||
|
|
||||||
|
postInstall = ''${o.postInstall or ""}
|
||||||
|
mkdir -p $out/libexec
|
||||||
|
mv $out/bin/arion $out/libexec
|
||||||
|
makeWrapper $out/libexec/arion $out/bin/arion \
|
||||||
|
--unset PYTHONPATH \
|
||||||
|
--prefix PATH : ${lib.makeBinPath [ pkgs.docker-compose ]} \
|
||||||
|
;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Unpacked sources for evaluation by `eval`
|
||||||
|
evalSrc' = args.evalSrc or (runCommand "arion-src" {}
|
||||||
|
"mkdir $out; tar -C $out --strip-components=1 -xf ${arion-compose.src}");
|
||||||
|
|
||||||
|
/* Function for evaluating a composition
|
||||||
|
|
||||||
|
Re-uses this Nixpkgs evaluation instead of `arion-pkgs.nix`.
|
||||||
|
|
||||||
|
Returns the module system's `config` and `options` variables.
|
||||||
|
*/
|
||||||
|
eval = args@{...}:
|
||||||
|
import (evalSrc' + "/src/nix/eval-composition.nix")
|
||||||
|
({ inherit pkgs; } // args);
|
||||||
|
|
||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
build = args@{...}:
|
||||||
|
let composition = eval args;
|
||||||
|
in composition.config.out.dockerComposeYaml;
|
||||||
|
|
||||||
|
in arion
|
118
nixos-module.nix
Normal file
118
nixos-module.nix
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
{ 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;
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
6
repl
Executable file
6
repl
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell -i bash
|
||||||
|
|
||||||
|
# A Haskell REPL for hacking on the Haskell code
|
||||||
|
|
||||||
|
cabal new-repl --write-ghc-environment-files=never
|
15
run-arion
Executable file
15
run-arion
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env nix-shell
|
||||||
|
#!nix-shell -i bash
|
||||||
|
#!nix-shell ./shell.nix
|
||||||
|
|
||||||
|
# For quick manual testing of a hacked arion
|
||||||
|
|
||||||
|
# NB: Only works inside the project directory
|
||||||
|
|
||||||
|
cabal \
|
||||||
|
new-run \
|
||||||
|
--write-ghc-environment-files=never \
|
||||||
|
:pkg:arion-compose:exe:arion \
|
||||||
|
-- \
|
||||||
|
"$@" \
|
||||||
|
;
|
15
run-arion-quick
Executable file
15
run-arion-quick
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
projectRoot="$(dirname ${BASH_SOURCE[0]})"
|
||||||
|
resultLink="$projectRoot/result-run-arion-quick"
|
||||||
|
|
||||||
|
[[ -e "$resultLink" ]] || {
|
||||||
|
echo 1>&2 "You don't have a prebuilt arion yet; building it."
|
||||||
|
nix-build "$projectRoot" -A arion --out-link "$resultLink"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo 1>&2 "Note that you will need to rm '$resultLink' to rebuild the arion executable when needed."
|
||||||
|
|
||||||
|
export arion_compose_datadir="$projectRoot/src"
|
||||||
|
|
||||||
|
exec "$resultLink/bin/arion" "$@"
|
6
run-arion-via-nix
Executable file
6
run-arion-via-nix
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# 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 "$@"
|
1
shell.nix
Normal file
1
shell.nix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
(builtins.getFlake ("git+file://" + toString ./.)).devShells.${builtins.currentSystem}.default
|
308
src/arion
308
src/arion
|
@ -1,308 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export PATH="@path@:$PATH"
|
|
||||||
|
|
||||||
nix_dir="@nix_dir@"
|
|
||||||
docker_compose_args=()
|
|
||||||
files=()
|
|
||||||
command="docker-compose"
|
|
||||||
pkgs_argument="./arion-pkgs.nix"
|
|
||||||
|
|
||||||
debug() {
|
|
||||||
# echo "$@"
|
|
||||||
:
|
|
||||||
}
|
|
||||||
|
|
||||||
while test $# != 0; do
|
|
||||||
case "$1" in
|
|
||||||
-f|--file)
|
|
||||||
shift
|
|
||||||
files+=("$1")
|
|
||||||
;;
|
|
||||||
-f*)
|
|
||||||
files+=("${1/#-f/}")
|
|
||||||
;;
|
|
||||||
--file=*)
|
|
||||||
files+=("${1/#--file=}")
|
|
||||||
;;
|
|
||||||
--pkgs)
|
|
||||||
shift
|
|
||||||
pkgs_argument="$1"
|
|
||||||
;;
|
|
||||||
-h|--help|help)
|
|
||||||
command="help"
|
|
||||||
shift
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
cat)
|
|
||||||
command="$1"
|
|
||||||
shift
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
repl)
|
|
||||||
command="$1"
|
|
||||||
shift
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
exec)
|
|
||||||
command="$1"
|
|
||||||
shift
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
docker-compose)
|
|
||||||
command="docker-compose"
|
|
||||||
shift
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
while test $# != 0; do
|
|
||||||
docker_compose_args+=("$1")
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
case "$command" in
|
|
||||||
help)
|
|
||||||
cat <<EOF
|
|
||||||
|
|
||||||
Arion wraps your system's docker-compose, providing a NixOps-like
|
|
||||||
experience for simple container deployments.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
arion up|logs|... - execute docker-compose commands
|
|
||||||
arion cat - display raw docker-compose.yaml
|
|
||||||
arion config - validate and display the config file
|
|
||||||
arion repl - explore the config interactively
|
|
||||||
arion help
|
|
||||||
arion docker-compose help
|
|
||||||
arion docker-compose help up|logs|...
|
|
||||||
|
|
||||||
Top-level arion options
|
|
||||||
|
|
||||||
These must be provided before the command.
|
|
||||||
|
|
||||||
--file FILE Use FILE instead of the default ./arion-compose.nix
|
|
||||||
Can be specified multiple times for a merged configuration.
|
|
||||||
--pkgs EXPR Use EXPR instead of ./arion-pkgs.nix to get the
|
|
||||||
Nixpkgs attrset used for bootstrapping and evaluating
|
|
||||||
the configuration.
|
|
||||||
|
|
||||||
EOF
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [[ ${#files[@]} == 0 ]]; then
|
|
||||||
files=("./arion-compose.nix")
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
debug docker_compose_args: "${docker_compose_args[@]}"
|
|
||||||
debug files: "${files[@]}"
|
|
||||||
|
|
||||||
docker_compose_yaml=.tmp-nix-docker-compose-$$-$RANDOM.yaml
|
|
||||||
cleanup() {
|
|
||||||
rm -f $docker_compose_yaml
|
|
||||||
}
|
|
||||||
trap cleanup EXIT
|
|
||||||
|
|
||||||
modules="["
|
|
||||||
|
|
||||||
for file in "${files[@]}"; do
|
|
||||||
case "$file" in
|
|
||||||
/*)
|
|
||||||
modules="$modules (/. + $(printf '"%q"' "$file"))"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
modules="$modules (./. + $(printf '"/%q"' "$file"))"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
modules="$modules ]"
|
|
||||||
|
|
||||||
debug modules: "$modules"
|
|
||||||
|
|
||||||
old_IFS="$IFS"
|
|
||||||
IFS=""
|
|
||||||
args=(
|
|
||||||
)
|
|
||||||
IFS="$old_IFS"
|
|
||||||
for arg in "${args[@]}"; do
|
|
||||||
echo "arg: $arg"
|
|
||||||
done
|
|
||||||
|
|
||||||
do_eval() {
|
|
||||||
echo 1>&2 "Evaluating configuration..."
|
|
||||||
# read-write-mode is required for import from derivation
|
|
||||||
nix-instantiate \
|
|
||||||
"$nix_dir/eval-composition.nix" \
|
|
||||||
--eval \
|
|
||||||
--read-write-mode \
|
|
||||||
--json \
|
|
||||||
--argstr uid "$UID" \
|
|
||||||
--arg modules "$modules" \
|
|
||||||
--arg pkgs "$pkgs_argument" \
|
|
||||||
--show-trace \
|
|
||||||
--attr 'config.build.dockerComposeYamlText' \
|
|
||||||
| jq -r . >$docker_compose_yaml;
|
|
||||||
}
|
|
||||||
|
|
||||||
do_build() {
|
|
||||||
echo 1>&2 "Building configuration..."
|
|
||||||
nix-build \
|
|
||||||
"$nix_dir/eval-composition.nix" \
|
|
||||||
--out-link $docker_compose_yaml \
|
|
||||||
--argstr uid "$UID" \
|
|
||||||
--arg modules "$modules" \
|
|
||||||
--arg pkgs "$pkgs_argument" \
|
|
||||||
--show-trace \
|
|
||||||
--attr 'config.build.dockerComposeYaml' \
|
|
||||||
>/dev/null ;
|
|
||||||
|
|
||||||
echo 1>&2 "Ensuring required images are loaded..."
|
|
||||||
jq -r <"$docker_compose_yaml" \
|
|
||||||
'.["x-arion"].images | map(" - " + .imageName + ":" + .imageTag) | join("\n")'
|
|
||||||
eval "$(
|
|
||||||
jq -r '.["docker-compose"]["x-arion"].images as $images
|
|
||||||
| .["existing-images"] as $loaded
|
|
||||||
| $images
|
|
||||||
| map(
|
|
||||||
if $loaded[.imageName + ":" + .imageTag]
|
|
||||||
then ""
|
|
||||||
else "docker load <" + .image + ";" end
|
|
||||||
)
|
|
||||||
| join("\n")
|
|
||||||
' <<EOF
|
|
||||||
{
|
|
||||||
"docker-compose": $(cat $docker_compose_yaml),
|
|
||||||
"existing-images": {
|
|
||||||
$(docker images \
|
|
||||||
--filter "dangling=false" \
|
|
||||||
--format '"{{.Repository}}:{{.Tag}}": true,')
|
|
||||||
"": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
)"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
do_repl() {
|
|
||||||
# nix repl doesn't autocall its <FILES> arguments
|
|
||||||
# so we improvise. We need a file in this directory
|
|
||||||
# to make sure that all paths are as expected :(
|
|
||||||
trap do_repl_cleanup EXIT;
|
|
||||||
|
|
||||||
REPL_TMP=.tmp-repl-$$-$RANDOM
|
|
||||||
cat <<EOF
|
|
||||||
Launch a repl for you, using a temporary file: $REPL_TMP.
|
|
||||||
|
|
||||||
This loads the configuration from the modules
|
|
||||||
${files[*]}
|
|
||||||
|
|
||||||
To get started:
|
|
||||||
|
|
||||||
To see deployment-wide configuration
|
|
||||||
type config. and hit TAB
|
|
||||||
To see the services
|
|
||||||
type config.docker-compose.evaluatedServices TAB or ENTER
|
|
||||||
To bring the top-level Nixpkgs attributes into scope
|
|
||||||
type :a (config._module.args.pkgs) // { inherit config; }
|
|
||||||
|
|
||||||
EOF
|
|
||||||
cat >"$REPL_TMP" <<EOF
|
|
||||||
import $nix_dir/eval-composition.nix {
|
|
||||||
uid = "$UID";
|
|
||||||
modules = $modules;
|
|
||||||
pkgs = $pkgs_argument;
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
nix repl \
|
|
||||||
"$REPL_TMP" \
|
|
||||||
;
|
|
||||||
}
|
|
||||||
do_repl_cleanup() {
|
|
||||||
rm -f $REPL_TMP
|
|
||||||
}
|
|
||||||
|
|
||||||
run_exec() {
|
|
||||||
case "${#docker_compose_args[@]}" in
|
|
||||||
0)
|
|
||||||
echo "As an argument to exec, please specify a service"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
1)
|
|
||||||
case "${docker_compose_args[0]}" in
|
|
||||||
-*|--*)
|
|
||||||
echo "As an argument to exec, please specify a service"
|
|
||||||
echo "Note that executing the default command currently does not support"
|
|
||||||
echo "docker-compose options."
|
|
||||||
# This requires parsing the options, in order to figure out
|
|
||||||
# which service to invoke.
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
serviceName="${docker_compose_args[0]}"
|
|
||||||
do_eval
|
|
||||||
default_args=()
|
|
||||||
while read arg; do
|
|
||||||
default_args+=("$arg")
|
|
||||||
done < <(
|
|
||||||
jq < "$docker_compose_yaml" \
|
|
||||||
--arg serviceName "$serviceName" \
|
|
||||||
-r \
|
|
||||||
'.["x-arion"].serviceInfo[$serviceName].defaultExec | tostream | .[1] | select(.)'
|
|
||||||
)
|
|
||||||
docker-compose -f $docker_compose_yaml exec "$serviceName" "${default_args[@]}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
do_eval
|
|
||||||
docker-compose -f $docker_compose_yaml exec "${docker_compose_args[@]}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
case "$command" in
|
|
||||||
cat)
|
|
||||||
do_eval
|
|
||||||
jq . < "$docker_compose_yaml"
|
|
||||||
;;
|
|
||||||
repl)
|
|
||||||
do_repl
|
|
||||||
;;
|
|
||||||
exec)
|
|
||||||
run_exec "$@"
|
|
||||||
;;
|
|
||||||
docker-compose)
|
|
||||||
if [[ ${#docker_compose_args[@]} != 0
|
|
||||||
&& ${docker_compose_args[0]} != "help"
|
|
||||||
&& ${docker_compose_args[0]} != "version"
|
|
||||||
]]; then
|
|
||||||
case "${docker_compose_args[0]}" in
|
|
||||||
help|version)
|
|
||||||
:
|
|
||||||
;;
|
|
||||||
config|down|events|exec|images|kill|logs|pause|port|ps|rm|stop|top|unpause)
|
|
||||||
do_eval
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
do_build
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
docker-compose -f $docker_compose_yaml "${docker_compose_args[@]}"
|
|
||||||
;;
|
|
||||||
esac
|
|
|
@ -1,3 +1,5 @@
|
||||||
FROM scratch
|
FROM scratch
|
||||||
COPY passwd /etc/passwd
|
# scratch itself can't be run.
|
||||||
ADD tarball.tar.gz /
|
|
||||||
|
# This seems like a no-op:
|
||||||
|
CMD []
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
root:x:0:0:System administrator:/root:/bin/sh
|
|
||||||
nobody:x:65534:65534:Unprivileged account (don't use!):/var/empty:/run/current-system/sw/bin/nologin
|
|
|
@ -1 +0,0 @@
|
||||||
/run/system/bin/sh
|
|
|
@ -1 +0,0 @@
|
||||||
/run/system/usr/bin/env
|
|
324
src/haskell/exe/Main.hs
Normal file
324
src/haskell/exe/Main.hs
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
{-# LANGUAGE ApplicativeDo #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
import Protolude hiding (Down, option)
|
||||||
|
|
||||||
|
import Arion.Nix
|
||||||
|
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 Options.Applicative
|
||||||
|
import Control.Monad.Fail
|
||||||
|
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
|
|
||||||
|
import Data.Aeson(Value)
|
||||||
|
|
||||||
|
import System.Posix.User (getRealUserID)
|
||||||
|
|
||||||
|
data CommonOptions =
|
||||||
|
CommonOptions
|
||||||
|
{ files :: NonEmpty FilePath
|
||||||
|
, pkgs :: Text
|
||||||
|
, nixArgs :: [Text]
|
||||||
|
, prebuiltComposeFile :: Maybe FilePath
|
||||||
|
, noAnsi :: Bool
|
||||||
|
, compatibility :: Bool
|
||||||
|
, logLevel :: Maybe Text
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
newtype DockerComposeArgs =
|
||||||
|
DockerComposeArgs { unDockerComposeArgs :: [Text] }
|
||||||
|
|
||||||
|
ensureConfigFile :: [FilePath] -> NonEmpty FilePath
|
||||||
|
ensureConfigFile [] = "./arion-compose.nix" :| []
|
||||||
|
ensureConfigFile (x:xs) = x :| xs
|
||||||
|
|
||||||
|
parseOptions :: Parser CommonOptions
|
||||||
|
parseOptions = do
|
||||||
|
files <-
|
||||||
|
ensureConfigFile <$>
|
||||||
|
many (strOption
|
||||||
|
( short 'f'
|
||||||
|
<> long "file"
|
||||||
|
<> metavar "FILE"
|
||||||
|
<> help "Use FILE instead of the default ./arion-compose.nix. \
|
||||||
|
\Can be specified multiple times for a merged configuration" ))
|
||||||
|
pkgs <- T.pack <$> strOption
|
||||||
|
( short 'p'
|
||||||
|
<> long "pkgs"
|
||||||
|
<> metavar "EXPR"
|
||||||
|
<> showDefault
|
||||||
|
<> value "./arion-pkgs.nix"
|
||||||
|
<> 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.")
|
||||||
|
-- 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
|
||||||
|
( long "prebuilt-file"
|
||||||
|
<> metavar "JSONFILE"
|
||||||
|
<> help "Do not evaluate and use the prebuilt JSONFILE instead. Causes other evaluation-related options to be ignored." )
|
||||||
|
noAnsi <- flag False True (long "no-ansi"
|
||||||
|
<> help "Avoid ANSI control sequences")
|
||||||
|
compatibility <- flag False True (long "no-ansi"
|
||||||
|
<> help "If set, Docker Compose will attempt to convert deploy keys in v3 files to their non-Swarm equivalent")
|
||||||
|
logLevel <- optional $ fmap T.pack $ strOption (long "log-level" <> metavar "LEVEL" <> help "Set log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
|
||||||
|
pure $
|
||||||
|
let nixArgs = userNixArgs <|> "--show-trace" <$ guard showTrace
|
||||||
|
in CommonOptions{..}
|
||||||
|
|
||||||
|
textArgument :: Mod ArgumentFields [Char] -> Parser Text
|
||||||
|
textArgument = fmap T.pack . strArgument
|
||||||
|
|
||||||
|
parseCommand :: Parser (CommonOptions -> IO ())
|
||||||
|
parseCommand =
|
||||||
|
hsubparser
|
||||||
|
( command "cat" (info (pure runCat) (progDesc "Spit out the docker compose file as JSON" <> fullDesc))
|
||||||
|
<> command "repl" (info (pure runRepl) (progDesc "Start a nix repl for the whole composition" <> fullDesc))
|
||||||
|
<> command "exec" (info (parseExecCommand) (progDesc "Execute a command in a running container" <> fullDesc))
|
||||||
|
)
|
||||||
|
<|>
|
||||||
|
hsubparser
|
||||||
|
( commandDC runBuildAndDC "build" "Build or rebuild services"
|
||||||
|
<> commandDC runBuildAndDC "bundle" "Generate a Docker bundle from the Compose file"
|
||||||
|
<> commandDC runEvalAndDC "config" "Validate and view the Compose file"
|
||||||
|
<> commandDC runBuildAndDC "create" "Create services"
|
||||||
|
<> commandDC runEvalAndDC "down" "Stop and remove containers, networks, images, and volumes"
|
||||||
|
<> commandDC runEvalAndDC "events" "Receive real time events from containers"
|
||||||
|
<> commandDC runDC "help" "Get help on a command"
|
||||||
|
<> commandDC runEvalAndDC "images" "List images"
|
||||||
|
<> commandDC runEvalAndDC "kill" "Kill containers"
|
||||||
|
<> commandDC runEvalAndDC "logs" "View output from containers"
|
||||||
|
<> commandDC runEvalAndDC "pause" "Pause services"
|
||||||
|
<> commandDC runEvalAndDC "port" "Print the public port for a port binding"
|
||||||
|
<> commandDC runEvalAndDC "ps" "List containers"
|
||||||
|
<> commandDC runBuildAndDC "pull" "Pull service images"
|
||||||
|
<> commandDC runBuildAndDC "push" "Push service images"
|
||||||
|
<> commandDC runBuildAndDC "restart" "Restart services"
|
||||||
|
<> commandDC runEvalAndDC "rm" "Remove stopped containers"
|
||||||
|
<> commandDC runBuildAndDC "run" "Run a one-off command"
|
||||||
|
<> commandDC runBuildAndDC "scale" "Set number of containers for a service"
|
||||||
|
<> commandDC runBuildAndDC "start" "Start services"
|
||||||
|
<> commandDC runEvalAndDC "stop" "Stop services"
|
||||||
|
<> commandDC runEvalAndDC "top" "Display the running processes"
|
||||||
|
<> commandDC runEvalAndDC "unpause" "Unpause services"
|
||||||
|
<> commandDC runBuildAndDC "up" "Create and start containers"
|
||||||
|
<> commandDC runDC "version" "Show the Docker-Compose version information"
|
||||||
|
|
||||||
|
<> metavar "DOCKER-COMPOSE-COMMAND"
|
||||||
|
<> commandGroup "Docker Compose Commands:"
|
||||||
|
)
|
||||||
|
|
||||||
|
parseAll :: Parser (IO ())
|
||||||
|
parseAll =
|
||||||
|
flip ($) <$> parseOptions <*> parseCommand
|
||||||
|
|
||||||
|
parseDockerComposeArgs :: Parser DockerComposeArgs
|
||||||
|
parseDockerComposeArgs =
|
||||||
|
DockerComposeArgs <$>
|
||||||
|
many (argument (T.pack <$> str) (metavar "DOCKER-COMPOSE ARGS..."))
|
||||||
|
|
||||||
|
commandDC
|
||||||
|
:: (Text -> DockerComposeArgs -> CommonOptions -> IO ())
|
||||||
|
-> Text
|
||||||
|
-> Text
|
||||||
|
-> Mod CommandFields (CommonOptions -> IO ())
|
||||||
|
commandDC run cmdStr helpText =
|
||||||
|
command
|
||||||
|
(T.unpack cmdStr)
|
||||||
|
(info
|
||||||
|
(run cmdStr <$> parseDockerComposeArgs)
|
||||||
|
(progDesc (T.unpack helpText) <> fullDesc <> forwardOptions))
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
runDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
|
||||||
|
runDC cmd (DockerComposeArgs args) _opts = do
|
||||||
|
DockerCompose.run DockerCompose.Args
|
||||||
|
{ files = []
|
||||||
|
, otherArgs = [cmd] ++ args
|
||||||
|
}
|
||||||
|
|
||||||
|
runBuildAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
|
||||||
|
runBuildAndDC cmd dopts opts = do
|
||||||
|
withBuiltComposeFile opts $ callDC cmd dopts opts True
|
||||||
|
|
||||||
|
runEvalAndDC :: Text -> DockerComposeArgs -> CommonOptions -> IO ()
|
||||||
|
runEvalAndDC cmd dopts opts = do
|
||||||
|
withComposeFile opts $ callDC cmd dopts opts False
|
||||||
|
|
||||||
|
callDC :: Text -> DockerComposeArgs -> CommonOptions -> Bool -> FilePath -> IO ()
|
||||||
|
callDC cmd dopts opts shouldLoadImages path = do
|
||||||
|
extendedInfo <- loadExtendedInfoFromPath path
|
||||||
|
when shouldLoadImages $ loadImages (images extendedInfo)
|
||||||
|
let firstOpts = projectArgs extendedInfo <> commonArgs opts
|
||||||
|
DockerCompose.run DockerCompose.Args
|
||||||
|
{ files = [path]
|
||||||
|
, otherArgs = firstOpts ++ [cmd] ++ unDockerComposeArgs dopts
|
||||||
|
}
|
||||||
|
|
||||||
|
projectArgs :: ExtendedInfo -> [Text]
|
||||||
|
projectArgs extendedInfo =
|
||||||
|
do
|
||||||
|
n <- toList (projectName extendedInfo)
|
||||||
|
["--project-name", n]
|
||||||
|
|
||||||
|
commonArgs :: CommonOptions -> [Text]
|
||||||
|
commonArgs opts = do
|
||||||
|
guard (noAnsi opts)
|
||||||
|
["--no-ansi"]
|
||||||
|
<> do
|
||||||
|
guard (compatibility opts)
|
||||||
|
["--compatibility"]
|
||||||
|
<> do
|
||||||
|
l <- toList (logLevel opts)
|
||||||
|
["--log-level", l]
|
||||||
|
|
||||||
|
withBuiltComposeFile :: CommonOptions -> (FilePath -> IO r) -> IO r
|
||||||
|
withBuiltComposeFile opts cont = case prebuiltComposeFile opts of
|
||||||
|
Just prebuilt -> do
|
||||||
|
cont prebuilt
|
||||||
|
Nothing -> do
|
||||||
|
args <- defaultEvaluationArgs opts
|
||||||
|
Arion.Nix.withBuiltComposition args cont
|
||||||
|
|
||||||
|
withComposeFile :: CommonOptions -> (FilePath -> IO r) -> IO r
|
||||||
|
withComposeFile opts cont = case prebuiltComposeFile opts of
|
||||||
|
Just prebuilt -> do
|
||||||
|
cont prebuilt
|
||||||
|
Nothing -> do
|
||||||
|
args <- defaultEvaluationArgs opts
|
||||||
|
Arion.Nix.withEvaluatedComposition args cont
|
||||||
|
|
||||||
|
getComposeValue :: CommonOptions -> IO Value
|
||||||
|
getComposeValue opts = case prebuiltComposeFile opts of
|
||||||
|
Just prebuilt -> do
|
||||||
|
decodeFile prebuilt
|
||||||
|
Nothing -> do
|
||||||
|
args <- defaultEvaluationArgs opts
|
||||||
|
Arion.Nix.evaluateComposition args
|
||||||
|
|
||||||
|
defaultEvaluationArgs :: CommonOptions -> IO EvaluationArgs
|
||||||
|
defaultEvaluationArgs co = do
|
||||||
|
uid <- getRealUserID
|
||||||
|
pure EvaluationArgs
|
||||||
|
{ evalUid = fromIntegral uid
|
||||||
|
, evalModules = files co
|
||||||
|
, evalPkgs = pkgs co
|
||||||
|
, evalWorkDir = Nothing
|
||||||
|
, evalMode = ReadWrite
|
||||||
|
, evalUserArgs = nixArgs co
|
||||||
|
}
|
||||||
|
|
||||||
|
runCat :: CommonOptions -> IO ()
|
||||||
|
runCat co = do
|
||||||
|
v <- getComposeValue co
|
||||||
|
T.hPutStrLn stdout (pretty v)
|
||||||
|
|
||||||
|
runRepl :: CommonOptions -> IO ()
|
||||||
|
runRepl co = do
|
||||||
|
putErrText
|
||||||
|
"Launching a repl for you. To get started:\n\
|
||||||
|
\\n\
|
||||||
|
\To see deployment-wide configuration\n\
|
||||||
|
\ type config. and use tab completion\n\
|
||||||
|
\To bring the top-level Nixpkgs attributes into scope\n\
|
||||||
|
\ type :a (config._module.args.pkgs) // { inherit config; }\n\
|
||||||
|
\"
|
||||||
|
Arion.Nix.replForComposition =<< defaultEvaluationArgs co
|
||||||
|
|
||||||
|
detachFlag :: Parser Bool
|
||||||
|
detachFlag = flag False True (long "detach" <> short 'd' <> help "Detached mode: Run command in the background.")
|
||||||
|
|
||||||
|
privilegedFlag :: Parser Bool
|
||||||
|
privilegedFlag = flag False True (long "privileged" <> help "Give extended privileges to the process.")
|
||||||
|
|
||||||
|
userOption :: Parser Text
|
||||||
|
userOption = strOption (long "user" <> short 'u' <> help "Run the command as this user.")
|
||||||
|
|
||||||
|
noTTYFlag :: Parser Bool
|
||||||
|
noTTYFlag = flag False True (short 'T' <> help "Disable pseudo-tty allocation. By default `exec` allocates a TTY.")
|
||||||
|
|
||||||
|
indexOption :: Parser Int
|
||||||
|
indexOption = option
|
||||||
|
(auto >>= \i -> i <$ unless (i >= 1) (fail "container index must be >= 1"))
|
||||||
|
(long "index" <> value 1 <> help "Index of the container if there are multiple instances of a service.")
|
||||||
|
|
||||||
|
envOption :: Parser (Text, Text)
|
||||||
|
envOption = option (auto >>= spl) (long "env" <> short 'e' <> help "Set environment variables (can be used multiple times, not supported in Docker API < 1.25)")
|
||||||
|
where spl s = case T.break (== '=') s of
|
||||||
|
(_, "") -> fail "--env parameter needs to combine key and value with = sign"
|
||||||
|
(k, ev) -> pure (k, T.drop 1 ev)
|
||||||
|
|
||||||
|
workdirOption :: Parser Text
|
||||||
|
workdirOption = strOption (long "workdir" <> short 'w' <> metavar "DIR" <> help "Working directory in which to start the command in the container.")
|
||||||
|
|
||||||
|
parseExecCommand :: Parser (CommonOptions -> IO ())
|
||||||
|
parseExecCommand = runExec
|
||||||
|
<$> detachFlag
|
||||||
|
<*> privilegedFlag
|
||||||
|
<*> optional userOption
|
||||||
|
<*> noTTYFlag
|
||||||
|
<*> indexOption
|
||||||
|
<*> many envOption
|
||||||
|
<*> optional workdirOption
|
||||||
|
<*> textArgument (metavar "SERVICE")
|
||||||
|
<*> orEmpty' (
|
||||||
|
(:) <$> argument (T.pack <$> str) (metavar "COMMAND")
|
||||||
|
<*> many (argument (T.pack <$> str) (metavar "ARG"))
|
||||||
|
)
|
||||||
|
|
||||||
|
orEmpty' :: (Alternative f, Monoid a) => f a -> f a
|
||||||
|
orEmpty' m = fromMaybe mempty <$> optional m
|
||||||
|
|
||||||
|
runExec :: Bool -> Bool -> Maybe Text -> Bool -> Int -> [(Text, Text)] -> Maybe Text -> Text -> [Text] -> CommonOptions -> IO ()
|
||||||
|
runExec detach privileged user noTTY index envs workDir service commandAndArgs opts =
|
||||||
|
withComposeFile opts $ \path -> do
|
||||||
|
extendedInfo <- loadExtendedInfoFromPath path
|
||||||
|
commandAndArgs'' <- case commandAndArgs of
|
||||||
|
[] -> do
|
||||||
|
cmd <- getDefaultExec path service
|
||||||
|
case cmd of
|
||||||
|
[] -> do
|
||||||
|
putErrText "You must provide a command via service.defaultExec or on the command line."
|
||||||
|
exitFailure
|
||||||
|
_ ->
|
||||||
|
pure cmd
|
||||||
|
x -> pure x
|
||||||
|
let commandAndArgs' = case commandAndArgs'' of
|
||||||
|
[] -> ["/bin/sh"]
|
||||||
|
x -> x
|
||||||
|
|
||||||
|
let args = concat
|
||||||
|
[ ["exec"]
|
||||||
|
, ("--detach" <$ guard detach :: [Text])
|
||||||
|
, "--privileged" <$ guard privileged
|
||||||
|
, "-T" <$ guard noTTY
|
||||||
|
, (\(k, v) -> ["--env", k <> "=" <> v]) =<< envs
|
||||||
|
, join $ toList (user <&> \u -> ["--user", u])
|
||||||
|
, ["--index", show index]
|
||||||
|
, join $ toList (workDir <&> \w -> ["--workdir", w])
|
||||||
|
, [service]
|
||||||
|
, commandAndArgs'
|
||||||
|
]
|
||||||
|
DockerCompose.run DockerCompose.Args
|
||||||
|
{ files = [path]
|
||||||
|
, otherArgs = projectArgs extendedInfo <> commonArgs opts <> args
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main =
|
||||||
|
(join . arionExecParser) (info (parseAll <**> helper) fullDesc)
|
||||||
|
where
|
||||||
|
arionExecParser = customExecParser (prefs showHelpOnEmpty)
|
||||||
|
|
27
src/haskell/lib/Arion/Aeson.hs
Normal file
27
src/haskell/lib/Arion/Aeson.hs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
module Arion.Aeson where
|
||||||
|
|
||||||
|
import Prelude ()
|
||||||
|
import Data.Aeson
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
import qualified Data.Text.Lazy as TL
|
||||||
|
import qualified Data.Text.Lazy.Builder as TB
|
||||||
|
import qualified Data.Aeson.Encode.Pretty
|
||||||
|
import Data.Aeson.Encode.Pretty ( defConfig
|
||||||
|
, confCompare
|
||||||
|
, confTrailingNewline
|
||||||
|
)
|
||||||
|
import Protolude
|
||||||
|
|
||||||
|
pretty :: ToJSON a => a -> Text
|
||||||
|
pretty =
|
||||||
|
TL.toStrict
|
||||||
|
. TB.toLazyText
|
||||||
|
. Data.Aeson.Encode.Pretty.encodePrettyToTextBuilder' config
|
||||||
|
where config = defConfig { confCompare = compare, confTrailingNewline = True }
|
||||||
|
|
||||||
|
decodeFile :: FromJSON a => FilePath -> IO a
|
||||||
|
decodeFile fp = do
|
||||||
|
b <- BL.readFile fp
|
||||||
|
case eitherDecode b of
|
||||||
|
Left e -> panic (toS e)
|
||||||
|
Right v -> pure v
|
30
src/haskell/lib/Arion/DockerCompose.hs
Normal file
30
src/haskell/lib/Arion/DockerCompose.hs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Arion.DockerCompose where
|
||||||
|
|
||||||
|
import Prelude ( )
|
||||||
|
import Protolude
|
||||||
|
import System.Process
|
||||||
|
|
||||||
|
data Args = Args
|
||||||
|
{ files :: [FilePath]
|
||||||
|
, otherArgs :: [Text]
|
||||||
|
}
|
||||||
|
|
||||||
|
run :: Args -> IO ()
|
||||||
|
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)
|
||||||
|
|
||||||
|
withCreateProcess procSpec $ \_in _out _err procHandle -> do
|
||||||
|
|
||||||
|
exitCode <- waitForProcess procHandle
|
||||||
|
|
||||||
|
case exitCode of
|
||||||
|
ExitSuccess -> pass
|
||||||
|
ExitFailure 1 -> exitFailure
|
||||||
|
ExitFailure {} -> do
|
||||||
|
throwIO $ FatalError $ "docker-compose failed with " <> show exitCode
|
37
src/haskell/lib/Arion/ExtendedInfo.hs
Normal file
37
src/haskell/lib/Arion/ExtendedInfo.hs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-
|
||||||
|
|
||||||
|
Parses the x-arion field in the generated compose file.
|
||||||
|
|
||||||
|
-}
|
||||||
|
module Arion.ExtendedInfo where
|
||||||
|
|
||||||
|
import Prelude()
|
||||||
|
import Protolude
|
||||||
|
import Data.Aeson as Aeson
|
||||||
|
import Arion.Aeson
|
||||||
|
import Control.Lens
|
||||||
|
import Data.Aeson.Lens
|
||||||
|
|
||||||
|
data Image = Image
|
||||||
|
{ image :: Maybe Text -- ^ image tar.gz file path
|
||||||
|
, imageExe :: Maybe Text -- ^ path to exe producing image tar
|
||||||
|
, imageName :: Text
|
||||||
|
, imageTag :: Text
|
||||||
|
} deriving (Eq, Show, Generic, Aeson.ToJSON, Aeson.FromJSON)
|
||||||
|
|
||||||
|
data ExtendedInfo = ExtendedInfo {
|
||||||
|
projectName :: Maybe Text,
|
||||||
|
images :: [Image]
|
||||||
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
loadExtendedInfoFromPath :: FilePath -> IO ExtendedInfo
|
||||||
|
loadExtendedInfoFromPath fp = do
|
||||||
|
v <- decodeFile fp
|
||||||
|
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
|
||||||
|
}
|
72
src/haskell/lib/Arion/Images.hs
Normal file
72
src/haskell/lib/Arion/Images.hs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Arion.Images
|
||||||
|
( loadImages
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Prelude()
|
||||||
|
import Protolude hiding (to)
|
||||||
|
|
||||||
|
import qualified System.Process as Process
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
|
import Arion.ExtendedInfo (Image(..))
|
||||||
|
|
||||||
|
type TaggedImage = Text
|
||||||
|
|
||||||
|
-- | Subject to change
|
||||||
|
loadImages :: [Image] -> IO ()
|
||||||
|
loadImages requestedImages = do
|
||||||
|
|
||||||
|
loaded <- getDockerImages
|
||||||
|
|
||||||
|
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
|
||||||
|
&& ("localhost/" <> imageName i <> ":" <> imageTag i) `notElem` loaded
|
||||||
|
|
||||||
|
traverse_ loadImage . filter isNew $ requestedImages
|
||||||
|
|
||||||
|
loadImage :: Image -> IO ()
|
||||||
|
loadImage Image { image = Just imgPath, imageName = name } =
|
||||||
|
withFile (toS imgPath) ReadMode $ \fileHandle -> do
|
||||||
|
let procSpec = (Process.proc "docker" [ "load" ]) {
|
||||||
|
Process.std_in = Process.UseHandle fileHandle
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
loadImage Image { imageExe = Just imgExe, imageName = name } = do
|
||||||
|
let loadSpec = (Process.proc "docker" [ "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 ->
|
||||||
|
withAsync (Process.waitForProcess loadProcHandle) $ \loadExitAsync ->
|
||||||
|
withAsync (Process.waitForProcess streamProcHandle) $ \streamExitAsync -> do
|
||||||
|
r <- waitEither loadExitAsync streamExitAsync
|
||||||
|
case r of
|
||||||
|
Right (ExitFailure code) -> panic $ "image producer for image " <> name <> " failed with exit code " <> show code <> " from executable " <> imgExe
|
||||||
|
Right ExitSuccess -> pass
|
||||||
|
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
|
||||||
|
_ -> pass
|
||||||
|
pass
|
||||||
|
|
||||||
|
loadImage 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}}" ]
|
||||||
|
map toS . T.lines . toS <$> Process.readCreateProcess procSpec ""
|
183
src/haskell/lib/Arion/Nix.hs
Normal file
183
src/haskell/lib/Arion/Nix.hs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Arion.Nix
|
||||||
|
( evaluateComposition
|
||||||
|
, withEvaluatedComposition
|
||||||
|
, buildComposition
|
||||||
|
, withBuiltComposition
|
||||||
|
, replForComposition
|
||||||
|
, EvaluationArgs(..)
|
||||||
|
, EvaluationMode(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Prelude ( )
|
||||||
|
import Protolude
|
||||||
|
import Arion.Aeson ( pretty )
|
||||||
|
import Data.Aeson
|
||||||
|
import qualified Data.String
|
||||||
|
import qualified System.Directory as Directory
|
||||||
|
import System.Process
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
import Paths_arion_compose
|
||||||
|
|
||||||
|
import qualified Data.Text.IO as T
|
||||||
|
|
||||||
|
import qualified Data.List.NonEmpty as NE
|
||||||
|
|
||||||
|
import Control.Arrow ( (>>>) )
|
||||||
|
import System.IO.Temp ( withTempFile )
|
||||||
|
import System.IO ( hClose )
|
||||||
|
|
||||||
|
data EvaluationMode =
|
||||||
|
ReadWrite | ReadOnly
|
||||||
|
|
||||||
|
data EvaluationArgs = EvaluationArgs
|
||||||
|
{ evalUid :: Int
|
||||||
|
, evalModules :: NonEmpty FilePath
|
||||||
|
, evalPkgs :: Text
|
||||||
|
, evalWorkDir :: Maybe FilePath
|
||||||
|
, evalMode :: EvaluationMode
|
||||||
|
, evalUserArgs :: [Text]
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluateComposition :: EvaluationArgs -> IO Value
|
||||||
|
evaluateComposition ea = do
|
||||||
|
evalComposition <- getEvalCompositionFile
|
||||||
|
let commandArgs =
|
||||||
|
[ "--eval"
|
||||||
|
, "--strict"
|
||||||
|
, "--json"
|
||||||
|
, "--attr"
|
||||||
|
, "config.out.dockerComposeYamlAttrs"
|
||||||
|
]
|
||||||
|
args =
|
||||||
|
[ evalComposition ]
|
||||||
|
++ commandArgs
|
||||||
|
++ modeArguments (evalMode ea)
|
||||||
|
++ argArgs ea
|
||||||
|
++ map toS (evalUserArgs ea)
|
||||||
|
procSpec = (proc "nix-instantiate" args)
|
||||||
|
{ cwd = evalWorkDir ea
|
||||||
|
, std_out = CreatePipe
|
||||||
|
}
|
||||||
|
|
||||||
|
withCreateProcess procSpec $ \_in outHM _err procHandle -> do
|
||||||
|
let outHandle = fromMaybe (panic "stdout missing") outHM
|
||||||
|
|
||||||
|
out <- BL.hGetContents outHandle
|
||||||
|
|
||||||
|
v <- Protolude.evaluate (eitherDecode out)
|
||||||
|
|
||||||
|
exitCode <- waitForProcess procHandle
|
||||||
|
|
||||||
|
case exitCode of
|
||||||
|
ExitSuccess -> pass
|
||||||
|
ExitFailure 1 -> exitFailure
|
||||||
|
ExitFailure {} -> do
|
||||||
|
throwIO $ FatalError $ "evaluation failed with " <> show exitCode
|
||||||
|
|
||||||
|
case v of
|
||||||
|
Right r -> pure r
|
||||||
|
Left e -> throwIO $ FatalError ("Couldn't parse nix-instantiate output" <> show e)
|
||||||
|
|
||||||
|
-- | Run with docker-compose.yaml tmpfile
|
||||||
|
withEvaluatedComposition :: EvaluationArgs -> (FilePath -> IO r) -> IO r
|
||||||
|
withEvaluatedComposition ea f = do
|
||||||
|
v <- evaluateComposition ea
|
||||||
|
withTempFile "." ".tmp-arion-docker-compose.yaml" $ \path yamlHandle -> do
|
||||||
|
T.hPutStrLn yamlHandle (pretty v)
|
||||||
|
hClose yamlHandle
|
||||||
|
f path
|
||||||
|
|
||||||
|
|
||||||
|
buildComposition :: FilePath -> EvaluationArgs -> IO ()
|
||||||
|
buildComposition outLink ea = do
|
||||||
|
evalComposition <- getEvalCompositionFile
|
||||||
|
let commandArgs =
|
||||||
|
[ "--attr"
|
||||||
|
, "config.out.dockerComposeYaml"
|
||||||
|
, "--out-link"
|
||||||
|
, outLink
|
||||||
|
]
|
||||||
|
args =
|
||||||
|
[ evalComposition ]
|
||||||
|
++ commandArgs
|
||||||
|
++ argArgs ea
|
||||||
|
++ map toS (evalUserArgs ea)
|
||||||
|
procSpec = (proc "nix-build" args) { cwd = evalWorkDir ea }
|
||||||
|
|
||||||
|
withCreateProcess procSpec $ \_in _out _err procHandle -> do
|
||||||
|
|
||||||
|
exitCode <- waitForProcess procHandle
|
||||||
|
|
||||||
|
case exitCode of
|
||||||
|
ExitSuccess -> pass
|
||||||
|
ExitFailure 1 -> exitFailure
|
||||||
|
ExitFailure {} -> do
|
||||||
|
throwIO $ FatalError $ "nix-build failed with " <> show exitCode
|
||||||
|
|
||||||
|
-- | Do something with a docker-compose.yaml.
|
||||||
|
withBuiltComposition :: EvaluationArgs -> (FilePath -> IO r) -> IO r
|
||||||
|
withBuiltComposition ea f = do
|
||||||
|
withTempFile "." ".tmp-arion-docker-compose.yaml" $ \path emptyYamlHandle -> do
|
||||||
|
hClose emptyYamlHandle
|
||||||
|
-- Known problem: kills atomicity of withTempFile; won't fix because we should manage gc roots,
|
||||||
|
-- impl of which will probably avoid this "problem". It seems unlikely to cause issues.
|
||||||
|
Directory.removeFile path
|
||||||
|
buildComposition path ea
|
||||||
|
f path
|
||||||
|
|
||||||
|
|
||||||
|
replForComposition :: EvaluationArgs -> IO ()
|
||||||
|
replForComposition ea = do
|
||||||
|
evalComposition <- getEvalCompositionFile
|
||||||
|
let args =
|
||||||
|
[ "repl", evalComposition ]
|
||||||
|
++ argArgs ea
|
||||||
|
++ map toS (evalUserArgs ea)
|
||||||
|
procSpec = (proc "nix" args) { cwd = evalWorkDir ea }
|
||||||
|
|
||||||
|
withCreateProcess procSpec $ \_in _out _err procHandle -> do
|
||||||
|
|
||||||
|
exitCode <- waitForProcess procHandle
|
||||||
|
|
||||||
|
case exitCode of
|
||||||
|
ExitSuccess -> pass
|
||||||
|
ExitFailure 1 -> exitFailure
|
||||||
|
ExitFailure {} -> do
|
||||||
|
throwIO $ FatalError $ "nix repl failed with " <> show exitCode
|
||||||
|
|
||||||
|
argArgs :: EvaluationArgs -> [[Char]]
|
||||||
|
argArgs ea =
|
||||||
|
[ "--argstr"
|
||||||
|
, "uid"
|
||||||
|
, show $ evalUid ea
|
||||||
|
, "--arg"
|
||||||
|
, "modules"
|
||||||
|
, modulesNixExpr $ evalModules ea
|
||||||
|
, "--arg"
|
||||||
|
, "pkgs"
|
||||||
|
, toS $ evalPkgs ea
|
||||||
|
]
|
||||||
|
|
||||||
|
getEvalCompositionFile :: IO FilePath
|
||||||
|
getEvalCompositionFile = getDataFileName "nix/eval-composition.nix"
|
||||||
|
|
||||||
|
modeArguments :: EvaluationMode -> [[Char]]
|
||||||
|
modeArguments ReadWrite = [ "--read-write-mode" ]
|
||||||
|
modeArguments ReadOnly = [ "--readonly-mode" ]
|
||||||
|
|
||||||
|
modulesNixExpr :: NonEmpty FilePath -> [Char]
|
||||||
|
modulesNixExpr =
|
||||||
|
NE.toList >>> fmap pathExpr >>> Data.String.unwords >>> wrapList
|
||||||
|
where
|
||||||
|
pathExpr :: FilePath -> [Char]
|
||||||
|
pathExpr path | isAbsolute path = "(/. + \"/${" <> toNixStringLiteral path <> "}\")"
|
||||||
|
| otherwise = "(./. + \"/${" <> toNixStringLiteral path <> "}\")"
|
||||||
|
|
||||||
|
isAbsolute ('/' : _) = True
|
||||||
|
isAbsolute _ = False
|
||||||
|
|
||||||
|
wrapList s = "[ " <> s <> " ]"
|
||||||
|
|
||||||
|
toNixStringLiteral :: [Char] -> [Char]
|
||||||
|
toNixStringLiteral = show -- FIXME: custom escaping including '$'
|
37
src/haskell/lib/Arion/Services.hs
Normal file
37
src/haskell/lib/Arion/Services.hs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE CPP #-}
|
||||||
|
module Arion.Services
|
||||||
|
( getDefaultExec
|
||||||
|
) where
|
||||||
|
|
||||||
|
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)
|
74
src/haskell/test/Arion/NixSpec.hs
Normal file
74
src/haskell/test/Arion/NixSpec.hs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
module Arion.NixSpec
|
||||||
|
( spec
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Protolude
|
||||||
|
import Test.Hspec
|
||||||
|
import qualified Data.List.NonEmpty as NEL
|
||||||
|
import Arion.Aeson
|
||||||
|
import Arion.Nix
|
||||||
|
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
|
||||||
|
|
||||||
|
censorPaths :: Text -> Text
|
||||||
|
censorPaths = censorImages . censorStorePaths
|
||||||
|
|
||||||
|
censorStorePaths :: Text -> Text
|
||||||
|
censorStorePaths x = case T.breakOn "/nix/store/" x of
|
||||||
|
(prefix, tl) | (tl :: Text) == "" -> prefix
|
||||||
|
(prefix, tl) -> prefix <> "<STOREPATH>" <> censorPaths
|
||||||
|
(T.dropWhile isNixNameChar $ T.drop (T.length "/nix/store/") tl)
|
||||||
|
|
||||||
|
-- Probably slow, due to not O(1) <>
|
||||||
|
censorImages :: Text -> Text
|
||||||
|
censorImages x = case T.break (\c -> c == ':' || c == '"') x of
|
||||||
|
(prefix, tl) | tl == "" -> prefix
|
||||||
|
(prefix, tl) | let imageId = T.take 33 (T.drop 1 tl)
|
||||||
|
, T.last imageId == '\"'
|
||||||
|
-- Approximation of nix hash validation
|
||||||
|
, T.all (\c -> (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) (T.take 32 imageId)
|
||||||
|
-> prefix <> T.take 1 tl <> "<HASH>" <> censorImages (T.drop 33 tl)
|
||||||
|
(prefix, tl) -> prefix <> T.take 1 tl <> censorImages (T.drop 1 tl)
|
||||||
|
|
||||||
|
|
||||||
|
-- | WARNING: THIS IS LIKELY WRONG: DON'T REUSE
|
||||||
|
isNixNameChar :: Char -> Bool
|
||||||
|
isNixNameChar c | c >= '0' && c <= '9' = True
|
||||||
|
isNixNameChar c | c >= 'a' && c <= 'z' = True
|
||||||
|
isNixNameChar c | c >= 'A' && c <= 'Z' = True
|
||||||
|
isNixNameChar c | c == '-' = True
|
||||||
|
isNixNameChar c | c == '.' = True
|
||||||
|
isNixNameChar c | c == '_' = True -- WRONG?
|
||||||
|
isNixNameChar _ = False -- WRONG?
|
12
src/haskell/test/Spec.hs
Normal file
12
src/haskell/test/Spec.hs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
module Spec
|
||||||
|
( spec
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Test.Hspec
|
||||||
|
import qualified Arion.NixSpec
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = do
|
||||||
|
describe "Arion.Nix" Arion.NixSpec.spec
|
||||||
|
|
10
src/haskell/test/TestMain.hs
Normal file
10
src/haskell/test/TestMain.hs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Prelude()
|
||||||
|
import Protolude
|
||||||
|
import Test.Hspec.Runner
|
||||||
|
import qualified Spec
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = hspecWith config Spec.spec
|
||||||
|
where config = defaultConfig { configColorMode = ColorAlways }
|
57
src/haskell/testdata/Arion/NixSpec/arion-compose.json
vendored
Normal file
57
src/haskell/testdata/Arion/NixSpec/arion-compose.json
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"networks": {
|
||||||
|
"default": {
|
||||||
|
"name": "unit-test-data"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"webserver": {
|
||||||
|
"command": [
|
||||||
|
"/usr/sbin/init"
|
||||||
|
],
|
||||||
|
"environment": {
|
||||||
|
"NIX_REMOTE": "",
|
||||||
|
"PATH": "/usr/bin:/run/current-system/sw/bin/",
|
||||||
|
"container": "docker"
|
||||||
|
},
|
||||||
|
"image": "localhost/webserver:<HASH>",
|
||||||
|
"ports": [
|
||||||
|
"8000:80"
|
||||||
|
],
|
||||||
|
"stop_signal": "SIGRTMIN+3",
|
||||||
|
"sysctls": {},
|
||||||
|
"tmpfs": [
|
||||||
|
"/run",
|
||||||
|
"/run/wrappers",
|
||||||
|
"/tmp:exec,mode=777"
|
||||||
|
],
|
||||||
|
"tty": true,
|
||||||
|
"volumes": [
|
||||||
|
"/sys/fs/cgroup:/sys/fs/cgroup:ro",
|
||||||
|
"/nix/store:/nix/store:ro"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "3.4",
|
||||||
|
"volumes": {},
|
||||||
|
"x-arion": {
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"imageExe": "<STOREPATH>",
|
||||||
|
"imageName": "localhost/webserver",
|
||||||
|
"imageTag": "<HASH>"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"project": {
|
||||||
|
"name": "unit-test-data"
|
||||||
|
},
|
||||||
|
"serviceInfo": {
|
||||||
|
"webserver": {
|
||||||
|
"defaultExec": [
|
||||||
|
"/run/current-system/sw/bin/bash",
|
||||||
|
"-l"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/haskell/testdata/Arion/NixSpec/arion-compose.nix
vendored
Normal file
13
src/haskell/testdata/Arion/NixSpec/arion-compose.nix
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
project.name = "unit-test-data";
|
||||||
|
services.webserver = { pkgs, ... }: {
|
||||||
|
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";
|
||||||
|
service.useHostStore = true;
|
||||||
|
service.ports = [
|
||||||
|
"8000:80" # host:container
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
41
src/haskell/testdata/Arion/NixSpec/arion-context-compose.json
vendored
Normal file
41
src/haskell/testdata/Arion/NixSpec/arion-context-compose.json
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/haskell/testdata/Arion/NixSpec/arion-context-compose.nix
vendored
Normal file
9
src/haskell/testdata/Arion/NixSpec/arion-context-compose.nix
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
project.name = "unit-test-data";
|
||||||
|
services.webserver.service = {
|
||||||
|
build.context = "${./build-context}";
|
||||||
|
ports = [
|
||||||
|
"8080:80"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
4
src/haskell/testdata/Arion/NixSpec/build-context/Dockerfile
vendored
Normal file
4
src/haskell/testdata/Arion/NixSpec/build-context/Dockerfile
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
FROM nginx
|
||||||
|
|
||||||
|
RUN echo this is a dockerfile to be built
|
||||||
|
|
45
src/haskell/testdata/docker-compose-example.json
vendored
Normal file
45
src/haskell/testdata/docker-compose-example.json
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"services": {
|
||||||
|
"webserver": {
|
||||||
|
"environment": {
|
||||||
|
"container": "docker"
|
||||||
|
},
|
||||||
|
"image": "webserver:xr4ljmz3qfcwlq9rl4mr4qdrzw93rl70",
|
||||||
|
"ports": [
|
||||||
|
"8000:80"
|
||||||
|
],
|
||||||
|
"stop_signal": "SIGRTMIN+3",
|
||||||
|
"sysctls": {},
|
||||||
|
"tmpfs": [
|
||||||
|
"/run",
|
||||||
|
"/run/wrappers",
|
||||||
|
"/tmp:exec,mode=777"
|
||||||
|
],
|
||||||
|
"tty": true,
|
||||||
|
"volumes": [
|
||||||
|
"/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"version": "3.4",
|
||||||
|
"x-arion": {
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"image": "/nix/store/xr4ljmz3qfcwlq9rl4mr4qdrzw93rl70-docker-image-webserver.tar.gz",
|
||||||
|
"imageName": "webserver",
|
||||||
|
"imageTag": "xr4ljmz3qfcwlq9rl4mr4qdrzw93rl70"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"project": {
|
||||||
|
"name": null
|
||||||
|
},
|
||||||
|
"serviceInfo": {
|
||||||
|
"webserver": {
|
||||||
|
"defaultExec": [
|
||||||
|
"/run/current-system/sw/bin/bash",
|
||||||
|
"-l"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,26 +12,26 @@ let
|
||||||
inherit (pkgs) lib;
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
composition = lib.evalModules {
|
composition = lib.evalModules {
|
||||||
check = true;
|
|
||||||
modules = builtinModules ++ modules;
|
modules = builtinModules ++ modules;
|
||||||
};
|
};
|
||||||
|
|
||||||
builtinModules = [
|
builtinModules = [
|
||||||
argsModule
|
argsModule
|
||||||
./modules/composition/docker-compose.nix
|
] ++ import ./modules.nix;
|
||||||
./modules/composition/host-environment.nix
|
|
||||||
./modules/composition/images.nix
|
|
||||||
./modules/composition/service-info.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
argsModule = {
|
argsModule = {
|
||||||
_file = ./eval-composition.nix;
|
_file = ./eval-composition.nix;
|
||||||
key = ./eval-composition.nix;
|
key = ./eval-composition.nix;
|
||||||
config._module.args.pkgs = lib.mkIf (pkgs != null) (lib.mkForce pkgs);
|
config._module.args.pkgs = lib.mkIf (pkgs != null) (lib.mkForce pkgs);
|
||||||
|
config._module.args.check = true;
|
||||||
config.host.nixStorePrefix = hostNixStorePrefix;
|
config.host.nixStorePrefix = hostNixStorePrefix;
|
||||||
config.host.uid = lib.toInt uid;
|
config.host.uid = lib.toInt uid;
|
||||||
};
|
};
|
||||||
|
|
||||||
in
|
in
|
||||||
# Typically you need composition.config.build.dockerComposeYaml
|
# Typically you need composition.config.out.dockerComposeYaml
|
||||||
composition
|
composition // {
|
||||||
|
# throw in lib and pkgs for repl convenience
|
||||||
|
inherit lib;
|
||||||
|
inherit (composition._module.args) pkgs;
|
||||||
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
{ lib, pkgs, ... }:
|
|
||||||
|
|
||||||
{ modules, host, name }:
|
|
||||||
let
|
|
||||||
composite = lib.evalModules {
|
|
||||||
check = true;
|
|
||||||
modules = builtinModules ++ modules;
|
|
||||||
};
|
|
||||||
|
|
||||||
builtinModules = [
|
|
||||||
argsModule
|
|
||||||
./modules/service/default-exec.nix
|
|
||||||
./modules/service/docker-compose-service.nix
|
|
||||||
./modules/service/extended-info.nix
|
|
||||||
./modules/service/host-store.nix
|
|
||||||
./modules/service/host.nix
|
|
||||||
./modules/service/image.nix
|
|
||||||
./modules/service/nixos.nix
|
|
||||||
./modules/service/nixos-init.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
argsModule = {
|
|
||||||
_file = ./eval-service.nix;
|
|
||||||
key = ./eval-service.nix;
|
|
||||||
config._module.args.pkgs = lib.mkForce pkgs;
|
|
||||||
config.host = host;
|
|
||||||
config.service.name = name;
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
composite
|
|
21
src/nix/lib.nix
Normal file
21
src/nix/lib.nix
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{ 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
|
||||||
|
;
|
||||||
|
}
|
8
src/nix/modules.nix
Normal file
8
src/nix/modules.nix
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[
|
||||||
|
./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
|
||||||
|
]
|
28
src/nix/modules/composition/composition.nix
Normal file
28
src/nix/modules/composition/composition.nix
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) types mkOption;
|
||||||
|
|
||||||
|
link = url: text:
|
||||||
|
''[${text}](${url})'';
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
docker-compose.extended.project.name = config.project.name;
|
||||||
|
};
|
||||||
|
}
|
|
@ -3,26 +3,53 @@
|
||||||
This is a composition-level module.
|
This is a composition-level module.
|
||||||
|
|
||||||
It defines the low-level options that are read by arion, like
|
It defines the low-level options that are read by arion, like
|
||||||
- build.dockerComposeYaml
|
- out.dockerComposeYaml
|
||||||
|
|
||||||
It declares options like
|
It declares options like
|
||||||
- docker-compose.services
|
- services
|
||||||
|
|
||||||
*/
|
*/
|
||||||
{ pkgs, lib, config, ... }:
|
compositionArgs@{ lib, config, options, pkgs, ... }:
|
||||||
let
|
let
|
||||||
evalService = name: modules: pkgs.callPackage ../../eval-service.nix {} { inherit name modules; inherit (config) host; };
|
inherit (lib) types;
|
||||||
|
|
||||||
|
service = {
|
||||||
|
imports = [ argsModule ] ++ import ../service/all-modules.nix;
|
||||||
|
};
|
||||||
|
argsModule =
|
||||||
|
{ name, # injected by types.submodule
|
||||||
|
...
|
||||||
|
}: {
|
||||||
|
_file = ./docker-compose.nix;
|
||||||
|
key = ./docker-compose.nix;
|
||||||
|
|
||||||
|
config._module.args.pkgs = lib.mkDefault compositionArgs.pkgs;
|
||||||
|
config.host = compositionArgs.config.host;
|
||||||
|
config.composition = compositionArgs.config;
|
||||||
|
config.service.name = name;
|
||||||
|
};
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
imports = [
|
||||||
|
../lib/assert.nix
|
||||||
|
(lib.mkRenamedOptionModule ["docker-compose" "services"] ["services"])
|
||||||
|
];
|
||||||
options = {
|
options = {
|
||||||
build.dockerComposeYaml = lib.mkOption {
|
out.dockerComposeYaml = lib.mkOption {
|
||||||
type = lib.types.package;
|
type = lib.types.package;
|
||||||
description = "A derivation that produces a docker-compose.yaml file for this composition.";
|
description = "A derivation that produces a docker-compose.yaml file for this composition.";
|
||||||
|
readOnly = true;
|
||||||
};
|
};
|
||||||
build.dockerComposeYamlText = lib.mkOption {
|
out.dockerComposeYamlText = lib.mkOption {
|
||||||
type = lib.types.string;
|
type = lib.types.str;
|
||||||
description = "The text of build.dockerComposeYaml.";
|
description = "The text of out.dockerComposeYaml.";
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
|
out.dockerComposeYamlAttrs = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.unspecified;
|
||||||
|
description = "The text of out.dockerComposeYaml.";
|
||||||
|
readOnly = true;
|
||||||
};
|
};
|
||||||
docker-compose.raw = lib.mkOption {
|
docker-compose.raw = lib.mkOption {
|
||||||
type = lib.types.attrs;
|
type = lib.types.attrs;
|
||||||
|
@ -32,26 +59,26 @@ in
|
||||||
type = lib.types.attrs;
|
type = lib.types.attrs;
|
||||||
description = "Attribute set that will be turned into the x-arion section of the docker-compose.yaml file.";
|
description = "Attribute set that will be turned into the x-arion section of the docker-compose.yaml file.";
|
||||||
};
|
};
|
||||||
docker-compose.services = lib.mkOption {
|
services = lib.mkOption {
|
||||||
default = {};
|
type = lib.types.attrsOf (lib.types.submodule service);
|
||||||
type = with lib.types; attrsOf (coercedTo unspecified (a: [a]) (listOf unspecified));
|
description = "An attribute set of service configurations. A service specifies how to run an image as a container.";
|
||||||
description = "A attribute set of service configurations. A service specifies how to run an image. Each of these service configurations is specified using modules whose options are described in the Service Options section.";
|
|
||||||
};
|
};
|
||||||
docker-compose.evaluatedServices = lib.mkOption {
|
docker-compose.volumes = lib.mkOption {
|
||||||
type = lib.types.attrsOf lib.types.attrs;
|
type = lib.types.attrsOf lib.types.unspecified;
|
||||||
description = "Attribute set of evaluated service configurations.";
|
description = "A attribute set of volume configurations.";
|
||||||
readOnly = true;
|
default = {};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = {
|
config = {
|
||||||
build.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.build.dockerComposeYamlText;
|
out.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.out.dockerComposeYamlText;
|
||||||
build.dockerComposeYamlText = builtins.toJSON (config.docker-compose.raw);
|
out.dockerComposeYamlText = builtins.toJSON (config.out.dockerComposeYamlAttrs);
|
||||||
|
out.dockerComposeYamlAttrs = config.assertWarn config.docker-compose.raw;
|
||||||
|
|
||||||
docker-compose.evaluatedServices = lib.mapAttrs evalService config.docker-compose.services;
|
|
||||||
docker-compose.raw = {
|
docker-compose.raw = {
|
||||||
version = "3.4";
|
version = "3.4";
|
||||||
services = lib.mapAttrs (k: c: c.config.build.service) config.docker-compose.evaluatedServices;
|
services = lib.mapAttrs (k: c: c.out.service) config.services;
|
||||||
x-arion = config.docker-compose.extended;
|
x-arion = config.docker-compose.extended;
|
||||||
|
volumes = config.docker-compose.volumes;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
host.nixStorePrefix = lib.mkOption {
|
host.nixStorePrefix = lib.mkOption {
|
||||||
type = lib.types.string;
|
type = lib.types.str;
|
||||||
default = "";
|
default = "";
|
||||||
example = "/mnt/foo";
|
example = "/mnt/foo";
|
||||||
description = ''
|
description = ''
|
||||||
|
@ -23,9 +23,9 @@
|
||||||
stored at an alternate location without altering the format of
|
stored at an alternate location without altering the format of
|
||||||
store paths.
|
store paths.
|
||||||
|
|
||||||
For example: instead of mounting the host's /nix/store as the
|
For example: instead of mounting the host's `/nix/store` as the
|
||||||
container's /nix/store, this will mount /mnt/foo/nix/store
|
container's `/nix/store`, this will mount `/mnt/foo/nix/store`
|
||||||
as the container's /nix/store.
|
as the container's `/nix/store`.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,31 +4,39 @@ let
|
||||||
|
|
||||||
serviceImages =
|
serviceImages =
|
||||||
lib.mapAttrs addDetails (
|
lib.mapAttrs addDetails (
|
||||||
lib.filterAttrs filterFunction config.docker-compose.evaluatedServices
|
lib.filterAttrs filterFunction config.services
|
||||||
);
|
);
|
||||||
|
|
||||||
filterFunction = serviceName: service:
|
filterFunction = serviceName: service:
|
||||||
builtins.addErrorContext "while evaluating whether the service ${serviceName} defines an image"
|
builtins.addErrorContext "while evaluating whether the service ${serviceName} defines an image"
|
||||||
service.config.image.nixBuild;
|
service.image.nixBuild;
|
||||||
|
|
||||||
addDetails = serviceName: service:
|
addDetails = serviceName: service:
|
||||||
builtins.addErrorContext "while evaluating the image for service ${serviceName}"
|
builtins.addErrorContext "while evaluating the image for service ${serviceName}"
|
||||||
(let
|
(let
|
||||||
inherit (service.config) build;
|
inherit (service) build;
|
||||||
in {
|
in {
|
||||||
image = build.image.outPath;
|
|
||||||
imageName = build.imageName or service.image.name;
|
imageName = build.imageName or service.image.name;
|
||||||
imageTag =
|
imageTag =
|
||||||
if build.image.imageTag != ""
|
if build.image.imageTag != ""
|
||||||
then build.image.imageTag
|
then build.image.imageTag
|
||||||
else lib.head (lib.strings.splitString "-" (baseNameOf build.image.outPath));
|
else lib.head (lib.strings.splitString "-" (baseNameOf build.image.outPath));
|
||||||
});
|
} // (if build.image.isExe or false
|
||||||
|
then {
|
||||||
|
imageExe = build.image.outPath;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
image = build.image.outPath;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
build.imagesToLoad = lib.mkOption {
|
build.imagesToLoad = lib.mkOption {
|
||||||
type = listOf unspecified;
|
type = listOf unspecified;
|
||||||
description = "List of dockerTools image derivations.";
|
internal = true;
|
||||||
|
description = "List of `dockerTools` image derivations.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = {
|
config = {
|
||||||
|
|
53
src/nix/modules/composition/networks.nix
Normal file
53
src/nix/modules/composition/networks.nix
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
{ 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;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
|
@ -5,16 +5,13 @@
|
||||||
*/
|
*/
|
||||||
{ config, lib, ... }:
|
{ config, lib, ... }:
|
||||||
let
|
let
|
||||||
|
inherit (lib) mapAttrs filterAttrs;
|
||||||
|
|
||||||
serviceInfo =
|
serviceInfo =
|
||||||
lib.mapAttrs getInfo (
|
filterAttrs (_k: v: v != {})
|
||||||
lib.filterAttrs filterFunction config.docker-compose.evaluatedServices
|
(mapAttrs (_serviceName: service: service.out.extendedInfo)
|
||||||
);
|
config.services
|
||||||
|
);
|
||||||
filterFunction = _serviceName: service:
|
|
||||||
# shallow equality suffices for emptiness test
|
|
||||||
builtins.attrNames service.config.build.extendedInfo != [];
|
|
||||||
|
|
||||||
getInfo = _serviceName: service: service.config.build.extendedInfo;
|
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
2
src/nix/modules/lib/README.md
Normal file
2
src/nix/modules/lib/README.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
`assertions.nix`: Nixpkgs
|
||||||
|
`assert.nix`: extracted from Nixpkgs, adapted
|
33
src/nix/modules/lib/assert.nix
Normal file
33
src/nix/modules/lib/assert.nix
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
|
||||||
|
# based on nixpkgs/nixos/modules/system/activation/top-level.nix
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
concatStringsSep
|
||||||
|
filter
|
||||||
|
mkOption
|
||||||
|
showWarnings
|
||||||
|
types
|
||||||
|
;
|
||||||
|
|
||||||
|
# Handle assertions and warnings
|
||||||
|
failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions);
|
||||||
|
|
||||||
|
assertWarn = if failedAssertions != []
|
||||||
|
then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
|
||||||
|
else showWarnings config.warnings;
|
||||||
|
|
||||||
|
in
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [ ./assertions.nix ];
|
||||||
|
options.assertWarn = mkOption {
|
||||||
|
type = types.unspecified; # a function
|
||||||
|
# It's for the wrapping program to know about this. User need not care.
|
||||||
|
internal = true;
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
|
config = { inherit assertWarn; };
|
||||||
|
}
|
||||||
|
|
34
src/nix/modules/lib/assertions.nix
Normal file
34
src/nix/modules/lib/assertions.nix
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
options = {
|
||||||
|
|
||||||
|
assertions = mkOption {
|
||||||
|
type = types.listOf types.unspecified;
|
||||||
|
internal = true;
|
||||||
|
default = [];
|
||||||
|
example = [ { assertion = false; message = "you can't enable this for that reason"; } ];
|
||||||
|
description = ''
|
||||||
|
This option allows modules to express conditions that must
|
||||||
|
hold for the evaluation of the system configuration to
|
||||||
|
succeed, along with associated error messages for the user.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
warnings = mkOption {
|
||||||
|
internal = true;
|
||||||
|
default = [];
|
||||||
|
type = types.listOf types.str;
|
||||||
|
example = [ "The `foo' service is deprecated and will go away soon!" ];
|
||||||
|
description = ''
|
||||||
|
This option allows modules to show warnings to users during
|
||||||
|
the evaluation of the system configuration.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
# impl of assertions is in <nixpkgs/nixos/modules/system/activation/top-level.nix>
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue