arion
arion copied to clipboard
Using environment variables in service label keys
Problem
I'd like to use environment variables like COMPOSE_PROJECT_NAME
in the labels of a service, but they only appear to be resolved in the label values, not the label keys.
repro
arion-compose.nix:
{ pkgs, ... }: {
config.services = {
webserver = {
service.useHostStore = true;
service.command = [
"${pkgs.bash}/bin/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.labels = {
"wef" = "erg";
"label_\${COMPOSE_PROJECT_NAME}" = "value_\${COMPOSE_PROJECT_NAME}";
};
};
};
}
$ COMPOSE_PROJECT_NAME=foo arion up
$ docker inspect --format='{{json .Config.Labels}}' foo_webserver_1 | jq
{
"com.docker.compose.config-hash": "88a285cf35d502fd6d9b23e8262c2edb6d030ce552e71ae79e5e9d18635e047b",
"com.docker.compose.container-number": "1",
"com.docker.compose.oneoff": "False",
"com.docker.compose.project": "foo",
"com.docker.compose.project.config_files": ".tmp-arion-docker-compose369907-0.yaml",
"com.docker.compose.project.working_dir": "/home/rkb/projects/PHDSys-webapp/hippo/tools/arion/minimal",
"com.docker.compose.service": "webserver",
"com.docker.compose.version": "1.29.2",
"wef": "erg",
"label_${COMPOSE_PROJECT_NAME}": "value_foo"
}
I expected the last keypair to evaluate to "label_foo": "value_foo"
, but Docker's env var syntax was left untouched on the left hand side only.
You could use config.project.name
at the Nix level, if you were setting that instead.
-{ pkgs, ... }: {
+{ config, pkgs, ... }: {
+ config.project.name = "foo";
config.services = {
- "label_\${COMPOSE_PROJECT_NAME}" = "value_\${COMPOSE_PROJECT_NAME}";
+ "label_${config.project.name}" = "value_\${config.project.name}";
ie using Nix programming instead of docker-compose interpolation.
Ad hoc configuration is a bit cumbersome at the moment.
echo "{ project.name = ''foo''; }" > ad-hoc-config.nix
arion -f arion-compose.nix -f ad-hoc-config.nix "$@"
rm ad-hoc-config.nix
This would be better achieved with a new option to avoid the tmpfile altogether.
(A pipe, as in <(echo .....)
, does not work; probably a limitation of Nix)
Thanks for the tip! I ended up wanting to pass arbitrary config values, which I couldn't get to work with the arion -f arion-compose.nix -f ad-hoc-config.nix
config stacking approach.
Here's the gist of what I'm using now:
arion-args.sh
#!/usr/bin/env bash
set -e
# https://stackoverflow.com/a/17744637/2014893
scriptFolder="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
cd "$scriptFolder"
tempFile() {
tempPrefix="$scriptFolder"/$(basename "$0")
mktemp "${tempPrefix}".XXXXXX.ad-hoc-config.nix
}
adHocConfig=$(tempFile)
# delete the ad-hoc config after we're done or crashed
trap 'rm -rf $adHocConfig' EXIT
# Generate a temp file, so we can use dynamic things like the project name in
# the Arion config.
cat << EOF > "$adHocConfig"
arionArgs@{ pkgs, ... }:
let
adHocArgs = {
projectName = "$COMPOSE_PROJECT_NAME";
bar = $someEnvValue;
baz = $someOtherEnvValue;
};
in (import ./arion-compose.nix ({ inherit adHocArgs; } // arionArgs))
EOF
# a little help for debugging
printf "%0.s-" {1..20}
echo "$adHocConfig"
cat "$adHocConfig"
printf "%0.s-" {1..20}
echo
arion -f "$adHocConfig" "$@"
arion-compose.nix
{ pkgs, adHocArgs, ... }:
let inherit (adHocArgs) projectName bar baz;
in {
config.services = {
webserver = {
service.useHostStore = true;
service.command = [
"${pkgs.bash}/bin/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.labels = {
"foo" = bar;
"label_${projectName}" = "value_${baz}";
};
};
};
}
I see. You're not supposed to invoke the arion-compose.nix module yourself. Instead you can use multiple -f
options to let the module system combine the modules for you.
You can declare your own options in the arion-compose.nix
file using options
and mkOption
, like you would in a NixOS module. Then turn "$adHocConfig"
into a configuration module that writes those options.
Ohh right, proper Nix Modules... I hadn't gotten involved with them before now. Time to read up!
Wow yeah, Modules do simplify the ad-hoc config file; here's my updated version:
arion-args.sh
#!/usr/bin/env bash
set -e
# https://stackoverflow.com/a/17744637/2014893
scriptFolder="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
cd "$scriptFolder"
tempFile() {
tempPrefix="$scriptFolder"/$(basename "$0")
mktemp "${tempPrefix}".XXXXXX.ad-hoc-config.nix
}
adHocConfig=$(tempFile)
# delete the ad-hoc config after we're done or crashed
trap 'rm -rf $adHocConfig' EXIT
# Generate a temp file, so we can use dynamic things like the project name in
# the Arion config.
cat << EOF > "$adHocConfig"
{
project.name = "$COMPOSE_PROJECT_NAME";
bar = $someEnvValue;
baz = $someOtherEnvValue;
}
EOF
# a little help for debugging
printf "%0.s-" {1..20}
echo "$adHocConfig"
cat "$adHocConfig"
printf "%0.s-" {1..20}
echo
arion -f ./arion-compose.nix -f "$adHocConfig" "$@"
arion-compose.nix
{ config, pkgs, lib, ... }: {
options = {
bar = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
lorem ipsum
'';
};
baz = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
dolor sit amet
'';
};
};
config.services = {
webserver = {
service.useHostStore = true;
service.command = [
"${pkgs.bash}/bin/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.labels = {
"foo" = lib.boolToString config.bar;
"label_${
if config.project.name == null then
"defaultName"
else
config.project.name
}" = "value_${lib.boolToString config.baz}";
};
};
};
}