Skip to content

Commit

Permalink
add support for docker compose secrets
Browse files Browse the repository at this point in the history
enables using [docker compose secrets](https://docs.docker.com/compose/use-secrets/)
from arion, which includes:

- [top-level `secrets` element](https://docs.docker.com/compose/compose-file/09-secrets/)
defining the secrets to be used
for the below two use-cases,
exposing them at `/run/secrets/<secret_name>`.
comes in flavors `file` vs `environment`.
- run-time: [`services` top-level `secrets` element](https://docs.docker.com/compose/compose-file/05-services/#secrets)
- build time: [build secrets](https://docs.docker.com/build/building/secrets/)
(to be [mounted](https://docs.docker.com/build/building/secrets/#secret-mounts)
in the `Dockerfile` like
`RUN --mount=type=secret,id=<secret_name> ...`)

unlike hercules-ci#52, i did not so far add support for their
[long syntax](https://docs.docker.com/compose/compose-file/05-services/#long-syntax-4),
which despite the confusing documentation appears
[limited to Docker Swarm](docker/compose#9648 (comment)),
in my understanding limiting its use in Arion.
  • Loading branch information
KiaraGrouwstra committed Aug 2, 2024
1 parent 236f9dd commit ee9fdc9
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 24 deletions.
1 change: 1 addition & 0 deletions src/haskell/testdata/Arion/NixSpec/arion-compose.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"name": "unit-test-data"
}
},
"secrets": {},
"services": {
"webserver": {
"command": [
Expand Down
10 changes: 9 additions & 1 deletion src/haskell/testdata/Arion/NixSpec/arion-context-compose.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@
"name": "unit-test-data"
}
},
"secrets": {
"foo": {
"environment": "FOO"
}
},
"services": {
"webserver": {
"build": {
"context": "<STOREPATH>"
"context": "<STOREPATH>",
"secrets": [
"foo"
]
},
"environment": {},
"ports": [
Expand Down
2 changes: 2 additions & 0 deletions src/haskell/testdata/Arion/NixSpec/arion-context-compose.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
project.name = "unit-test-data";
services.webserver.service = {
build.context = "${./build-context}";
build.secrets = ["foo"];
ports = [
"8080:80"
];
};
secrets.foo.environment = "FOO";
}
4 changes: 4 additions & 0 deletions src/nix/lib.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ let
networkRef = fragment:
''See ${link "https://github.com/compose-spec/compose-spec/blob/${composeSpecRev}/06-networks.md#${fragment}" "Compose Spec Networks #${fragment}"}'';

secretRef = fragment:
''See ${link "https://github.com/compose-spec/compose-spec/blob/${composeSpecRev}/09-secrets.md#${fragment}" "Compose Spec Secrets #${fragment}"}'';

in
{
inherit
link
networkRef
serviceRef
secretRef
;
}
3 changes: 2 additions & 1 deletion src/nix/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
./modules/composition/host-environment.nix
./modules/composition/images.nix
./modules/composition/networks.nix
./modules/composition/secrets.nix
./modules/composition/service-info.nix
./modules/composition/composition.nix
]
]
9 changes: 9 additions & 0 deletions src/nix/modules/composition/docker-compose.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ in
description = "A attribute set of volume configurations.";
default = {};
};
docker-compose.secrets = lib.mkOption {
type = lib.types.attrsOf lib.types.unspecified;
description = ''
An attribute set of secret configurations. For more info, see:
https://docs.docker.com/compose/compose-file/09-secrets/
'';
default = {};
};
};
config = {
out.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.out.dockerComposeYamlText;
Expand All @@ -79,6 +87,7 @@ in
services = lib.mapAttrs (k: c: c.out.service) config.services;
x-arion = config.docker-compose.extended;
volumes = config.docker-compose.volumes;
secrets = config.docker-compose.secrets;
};
};
}
33 changes: 33 additions & 0 deletions src/nix/modules/composition/secrets.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{ config, lib, ... }:

let
inherit (lib)
mkOption
types
;
inherit (import ../../lib.nix { inherit lib; })
link
;
in
{

options = {
secrets = mkOption {
type = types.lazyAttrsOf (types.submoduleWith {
modules = [
../secrets/secret.nix
];
});
description = ''
See ${link "https://docs.docker.com/compose/compose-file/09-secrets/" "Docker Compose Secrets"}
'';
};
};

config = {

secrets = {};
docker-compose.secrets = lib.mapAttrs (k: v: v.out) config.secrets;

};
}
55 changes: 55 additions & 0 deletions src/nix/modules/secrets/secret.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{ config, lib, options, ... }:

let
inherit (lib)
mkOption
optionalAttrs
types
;
inherit (import ../../lib.nix { inherit lib; })
secretRef
;
in
{
options = {
file = mkOption {
description = ''
The secret is created with the contents of the file at the specified path.
${secretRef "file"}
'';
type = types.nullOr types.str;
};

environment = mkOption {
description = ''
The secret is created with the value of an environment variable.
${secretRef "environment"}
'';
type = types.nullOr types.str;
};

out = mkOption {
internal = true;
description = ''
Defines sensitive data that is granted to the services in your Compose application.
The source of the secret is either `file` or `environment`.
'';
type = lib.types.attrsOf lib.types.raw or lib.types.unspecified;
};
};

config = {
out =
lib.mapAttrs
(k: opt: opt.value)
(lib.filterAttrs
(k: opt: opt.isDefined)
{
inherit (options)
file
environment
;
}
);
};
}
73 changes: 51 additions & 22 deletions src/nix/modules/service/docker-compose-service.nix
Original file line number Diff line number Diff line change
Expand Up @@ -57,29 +57,56 @@ in
default = [];
description = serviceRef "tmpfs";
};
service.build.context = mkOption {
type = nullOr str;
default = null;
description = ''
Locates a Dockerfile to use for creating an image to use in this service.
service.build = mkOption {
default = {};
description = serviceRef "build";
type = submodule ({ options, ...}: {
options = {
_out = mkOption {
internal = true;
readOnly = true;
default = lib.mapAttrs (k: opt: opt.value) (lib.filterAttrs (_: opt: opt.value != null) { inherit (options) context dockerfile target secrets; });
};
context = mkOption {
type = nullOr str;
default = null;
description = ''
Locates a Dockerfile to use for creating an image to use in this service.
https://docs.docker.com/compose/compose-file/build/#context
'';
};
service.build.dockerfile = mkOption {
type = nullOr str;
default = null;
description = ''
Sets an alternate Dockerfile. A relative path is resolved from the build context.
https://docs.docker.com/compose/compose-file/build/#dockerfile
'';
https://docs.docker.com/compose/compose-file/build/#context
'';
};
dockerfile = mkOption {
type = nullOr str;
default = null;
description = ''
Sets an alternate Dockerfile. A relative path is resolved from the build context.
https://docs.docker.com/compose/compose-file/build/#dockerfile
'';
};
target = mkOption {
type = nullOr str;
default = null;
description = ''
Defines the stage to build as defined inside a multi-stage Dockerfile.
https://docs.docker.com/compose/compose-file/build/#target
'';
};
secrets = mkOption {
type = nullOr (listOf str);
default = null;
description = ''
Build-time secrets exposed to the service.
'';
};
};
});
};
service.build.target = mkOption {
type = nullOr str;
default = null;
service.secrets = mkOption {
type = listOf str;
default = [];
description = ''
Defines the stage to build as defined inside a multi-stage Dockerfile.
https://docs.docker.com/compose/compose-file/build/#target
Run-time secrets exposed to the service.
'';
};
service.hostname = mkOption {
Expand Down Expand Up @@ -353,8 +380,8 @@ in
;
} // lib.optionalAttrs (config.service.image != null) {
inherit (config.service) image;
} // lib.optionalAttrs (config.service.build.context != null ) {
build = lib.filterAttrs (n: v: v != null) config.service.build;
} // lib.optionalAttrs (config.service.build._out != {}) {
build = config.service.build._out;
} // lib.optionalAttrs (cap_add != []) {
inherit cap_add;
} // lib.optionalAttrs (cap_drop != []) {
Expand All @@ -379,6 +406,8 @@ in
inherit (config.service) external_links;
} // lib.optionalAttrs (config.service.extra_hosts != []) {
inherit (config.service) extra_hosts;
} // lib.optionalAttrs (config.service.secrets != []) {
inherit (config.service) secrets;
} // lib.optionalAttrs (config.service.hostname != null) {
inherit (config.service) hostname;
} // lib.optionalAttrs (config.service.dns != []) {
Expand Down

0 comments on commit ee9fdc9

Please sign in to comment.