arion icon indicating copy to clipboard operation
arion copied to clipboard

Using environment variables in service label keys

Open r-k-b opened this issue 3 years ago • 4 comments

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.

r-k-b avatar Sep 30 '21 10:09 r-k-b

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)

roberth avatar Sep 30 '21 15:09 roberth

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}";
      };
    };
  };
}

r-k-b avatar Oct 01 '21 21:10 r-k-b

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.

roberth avatar Oct 01 '21 22:10 roberth

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}";
      };
    };
  };
}

r-k-b avatar Oct 02 '21 05:10 r-k-b