diff --git a/modules/module-list.nix b/modules/module-list.nix
index 1be337d..a226bba 100644
--- a/modules/module-list.nix
+++ b/modules/module-list.nix
@@ -44,7 +44,7 @@
./launchd
./services/activate-system
./services/autossh.nix
- ./services/buildkite-agent.nix
+ ./services/buildkite-agents.nix
./services/chunkwm.nix
./services/cachix-agent.nix
./services/dnsmasq.nix
diff --git a/modules/services/buildkite-agent.nix b/modules/services/buildkite-agent.nix
deleted file mode 100644
index ace89ec..0000000
--- a/modules/services/buildkite-agent.nix
+++ /dev/null
@@ -1,253 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
- cfg = config.services.buildkite-agent;
-
- mkHookOption = { name, description, example ? null }: {
- inherit name;
- value = mkOption {
- default = null;
- inherit description;
- type = types.nullOr types.lines;
- } // (if example == null then {} else { inherit example; });
- };
- mkHookOptions = hooks: listToAttrs (map mkHookOption hooks);
-
- hooksDir = let
- mkHookEntry = name: value: {
- inherit name;
- path = pkgs.writeScript "buildkite-agent-hook-${name}" ''
- #! ${pkgs.stdenv.shell}
- set -e
- ${value}
- '';
- };
- in pkgs.linkFarm "buildkite-agent-hooks"
- (mapAttrsToList mkHookEntry (filterAttrs (n: v: v != null) cfg.hooks));
-
-in
-
-{
- options = {
- services.buildkite-agent.enable = mkEnableOption "buildkite-agent";
-
- services.buildkite-agent.package = mkOption {
- default = pkgs.buildkite-agent;
- defaultText = "pkgs.buildkite-agent";
- description = "Which buildkite-agent derivation to use";
- type = types.package;
- };
-
- services.buildkite-agent.dataDir = mkOption {
- default = "/var/lib/buildkite-agent";
- description = "The workdir for the agent";
- type = types.str;
- };
-
- services.buildkite-agent.runtimePackages = mkOption {
- default = [ pkgs.bash pkgs.nix ];
- defaultText = "[ pkgs.bash pkgs.nix ]";
- description = "Add programs to the buildkite-agent environment";
- type = types.listOf types.package;
- };
-
- services.buildkite-agent.tokenPath = mkOption {
- type = types.path;
- description = ''
- The token from your Buildkite "Agents" page.
-
- A run-time path to the token file, which is supposed to be provisioned
- outside of Nix store.
- '';
- };
-
- services.buildkite-agent.name = mkOption {
- type = types.str;
- default = "%hostname-%n";
- description = ''
- The name of the agent.
- '';
- };
-
- services.buildkite-agent.meta-data = mkOption {
- type = types.str;
- default = "";
- example = "queue=default,docker=true,ruby2=true";
- description = ''
- Meta data for the agent. This is a comma-separated list of
- key=value pairs.
- '';
- };
-
- services.buildkite-agent.extraConfig = mkOption {
- type = types.lines;
- default = "";
- example = "debug=true";
- description = ''
- Extra lines to be added verbatim to the configuration file.
- '';
- };
- services.buildkite-agent.preCommands = mkOption {
- type = types.lines;
- default = "";
- description = ''
- Extra commands to run before starting buildkite.
- '';
- };
-
- services.buildkite-agent.openssh =
- { privateKeyPath = mkOption {
- type = types.path;
- description = ''
- Private agent key.
-
- A run-time path to the key file, which is supposed to be provisioned
- outside of Nix store.
- '';
- };
- publicKeyPath = mkOption {
- type = types.path;
- description = ''
- Public agent key.
-
- A run-time path to the key file, which is supposed to be provisioned
- outside of Nix store.
- '';
- };
- };
-
- services.buildkite-agent.hooks = mkHookOptions [
- { name = "checkout";
- description = ''
- The `checkout` hook script will replace the default checkout routine of the
- bootstrap.sh script. You can use this hook to do your own SCM checkout
- behaviour
- ''; }
- { name = "command";
- description = ''
- The `command` hook script will replace the default implementation of running
- the build command.
- ''; }
- { name = "environment";
- description = ''
- The `environment` hook will run before all other commands, and can be used
- to set up secrets, data, etc. Anything exported in hooks will be available
- to the build script.
-
- Note: the contents of this file will be copied to the world-readable
- Nix store.
- '';
- example = ''
- export SECRET_VAR=`head -1 /run/keys/secret`
- ''; }
- { name = "post-artifact";
- description = ''
- The `post-artifact` hook will run just after artifacts are uploaded
- ''; }
- { name = "post-checkout";
- description = ''
- The `post-checkout` hook will run after the bootstrap script has checked out
- your projects source code.
- ''; }
- { name = "post-command";
- description = ''
- The `post-command` hook will run after the bootstrap script has run your
- build commands
- ''; }
- { name = "pre-artifact";
- description = ''
- The `pre-artifact` hook will run just before artifacts are uploaded
- ''; }
- { name = "pre-checkout";
- description = ''
- The `pre-checkout` hook will run just before your projects source code is
- checked out from your SCM provider
- ''; }
- { name = "pre-command";
- description = ''
- The `pre-command` hook will run just before your build command runs
- ''; }
- { name = "pre-exit";
- description = ''
- The `pre-exit` hook will run just before your build job finishes
- ''; }
- ];
- };
-
- config = mkIf config.services.buildkite-agent.enable {
- users.users.buildkite-agent =
- { name = "buildkite-agent";
- home = cfg.dataDir;
- description = "Buildkite agent user";
- };
- users.groups.buildkite-agent =
- { name = "buildkite-agent";
- description = "Buildkite agent user group";
- };
-
- environment.systemPackages = [ cfg.package ];
-
- launchd.daemons.buildkite-agent =
- {
- path = cfg.runtimePackages ++ [ pkgs.coreutils cfg.package ]
- ++ (if pkgs.stdenv.isDarwin then [ pkgs.darwin.DarwinTools ] else []);
- environment = {
- HOME = cfg.dataDir;
- NIX_SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
- } // (if config.nix.useDaemon then { NIX_REMOTE = "daemon"; } else {});
-
- ## NB: maximum care is taken so that secrets (ssh keys and the CI token)
- ## don't end up in the Nix store.
- script = let
- sshDir = "${cfg.dataDir}/.ssh";
- in
- ''
- mkdir -m 0700 -p "${sshDir}"
- cp -f "${toString cfg.openssh.privateKeyPath}" "${sshDir}/id_rsa"
- cp -f "${toString cfg.openssh.publicKeyPath}" "${sshDir}/id_rsa.pub"
- chmod 600 "${sshDir}"/id_rsa*
-
- cat > "${cfg.dataDir}/buildkite-agent.cfg" < $out/${name} <<'EOF'
+ #! ${pkgs.runtimeShell}
+ set -e
+ ${value}
+ EOF
+ chmod 755 $out/${name}
+ '';
+ in pkgs.runCommand "buildkite-agent-hooks" { preferLocalBuild = true; } ''
+ mkdir $out
+ ${concatStringsSep "\n" (mapAttrsToList mkHookEntry (filterAttrs (n: v: v != null) cfg.hooks))}
+ '';
+
+ buildkiteOptions = { name ? "", config, ... }: {
+ options = {
+ enable = mkOption {
+ default = true;
+ type = types.bool;
+ description = mdDoc "Whether to enable this buildkite agent";
+ };
+
+ package = mkOption {
+ default = pkgs.buildkite-agent;
+ defaultText = literalExpression "pkgs.buildkite-agent";
+ description = mdDoc "Which buildkite-agent derivation to use";
+ type = types.package;
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/buildkite-agent-${name}";
+ description = mdDoc "The workdir for the agent";
+ type = types.str;
+ };
+
+ runtimePackages = mkOption {
+ default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ];
+ defaultText = literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
+ description = mdDoc "Add programs to the buildkite-agent environment";
+ type = types.listOf types.package;
+ };
+
+ tokenPath = mkOption {
+ type = types.path;
+ description = mdDoc ''
+ The token from your Buildkite "Agents" page.
+
+ A run-time path to the token file, which is supposed to be provisioned
+ outside of Nix store.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ default = "%hostname-${name}-%n";
+ description = mdDoc ''
+ The name of the agent as seen in the buildkite dashboard.
+ '';
+ };
+
+ tags = mkOption {
+ type = types.attrsOf (types.either types.str (types.listOf types.str));
+ default = {};
+ example = { queue = "default"; docker = "true"; ruby2 ="true"; };
+ description = mdDoc ''
+ Tags for the agent.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "debug=true";
+ description = mdDoc ''
+ Extra lines to be added verbatim to the configuration file.
+ '';
+ };
+
+ preCommands = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra commands to run before starting buildkite.
+ '';
+ };
+
+ privateSshKeyPath = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ ## maximum care is taken so that secrets (ssh keys and the CI token)
+ ## don't end up in the Nix store.
+ apply = final: if final == null then null else toString final;
+
+ description = mdDoc ''
+ OpenSSH private key
+
+ A run-time path to the key file, which is supposed to be provisioned
+ outside of Nix store.
+ '';
+ };
+
+ hooks = mkHookOptions [
+ { name = "checkout";
+ description = ''
+ The `checkout` hook script will replace the default checkout routine of the
+ bootstrap.sh script. You can use this hook to do your own SCM checkout
+ behaviour
+ ''; }
+ { name = "command";
+ description = ''
+ The `command` hook script will replace the default implementation of running
+ the build command.
+ ''; }
+ { name = "environment";
+ description = ''
+ The `environment` hook will run before all other commands, and can be used
+ to set up secrets, data, etc. Anything exported in hooks will be available
+ to the build script.
+
+ Note: the contents of this file will be copied to the world-readable
+ Nix store.
+ '';
+ example = ''
+ export SECRET_VAR=`head -1 /run/keys/secret`
+ ''; }
+ { name = "post-artifact";
+ description = ''
+ The `post-artifact` hook will run just after artifacts are uploaded
+ ''; }
+ { name = "post-checkout";
+ description = ''
+ The `post-checkout` hook will run after the bootstrap script has checked out
+ your projects source code.
+ ''; }
+ { name = "post-command";
+ description = ''
+ The `post-command` hook will run after the bootstrap script has run your
+ build commands
+ ''; }
+ { name = "pre-artifact";
+ description = ''
+ The `pre-artifact` hook will run just before artifacts are uploaded
+ ''; }
+ { name = "pre-checkout";
+ description = ''
+ The `pre-checkout` hook will run just before your projects source code is
+ checked out from your SCM provider
+ ''; }
+ { name = "pre-command";
+ description = ''
+ The `pre-command` hook will run just before your build command runs
+ ''; }
+ { name = "pre-exit";
+ description = ''
+ The `pre-exit` hook will run just before your build job finishes
+ ''; }
+ ];
+
+ hooksPath = mkOption {
+ type = types.path;
+ default = hooksDir config;
+ defaultText = literalMD "generated from {option}`services.buildkite-agents..hooks`";
+ description = mdDoc ''
+ Path to the directory storing the hooks.
+ Consider using {option}`services.buildkite-agents..hooks.`
+ instead.
+ '';
+ };
+
+ shell = mkOption {
+ type = types.str;
+ default = "${pkgs.bash}/bin/bash -e -c";
+ defaultText = literalExpression ''"''${pkgs.bash}/bin/bash -e -c"'';
+ description = mdDoc ''
+ Command that buildkite-agent 3 will execute when it spawns a shell.
+ '';
+ };
+ };
+ };
+ enabledAgents = lib.filterAttrs (n: v: v.enable) cfg;
+ mapAgents = function: lib.mkMerge (lib.mapAttrsToList function enabledAgents);
+in
+{
+ options.services.buildkite-agents = mkOption {
+ type = types.attrsOf (types.submodule buildkiteOptions);
+ default = {};
+ description = mdDoc ''
+ Attribute set of buildkite agents.
+ The attribute key is combined with the hostname and a unique integer to
+ create the final agent name. This can be overridden by setting the `name`
+ attribute.
+ '';
+ };
+
+ config.users.users = mapAgents (name: cfg: {
+ "buildkite-agent-${name}" = {
+ name = "buildkite-agent-${name}";
+ home = cfg.dataDir;
+ createHome = true;
+ description = "Buildkite agent user";
+ };
+ });
+ config.users.groups = mapAgents (name: cfg: {
+ "buildkite-agent-${name}" = {};
+ });
+
+ config.launchd.daemons = mapAgents (name: cfg: {
+ "buildkite-agent-${name}" =
+ { path = cfg.runtimePackages ++ [ cfg.package pkgs.coreutils pkgs.darwin.DarwinTools ];
+ environment = {
+ HOME = cfg.dataDir;
+ }// (if config.nix.useDaemon then { NIX_REMOTE = "daemon"; } else {});
+
+ ## NB: maximum care is taken so that secrets (ssh keys and the CI token)
+ ## don't end up in the Nix store.
+ script = let
+ sshDir = "${cfg.dataDir}/.ssh";
+ tagStr = lib.concatStringsSep "," (lib.mapAttrsToList (name: value: "${name}=${value}") cfg.tags);
+ in
+ optionalString (cfg.privateSshKeyPath != null) ''
+ mkdir -m 0700 -p "${sshDir}"
+ install -m600 "${toString cfg.privateSshKeyPath}" "${sshDir}/id_rsa"
+ '' + ''
+ cat > "${cfg.dataDir}/buildkite-agent.cfg" <' are mutually exclusive.
+ '';
+ }
+ ]);
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "buildkite-agent"] "services.buildkite-agent has been moved to an attribute set at services.buildkite-agents")
+ ];
+}
diff --git a/tests/services-buildkite-agent.nix b/tests/services-buildkite-agent.nix
index 4ca89b8..557aad2 100644
--- a/tests/services-buildkite-agent.nix
+++ b/tests/services-buildkite-agent.nix
@@ -6,22 +6,21 @@ let
in
{
- services.buildkite-agent = {
+ services.buildkite-agents.test = {
enable = true;
package = buildkite-agent;
extraConfig = "yolo=1";
- openssh.privateKeyPath = "/dev/null";
- openssh.publicKeyPath = "/dev/null";
+ privateSshKeyPath = "/dev/null";
hooks.command = "echo test hook";
inherit tokenPath;
};
test = ''
- echo "checking buildkite-agent service in /Library/LaunchDaemons" >&2
- grep "org.nixos.buildkite-agent" ${config.out}/Library/LaunchDaemons/org.nixos.buildkite-agent.plist
+ echo "checking buildkite-agent-test service in /Library/LaunchDaemons" >&2
+ grep "org.nixos.buildkite-agent-test" ${config.out}/Library/LaunchDaemons/org.nixos.buildkite-agent-test.plist
echo "checking creation of buildkite-agent service config" >&2
- script=$(cat ${config.out}/Library/LaunchDaemons/org.nixos.buildkite-agent.plist | awk -F'[< ]' '$3 ~ "^/nix/store/.*" {print $3}')
+ script=$(cat ${config.out}/Library/LaunchDaemons/org.nixos.buildkite-agent-test.plist | awk -F'[< ]' '$3 ~ "^/nix/store/.*" {print $3}')
grep "yolo=1" "$script"
grep "${tokenPath}" "$script"