Add secrets options to service and composition

This commit is contained in:
Robert Hensing 2019-05-22 12:23:32 +02:00
parent 221bccd7f1
commit a6878c0391
6 changed files with 128 additions and 1 deletions

View file

@ -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 <link xlink:href="https://docs.docker.com/compose/compose-file/#${fragment}">Docker Compose#${fragment}</link>'';
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;
};
};
}

View file

@ -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:
''<link xlink:href="${url}">${text}</link>'';
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) {

View file

@ -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");
};
'';
}

View file

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

View file

@ -0,0 +1,2 @@
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
import <nixpkgs> {}

View file

@ -0,0 +1 @@
qux