diff --git a/src/arion b/src/arion
index 32ec65d..41a1dea 100755
--- a/src/arion
+++ b/src/arion
@@ -46,6 +46,11 @@ while test $# != 0; do
shift
break
;;
+ exec)
+ command="$1"
+ shift
+ break
+ ;;
docker-compose)
command="docker-compose"
shift
@@ -231,6 +236,45 @@ 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.webserver.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
@@ -239,6 +283,9 @@ case "$command" in
repl)
do_repl
;;
+ exec)
+ run_exec "$@"
+ ;;
docker-compose)
if [[ ${#docker_compose_args[@]} != 0
&& ${docker_compose_args[0]} != "help"
diff --git a/src/nix/eval-composition.nix b/src/nix/eval-composition.nix
index 7d10e99..b3fc78a 100644
--- a/src/nix/eval-composition.nix
+++ b/src/nix/eval-composition.nix
@@ -21,6 +21,7 @@ let
./modules/composition/docker-compose.nix
./modules/composition/host-environment.nix
./modules/composition/images.nix
+ ./modules/composition/service-info.nix
];
argsModule = {
diff --git a/src/nix/eval-service.nix b/src/nix/eval-service.nix
index 887b419..26a498c 100644
--- a/src/nix/eval-service.nix
+++ b/src/nix/eval-service.nix
@@ -9,7 +9,9 @@ let
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
diff --git a/src/nix/modules/composition/docker-compose.nix b/src/nix/modules/composition/docker-compose.nix
index 552c2e4..95855ff 100644
--- a/src/nix/modules/composition/docker-compose.nix
+++ b/src/nix/modules/composition/docker-compose.nix
@@ -26,7 +26,11 @@ in
};
docker-compose.raw = lib.mkOption {
type = lib.types.attrs;
- description = "Nested attribute set that will be turned into the docker-compose.yaml file, using Nix's toJSON builtin.";
+ description = "Attribute set that will be turned into the docker-compose.yaml file, using Nix's toJSON builtin.";
+ };
+ docker-compose.extended = lib.mkOption {
+ type = lib.types.attrs;
+ description = "Attribute set that will be turned into the x-arion section of the docker-compose.yaml file.";
};
docker-compose.services = lib.mkOption {
default = {};
@@ -47,6 +51,7 @@ in
docker-compose.raw = {
version = "3.4";
services = lib.mapAttrs (k: c: c.config.build.service) config.docker-compose.evaluatedServices;
+ x-arion = config.docker-compose.extended;
};
};
}
diff --git a/src/nix/modules/composition/images.nix b/src/nix/modules/composition/images.nix
index 19035e8..9a9b04c 100644
--- a/src/nix/modules/composition/images.nix
+++ b/src/nix/modules/composition/images.nix
@@ -31,6 +31,6 @@ in
};
config = {
build.imagesToLoad = lib.attrValues serviceImages;
- docker-compose.raw.x-arion.images = config.build.imagesToLoad;
+ docker-compose.extended.images = config.build.imagesToLoad;
};
}
diff --git a/src/nix/modules/composition/service-info.nix b/src/nix/modules/composition/service-info.nix
new file mode 100644
index 0000000..1ed2989
--- /dev/null
+++ b/src/nix/modules/composition/service-info.nix
@@ -0,0 +1,24 @@
+/*
+ Adds extendedInfo from services to the Docker Compose file.
+
+ This contains fields that are not in Docker Compose schema.
+ */
+{ config, lib, ... }:
+let
+ serviceInfo =
+ lib.mapAttrs getInfo (
+ lib.filterAttrs filterFunction config.docker-compose.evaluatedServices
+ );
+
+ filterFunction = _serviceName: service:
+ # shallow equality suffices for emptiness test
+ builtins.attrNames service.config.build.extendedInfo != [];
+
+ getInfo = _serviceName: service: service.config.build.extendedInfo;
+
+in
+{
+ config = {
+ docker-compose.extended.serviceInfo = serviceInfo;
+ };
+}
diff --git a/src/nix/modules/nixos/default-shell.nix b/src/nix/modules/nixos/default-shell.nix
new file mode 100644
index 0000000..2eb4173
--- /dev/null
+++ b/src/nix/modules/nixos/default-shell.nix
@@ -0,0 +1,4 @@
+{ config, utils, ... }:
+{
+ config.system.build.x-arion-defaultShell = utils.toShellPath config.users.defaultUserShell;
+}
diff --git a/src/nix/modules/service/default-exec.nix b/src/nix/modules/service/default-exec.nix
new file mode 100644
index 0000000..299e083
--- /dev/null
+++ b/src/nix/modules/service/default-exec.nix
@@ -0,0 +1,19 @@
+{ config, lib, ... }:
+let
+ inherit (lib) types mkOption;
+in
+{
+ options = {
+ service.defaultExec = mkOption {
+ type = types.listOf types.str;
+ default = ["/bin/sh"];
+ description = ''
+ Container program and arguments to invoke when calling
+ arion exec <service.name>
without further arguments.
+ '';
+ };
+ };
+ config = {
+ build.extendedInfo.defaultExec = config.service.defaultExec;
+ };
+}
\ No newline at end of file
diff --git a/src/nix/modules/service/extended-info.nix b/src/nix/modules/service/extended-info.nix
new file mode 100644
index 0000000..bb876c7
--- /dev/null
+++ b/src/nix/modules/service/extended-info.nix
@@ -0,0 +1,20 @@
+{ config, lib, ... }:
+let
+ inherit (lib) mkOption;
+ inherit (lib.types) attrsOf unspecified;
+in
+{
+ options = {
+ build.extendedInfo = mkOption {
+ type = attrsOf unspecified;
+ description = ''
+ Information about a service to include in the Docker Compose file,
+ but that will not be used by the docker-compose
command
+ itself.
+
+ It will be inserted in x-arion.serviceInfo.<service.name>
.
+ '';
+ default = {};
+ };
+ };
+}
\ No newline at end of file
diff --git a/src/nix/modules/service/nixos-init.nix b/src/nix/modules/service/nixos-init.nix
index 5c6da89..7521a09 100644
--- a/src/nix/modules/service/nixos-init.nix
+++ b/src/nix/modules/service/nixos-init.nix
@@ -21,6 +21,7 @@ in
config = lib.mkIf (config.nixos.useSystemd) {
nixos.configuration.imports = [
../nixos/container-systemd.nix
+ ../nixos/default-shell.nix
(pkgs.path + "/nixos/modules/profiles/minimal.nix")
];
image.command = [ "${config.nixos.build.toplevel}/init" ];
@@ -35,5 +36,6 @@ in
service.stop_signal = "SIGRTMIN+3";
service.tty = true;
+ service.defaultExec = [config.nixos.build.x-arion-defaultShell "-l"];
};
}
diff --git a/tests/arion-test/default.nix b/tests/arion-test/default.nix
index 4ad5aca..1020a1b 100644
--- a/tests/arion-test/default.nix
+++ b/tests/arion-test/default.nix
@@ -51,6 +51,8 @@ in
subtest "full-nixos", sub {
$machine->succeed("cp -r ${../../examples/full-nixos} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d");
$machine->waitUntilSucceeds("curl localhost:8000");
+ # Also test exec with defaultExec
+ $machine->succeed("cd work && export NIX_PATH=nixpkgs='${pkgs.path}' && (echo 'nix run -f ~/h/arion arion -c arion exec webserver'; echo 'target=world; echo Hello \$target'; echo exit) | script /dev/null | grep 'Hello world'");
$machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work");
$machine->waitUntilFails("curl localhost:8000");
};