Merge pull request #23 from hercules-ci/documentation

Add doc/manual, tweak README
This commit is contained in:
Domen Kožar 2019-03-04 10:36:41 +07:00 committed by GitHub
commit d63026ce42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 784 additions and 63 deletions

View file

@ -4,10 +4,9 @@
*Wait, what?* *Wait, what?*
With Arion you can fire up containers without creating images for each With Arion you can fire up containers without creating images for each
service. Instead, it uses a mostly empty image, sort of like a base service. It can use a mostly empty image, and makes the host's Nix
image, and makes the host's Nix store available in the container, store available in the container, allowing the container to run
allowing the container to run programs without having to re-package programs without having to re-package them into a docker image.
them into a docker image.
Arion is configured using Nix with modules, like those in Arion is configured using Nix with modules, like those in
NixOS. Similar to `docker-compose` it can therefore combine NixOS. Similar to `docker-compose` it can therefore combine
@ -21,13 +20,10 @@ development environments while working
on [Hercules CI](https://www.hercules-ci.com). (It was also born out on [Hercules CI](https://www.hercules-ci.com). (It was also born out
of ancient Greek deities disguised as horses. More on that later.) of ancient Greek deities disguised as horses. More on that later.)
We don't use it to deploy to 'real' environments and we have no plans If you do want to use Arion for production environments, you'll
to do so in the future. probably want to either build normal container images or manage
garbage collection roots if you control the deployment host. Neither
If you do want to use Arion for 'real' environments, you'll probably scenario is made easier by arion at this time.
want to either build images or manage garbage collection roots if you
control the deployment host. Either of these has yet to be
implemented.
Support for other Linux than NixOS is untested. Support for other Linux than NixOS is untested.
@ -68,7 +64,7 @@ This Nix expression serves the Nix manual at host port 8000 when launched with `
} }
``` ```
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`. 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 # A full featured example
@ -90,7 +86,7 @@ When it runs, it does the following:
Most of the interesting stuff happens in Arion's Nix expressions, 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. 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.
The interesting part is of course the [service-host-store.nix module](src/nix/service-host-store.nix) which performs the bind mounts to make the host Nix store available in the container. 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 # FAQ
@ -101,7 +97,7 @@ Nope, it's just Nix and Docker Compose under the hood.
### Does Arion support Docker images? ### Does Arion support Docker images?
Yes, you can still specify a docker image. For example: Yes, you can also specify a normal Docker image. For example:
postgres = { postgres = {
service.image = "postgres:10"; service.image = "postgres:10";

View file

@ -2,4 +2,5 @@ args@{ pkgs ? import ./nix args, ... }:
{ {
inherit (pkgs) arion tests; inherit (pkgs) arion tests;
doc = pkgs.recurseIntoAttrs (import ./doc { inherit pkgs; });
} }

8
doc/default.nix Normal file
View file

@ -0,0 +1,8 @@
{ pkgs ? import ../nix {} }:
let
inherit (pkgs) recurseIntoAttrs callPackage;
in
recurseIntoAttrs {
manual = callPackage ./manual {};
}

5
doc/manual/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
introduction.xml
installation.xml
manual.html
options-composition.xml
options-service.xml

35
doc/manual/Makefile Normal file
View file

@ -0,0 +1,35 @@
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)

2
doc/manual/build Executable file
View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
nix-shell -A manual --run 'patchPhase && make'

124
doc/manual/default.nix Normal file
View file

@ -0,0 +1,124 @@
{ 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.fakeroot 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 >$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 = {}; };
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

View file

@ -0,0 +1,37 @@
= 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.
== 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 ];
}
```
////

View file

@ -0,0 +1,33 @@
= 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.

3
doc/manual/live-build Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env nix-shell
#!nix-shell -A manual
#!nix-shell --run live-build

25
doc/manual/manual.xml Normal file
View file

@ -0,0 +1,25 @@
<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>

View file

@ -0,0 +1,224 @@
<?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, '*', '_'), '&lt;', '_'), '>', '_'), '?', '_'))" />
<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, '&#010;')]">
<programlisting>
<xsl:text>''
</xsl:text><xsl:value-of select='str:replace(string/@value, "${", "&apos;&apos;${")' /><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, '&quot;') or contains(@value, '\')) and not(contains(@value, '&#010;'))">
<xsl:text>''</xsl:text><xsl:value-of select='str:replace(@value, "${", "&apos;&apos;${")' /><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, '\', '\\'), '&quot;', '\&quot;'), '&#010;', '\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 its 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 != ''">
&lt;<xsl:value-of select="$nixPathKey"/>/<xsl:value-of select="@value"/>&gt;
</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>

159
doc/manual/style.css Normal file
View file

@ -0,0 +1,159 @@
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;
}

View file

@ -1,4 +1,5 @@
self: super: { self: super: {
arion = super.callPackage ../arion.nix {}; arion = super.callPackage ../arion.nix {};
tests = super.callPackage ../tests {}; tests = super.callPackage ../tests {};
doc = super.callPackage ../doc {};
} }

25
src/nix/eval-service.nix Normal file
View file

@ -0,0 +1,25 @@
{ lib, pkgs, ... }:
{ modules, host }:
let
composite = lib.evalModules {
check = true;
modules = builtinModules ++ modules;
};
builtinModules = [
argsModule
./modules/service/docker-compose-service.nix
./modules/service/host-store.nix
./modules/service/host.nix
];
argsModule = {
_file = ./docker-compose.nix;
key = ./docker-compose.nix;
config._module.args.pkgs = lib.mkForce pkgs;
config.host = host;
};
in
composite

View file

@ -10,47 +10,28 @@
*/ */
{ pkgs, lib, config, ... }: { pkgs, lib, config, ... }:
let let
evalService = name: modules: evalService = name: modules: (pkgs.callPackage ../../eval-service.nix {} { inherit modules; inherit (config) host; }).config.build.service;
let
composite = lib.evalModules {
check = true;
modules = builtinModules ++ modules;
};
builtinModules = [
argsModule
../service/docker-compose-service.nix
../service/host-store.nix
../service/host.nix
];
argsModule = {
_file = ./docker-compose.nix;
key = ./docker-compose.nix;
config._module.args.pkgs = lib.mkForce pkgs;
config.host = config.host;
};
in
composite.config.build.service;
in in
{ {
options = { options = {
build.dockerComposeYaml = lib.mkOption { build.dockerComposeYaml = lib.mkOption {
type = lib.types.package; type = lib.types.package;
description = "A derivation that produces a docker-compose.yaml file for this composition.";
}; };
build.dockerComposeYamlText = lib.mkOption { build.dockerComposeYamlText = lib.mkOption {
type = lib.types.string; type = lib.types.string;
description = "The text of build.dockerComposeYaml.";
}; };
docker-compose.raw = lib.mkOption { docker-compose.raw = lib.mkOption {
type = lib.types.attrs; type = lib.types.attrs;
description = "Nested attribute set that will be turned into the docker-compose.yaml file, using Nix's toJSON builtin.";
}; };
docker-compose.services = lib.mkOption { docker-compose.services = lib.mkOption {
default = {}; default = {};
type = with lib.types; attrsOf (coercedTo unspecified (a: [a]) (listOf unspecified)); type = with lib.types; attrsOf (coercedTo unspecified (a: [a]) (listOf unspecified));
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.";
}; };
}; };
config = { config = {

View file

@ -8,8 +8,9 @@
description = '' description = ''
The numeric user id (UID) of the user running arion. The numeric user id (UID) of the user running arion.
Assuming this user id is helpful when dealing with the user's This lets you to write modules that interact with the host
files, mounted into the container as a volume. user's files, which is helpful for local development, but not
intended for production-like deployment scenarios.
''; '';
}; };

View file

@ -8,80 +8,127 @@
let let
inherit (lib) mkOption types; inherit (lib) mkOption types;
inherit (types) listOf nullOr attrsOf string either int bool; inherit (types) listOf nullOr attrsOf str either int bool;
dockerComposeRef = fragment:
''See <link xlink:href="https://docs.docker.com/compose/compose-file/#${fragment}">Docker Compose#${fragment}</link>'';
dockerComposeKitchenSink = ''
Analogous to the <code>docker run</code> counterpart.
${dockerComposeRef "domainname-hostname-ipc-mac_address-privileged-read_only-shm_size-stdin_open-tty-user-working_dir"}
'';
in in
{ {
options = { options = {
build.service = mkOption {
type = attrsOf types.unspecified;
description = ''
Raw input for the service in <code>docker-compose.yaml</code>.
You should not need to use this option. If anything is
missing, please contribute the missing option.
This option is user accessible because it may serve as an
escape hatch for some.
'';
};
service.volumes = mkOption { service.volumes = mkOption {
type = listOf types.unspecified; type = listOf types.unspecified;
default = []; default = [];
description = "";
}; };
service.build.context = mkOption { service.build.context = mkOption {
type = nullOr string; type = nullOr str;
default = null; default = null;
description = ''
Locates a Dockerfile to use for creating an image to use in this service.
${dockerComposeRef "context"}
'';
}; };
service.hostname = mkOption { service.hostname = mkOption {
type = nullOr string; type = nullOr str;
default = null; default = null;
description = dockerComposeKitchenSink;
}; };
service.environment = mkOption { service.environment = mkOption {
type = attrsOf (either string int); type = attrsOf (either str int);
default = {}; default = {};
description = dockerComposeRef "environment";
}; };
service.image = mkOption { service.image = mkOption {
type = string; type = str;
description = dockerComposeRef "image";
}; };
service.command = mkOption { service.command = mkOption {
type = nullOr types.unspecified; type = nullOr types.unspecified;
default = null; default = null;
description = dockerComposeRef "command";
}; };
service.depends_on = mkOption { service.depends_on = mkOption {
type = listOf string; type = listOf str;
default = []; default = [];
description = dockerComposeRef "depends_on";
}; };
service.links = mkOption { service.links = mkOption {
type = listOf string; type = listOf str;
default = []; default = [];
description = dockerComposeRef "links";
}; };
service.external_links = mkOption { service.external_links = mkOption {
type = listOf string; type = listOf str;
default = []; default = [];
description = dockerComposeRef "external_links";
}; };
service.extra_hosts = mkOption { service.extra_hosts = mkOption {
type = listOf string; type = listOf str;
default = []; default = [];
description = dockerComposeRef "extra_hosts";
}; };
service.working_dir = mkOption { service.working_dir = mkOption {
type = nullOr string; type = nullOr str;
default = null; default = null;
description = dockerComposeKitchenSink;
}; };
service.privileged = mkOption { service.privileged = mkOption {
type = nullOr bool; type = nullOr bool;
default = null; default = null;
description = dockerComposeKitchenSink;
}; };
service.entrypoint = mkOption { service.entrypoint = mkOption {
type = nullOr string; type = nullOr str;
default = null; default = null;
description = dockerComposeRef "entrypoint";
}; };
service.restart = mkOption { service.restart = mkOption {
type = nullOr string; type = nullOr str;
default = null; default = null;
description = dockerComposeRef "restart";
}; };
service.ports = mkOption { service.ports = mkOption {
type = listOf types.unspecified; type = listOf types.unspecified;
default = []; default = [];
description = '' description = ''
Expose ports on host. "host:container" or structured. Expose ports on host. "host:container" or structured.
See https://docs.docker.com/compose/compose-file/#ports
${dockerComposeRef "ports"}
''; '';
}; };
service.expose = mkOption { service.expose = mkOption {
type = listOf string; type = listOf str;
default = []; default = [];
description = dockerComposeRef "expose";
}; };
service.env_file = mkOption {
build.service = mkOption { type = listOf str;
type = attrsOf types.unspecified; default = [];
description = dockerComposeRef "env_file";
};
service.network_mode = mkOption {
type = nullOr str;
default = null;
description = dockerComposeRef "network_mode";
}; };
}; };
@ -97,15 +144,29 @@ in
inherit (config.service) command; inherit (config.service) command;
} // lib.optionalAttrs (config.service.depends_on != []) { } // lib.optionalAttrs (config.service.depends_on != []) {
inherit (config.service) depends_on; inherit (config.service) depends_on;
} // lib.optionalAttrs (config.service.entrypoint != null) {
inherit (config.service) entrypoint;
} // lib.optionalAttrs (config.service.env_file != []) {
inherit (config.service) env_file;
} // lib.optionalAttrs (config.service.expose != []) {
inherit (config.service) expose;
} // lib.optionalAttrs (config.service.external_links != []) {
inherit (config.service) external_links;
} // lib.optionalAttrs (config.service.extra_hosts != []) {
inherit (config.service) extra_hosts;
} // lib.optionalAttrs (config.service.hostname != null) {
inherit (config.service) hostname;
} // lib.optionalAttrs (config.service.links != []) {
inherit (config.service) links;
} // lib.optionalAttrs (config.service.ports != []) {
inherit (config.service) ports;
} // lib.optionalAttrs (config.service.privileged != null) {
inherit (config.service) privileged;
} // lib.optionalAttrs (config.service.network_mode != null) {
inherit (config.service) network_mode;
} // lib.optionalAttrs (config.service.restart != null) { } // lib.optionalAttrs (config.service.restart != null) {
inherit (config.service) restart; inherit (config.service) restart;
} // lib.optionalAttrs (config.service.working_dir != null) { } // lib.optionalAttrs (config.service.working_dir != null) {
inherit (config.service) working_dir; inherit (config.service) working_dir;
} // lib.optionalAttrs (config.service.entrypoint != null) {
inherit (config.service) entrypoint;
} // lib.optionalAttrs (config.service.ports != []) {
inherit (config.service) ports;
} // lib.optionalAttrs (config.service.expose != []) {
inherit (config.service) expose;
}; };
} }