vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / continuous-integration / buildkite-agents.nix
blobfc30172c64999d7a0e089052b7eba1a17bea8cc1
1 { config, lib, pkgs, ... }:
3 let
4   cfg = config.services.buildkite-agents;
6   hooksDir = hooks:
7     let
8       mkHookEntry = name: text: ''
9         ln --symbolic ${pkgs.writeShellApplication { inherit name text; }}/bin/${name} $out/${name}
10       '';
11     in
12     pkgs.runCommandLocal "buildkite-agent-hooks" { } ''
13       mkdir $out
14       ${lib.concatStringsSep "\n" (lib.mapAttrsToList mkHookEntry hooks)}
15     '';
17   buildkiteOptions = { name ? "", config, ... }: {
18     options = {
19       enable = lib.mkOption {
20         default = true;
21         type = lib.types.bool;
22         description = "Whether to enable this buildkite agent";
23       };
25       package = lib.mkOption {
26         default = pkgs.buildkite-agent;
27         defaultText = lib.literalExpression "pkgs.buildkite-agent";
28         description = "Which buildkite-agent derivation to use";
29         type = lib.types.package;
30       };
32       dataDir = lib.mkOption {
33         default = "/var/lib/buildkite-agent-${name}";
34         description = "The workdir for the agent";
35         type = lib.types.str;
36       };
38       extraGroups = lib.mkOption {
39         default = [ "keys" ];
40         description = "Groups the user for this buildkite agent should belong to";
41         type = lib.types.listOf lib.types.str;
42       };
44       runtimePackages = lib.mkOption {
45         default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ];
46         defaultText = lib.literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
47         description = "Add programs to the buildkite-agent environment";
48         type = lib.types.listOf lib.types.package;
49       };
51       tokenPath = lib.mkOption {
52         type = lib.types.path;
53         description = ''
54           The token from your Buildkite "Agents" page.
56           A run-time path to the token file, which is supposed to be provisioned
57           outside of Nix store.
58         '';
59       };
61       name = lib.mkOption {
62         type = lib.types.str;
63         default = "%hostname-${name}-%n";
64         description = ''
65           The name of the agent as seen in the buildkite dashboard.
66         '';
67       };
69       tags = lib.mkOption {
70         type = lib.types.attrsOf (lib.types.either lib.types.str (lib.types.listOf lib.types.str));
71         default = { };
72         example = { queue = "default"; docker = "true"; ruby2 = "true"; };
73         description = ''
74           Tags for the agent.
75         '';
76       };
78       extraConfig = lib.mkOption {
79         type = lib.types.lines;
80         default = "";
81         example = "debug=true";
82         description = ''
83           Extra lines to be added verbatim to the configuration file.
84         '';
85       };
87       privateSshKeyPath = lib.mkOption {
88         type = lib.types.nullOr lib.types.path;
89         default = null;
90         ## maximum care is taken so that secrets (ssh keys and the CI token)
91         ## don't end up in the Nix store.
92         apply = final: if final == null then null else toString final;
94         description = ''
95           OpenSSH private key
97           A run-time path to the key file, which is supposed to be provisioned
98           outside of Nix store.
99         '';
100       };
102       hooks = lib.mkOption {
103         type = lib.types.attrsOf lib.types.lines;
104         default = { };
105         example = lib.literalExpression ''
106           {
107             environment = '''
108               export SECRET_VAR=`head -1 /run/keys/secret`
109             ''';
110           }'';
111         description = ''
112           "Agent" hooks to install.
113           See <https://buildkite.com/docs/agent/v3/hooks> for possible options.
114         '';
115       };
117       hooksPath = lib.mkOption {
118         type = lib.types.path;
119         default = hooksDir config.hooks;
120         defaultText = lib.literalMD "generated from {option}`services.buildkite-agents.<name>.hooks`";
121         description = ''
122           Path to the directory storing the hooks.
123           Consider using {option}`services.buildkite-agents.<name>.hooks.<name>`
124           instead.
125         '';
126       };
128       shell = lib.mkOption {
129         type = lib.types.str;
130         default = "${pkgs.bash}/bin/bash -e -c";
131         defaultText = lib.literalExpression ''"''${pkgs.bash}/bin/bash -e -c"'';
132         description = ''
133           Command that buildkite-agent 3 will execute when it spawns a shell.
134         '';
135       };
136     };
137   };
138   enabledAgents = lib.filterAttrs (n: v: v.enable) cfg;
139   mapAgents = function: lib.mkMerge (lib.mapAttrsToList function enabledAgents);
142   options.services.buildkite-agents = lib.mkOption {
143     type = lib.types.attrsOf (lib.types.submodule buildkiteOptions);
144     default = { };
145     description = ''
146       Attribute set of buildkite agents.
147       The attribute key is combined with the hostname and a unique integer to
148       create the final agent name. This can be overridden by setting the `name`
149       attribute.
150     '';
151   };
153   config.users.users = mapAgents (name: cfg: {
154     "buildkite-agent-${name}" = {
155       name = "buildkite-agent-${name}";
156       home = cfg.dataDir;
157       createHome = true;
158       description = "Buildkite agent user";
159       extraGroups = cfg.extraGroups;
160       isSystemUser = true;
161       group = "buildkite-agent-${name}";
162     };
163   });
164   config.users.groups = mapAgents (name: cfg: {
165     "buildkite-agent-${name}" = { };
166   });
168   config.systemd.services = mapAgents (name: cfg: {
169     "buildkite-agent-${name}" = {
170       description = "Buildkite Agent";
171       wantedBy = [ "multi-user.target" ];
172       after = [ "network.target" ];
173       path = cfg.runtimePackages ++ [ cfg.package pkgs.coreutils ];
174       environment = config.networking.proxy.envVars // {
175         HOME = cfg.dataDir;
176         NIX_REMOTE = "daemon";
177       };
179       ## NB: maximum care is taken so that secrets (ssh keys and the CI token)
180       ##     don't end up in the Nix store.
181       preStart =
182         let
183           sshDir = "${cfg.dataDir}/.ssh";
184           tagStr = name: value:
185             if lib.isList value
186             then lib.concatStringsSep "," (builtins.map (v: "${name}=${v}") value)
187             else "${name}=${value}";
188           tagsStr = lib.concatStringsSep "," (lib.mapAttrsToList tagStr cfg.tags);
189         in
190         lib.optionalString (cfg.privateSshKeyPath != null) ''
191           mkdir -m 0700 -p "${sshDir}"
192           install -m600 "${toString cfg.privateSshKeyPath}" "${sshDir}/id_rsa"
193         '' + ''
194           cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF
195           token="$(cat ${toString cfg.tokenPath})"
196           name="${cfg.name}"
197           shell="${cfg.shell}"
198           tags="${tagsStr}"
199           build-path="${cfg.dataDir}/builds"
200           hooks-path="${cfg.hooksPath}"
201           ${cfg.extraConfig}
202           EOF
203         '';
205       serviceConfig = {
206         ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg";
207         User = "buildkite-agent-${name}";
208         RestartSec = 5;
209         Restart = "on-failure";
210         TimeoutSec = 10;
211         # set a long timeout to give buildkite-agent a chance to finish current builds
212         TimeoutStopSec = "2 min";
213         KillMode = "mixed";
214       };
215     };
216   });
218   config.assertions = mapAgents (name: cfg: [{
219     assertion = cfg.hooksPath != hooksDir cfg.hooks -> cfg.hooks == { };
220     message = ''
221       Options `services.buildkite-agents.${name}.hooksPath' and
222       `services.buildkite-agents.${name}.hooks.<name>' are mutually exclusive.
223     '';
224   }]);