diff --git a/src/nix/modules/composition/docker-compose.nix b/src/nix/modules/composition/docker-compose.nix
index 95855ff..518da5e 100644
--- a/src/nix/modules/composition/docker-compose.nix
+++ b/src/nix/modules/composition/docker-compose.nix
@@ -11,8 +11,36 @@
*/
{ pkgs, lib, config, ... }:
let
+ cfg = config.docker-compose;
+ inherit (lib) mkOption optionalAttrs mapAttrs;
+ inherit (lib.types) submodule attrsOf nullOr str path bool;
evalService = name: modules: pkgs.callPackage ../../eval-service.nix {} { inherit name modules; inherit (config) host; };
+ dockerComposeRef = fragment:
+ ''See Docker Compose#${fragment}'';
+
+ secretType = submodule {
+ options = {
+ file = mkOption {
+ type = either path str;
+ description = ''
+ Sets the secret's value to this file.
+
+ ${dockerComposeRef "secrets"}
+ '';
+ };
+ external = mkOption {
+ type = bool;
+ default = false;
+ description = ''
+ Whether the value of this secret is set via other means.
+
+ ${dockerComposeRef "secrets"}
+ '';
+ };
+ };
+ };
+
in
{
options = {
@@ -42,6 +70,11 @@ in
description = "Attribute set of evaluated service configurations.";
readOnly = true;
};
+ docker-compose.secrets = lib.mkOption {
+ type = attrsOf secretType;
+ description = dockerComposeRef "secrets";
+ default = {};
+ };
};
config = {
build.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.build.dockerComposeYamlText;
@@ -52,6 +85,13 @@ in
version = "3.4";
services = lib.mapAttrs (k: c: c.config.build.service) config.docker-compose.evaluatedServices;
x-arion = config.docker-compose.extended;
+ } // optionalAttrs (cfg.secrets != {}) {
+ secrets = mapAttrs (_k: s: optionalAttrs (s.external != false) {
+ inherit (s) external;
+ } // optionalAttrs (s.file != null) {
+ file = toString s.file;
+ }
+ ) cfg.secrets;
};
};
}
diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix
index 54dae9f..d40c6f7 100644
--- a/src/nix/modules/service/docker-compose-service.nix
+++ b/src/nix/modules/service/docker-compose-service.nix
@@ -7,9 +7,11 @@
{ pkgs, lib, config, ... }:
let
- inherit (lib) mkOption types;
+ inherit (lib) mkOption types mapAttrs mapAttrsToList;
inherit (types) listOf nullOr attrsOf str either int bool;
+ cfg = config.service;
+
link = url: text:
''${text}'';
dockerComposeRef = fragment:
@@ -23,6 +25,48 @@ let
cap_add = lib.attrNames (lib.filterAttrs (name: value: value == true) config.service.capabilities);
cap_drop = lib.attrNames (lib.filterAttrs (name: value: value == false) config.service.capabilities);
+ serviceSecretType = types.submodule {
+ options = {
+ source = mkOption {
+ type = nullOr str;
+ default = null;
+ description = dockerComposeRef "secrets";
+ };
+ uid = mkOption {
+ type = nullOr (either str int);
+ default = null;
+ description = dockerComposeRef "secrets";
+ };
+ gid = mkOption {
+ type = nullOr (either str int);
+ default = null;
+ description = dockerComposeRef "secrets";
+ };
+ mode = mkOption {
+ type = nullOr str;
+ # default = "0444";
+ default = null;
+ description = ''
+ The default value of is usually 0444. This option may not be supported
+ when not deploying to a Swarm.
+
+ ${dockerComposeRef "secrets"}
+ '';
+ };
+ };
+ };
+ secrets = mapAttrsToList (k: s: {
+ target = k;
+ } //lib.optionalAttrs (s.source != null) {
+ inherit (s) source;
+ } // lib.optionalAttrs (s.uid != null) {
+ inherit (s) uid;
+ } // lib.optionalAttrs (s.gid != null) {
+ inherit (s) gid;
+ } // lib.optionalAttrs (s.mode != null) {
+ inherit (s) mode;
+ }) cfg.secrets;
+
in
{
options = {
@@ -197,6 +241,19 @@ in
}
'';
};
+ service.secrets = mkOption {
+ type = attrsOf serviceSecretType;
+ default = {};
+ description = dockerComposeRef "secrets";
+ example = {
+ redis_secret = {
+ source = "web_cache_redis_secret";
+ uid = 123;
+ gid = 123;
+ mode = "0440";
+ };
+ };
+ };
};
config.build.service = {
@@ -242,6 +299,8 @@ in
inherit (config.service) restart;
} // lib.optionalAttrs (config.service.stop_signal != null) {
inherit (config.service) stop_signal;
+ } // lib.optionalAttrs (secrets != null) {
+ inherit secrets;
} // lib.optionalAttrs (config.service.tmpfs != []) {
inherit (config.service) tmpfs;
} // lib.optionalAttrs (config.service.tty != null) {
diff --git a/tests/arion-test/default.nix b/tests/arion-test/default.nix
index 1020a1b..226586f 100644
--- a/tests/arion-test/default.nix
+++ b/tests/arion-test/default.nix
@@ -63,5 +63,13 @@ in
$machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work");
$machine->waitUntilFails("curl localhost:8000");
};
+
+ subtest "secrets", sub {
+ $machine->succeed("cp -r ${../testcases/secrets} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d");
+ $machine->waitUntilSucceeds("curl localhost:8000");
+ $machine->succeed("test qux = \"$(curl localhost:8000)\"");
+ $machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work");
+ $machine->waitUntilFails("curl localhost:8000");
+ };
'';
}
diff --git a/tests/testcases/secrets/arion-compose.nix b/tests/testcases/secrets/arion-compose.nix
new file mode 100644
index 0000000..dc530eb
--- /dev/null
+++ b/tests/testcases/secrets/arion-compose.nix
@@ -0,0 +1,17 @@
+{
+ docker-compose.services.webserver = { pkgs, ... }: {
+ nixos.useSystemd = true;
+ nixos.configuration.boot.tmpOnTmpfs = true;
+ nixos.configuration.services.nginx.enable = true;
+
+ # Please don't do this
+ nixos.configuration.services.nginx.virtualHosts.localhost.root = "/run/secrets";
+
+ service.useHostStore = true;
+ service.ports = [
+ "8000:80" # host:container
+ ];
+ service.secrets."foo.txt".source = "foo";
+ };
+ docker-compose.secrets.foo.file = ./foo.key;
+}
diff --git a/tests/testcases/secrets/arion-pkgs.nix b/tests/testcases/secrets/arion-pkgs.nix
new file mode 100644
index 0000000..d13a605
--- /dev/null
+++ b/tests/testcases/secrets/arion-pkgs.nix
@@ -0,0 +1,2 @@
+# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
+import {}
diff --git a/tests/testcases/secrets/foo.key b/tests/testcases/secrets/foo.key
new file mode 100644
index 0000000..78df5b0
--- /dev/null
+++ b/tests/testcases/secrets/foo.key
@@ -0,0 +1 @@
+qux
\ No newline at end of file