at 18.09-beta 8.1 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 cfg = config.services.buildkite-agent; 7 8 mkHookOption = { name, description, example ? null }: { 9 inherit name; 10 value = mkOption { 11 default = null; 12 inherit description; 13 type = types.nullOr types.lines; 14 } // (if example == null then {} else { inherit example; }); 15 }; 16 mkHookOptions = hooks: listToAttrs (map mkHookOption hooks); 17 18 hooksDir = let 19 mkHookEntry = name: value: '' 20 cat > $out/${name} <<'EOF' 21 #! ${pkgs.runtimeShell} 22 set -e 23 ${value} 24 EOF 25 chmod 755 $out/${name} 26 ''; 27 in pkgs.runCommand "buildkite-agent-hooks" {} '' 28 mkdir $out 29 ${concatStringsSep "\n" (mapAttrsToList mkHookEntry (filterAttrs (n: v: v != null) cfg.hooks))} 30 ''; 31 32in 33 34{ 35 options = { 36 services.buildkite-agent = { 37 enable = mkEnableOption "buildkite-agent"; 38 39 package = mkOption { 40 default = pkgs.buildkite-agent; 41 defaultText = "pkgs.buildkite-agent"; 42 description = "Which buildkite-agent derivation to use"; 43 type = types.package; 44 }; 45 46 dataDir = mkOption { 47 default = "/var/lib/buildkite-agent"; 48 description = "The workdir for the agent"; 49 type = types.str; 50 }; 51 52 runtimePackages = mkOption { 53 default = [ pkgs.bash pkgs.nix ]; 54 defaultText = "[ pkgs.bash pkgs.nix ]"; 55 description = "Add programs to the buildkite-agent environment"; 56 type = types.listOf types.package; 57 }; 58 59 tokenPath = mkOption { 60 type = types.path; 61 description = '' 62 The token from your Buildkite "Agents" page. 63 64 A run-time path to the token file, which is supposed to be provisioned 65 outside of Nix store. 66 ''; 67 }; 68 69 name = mkOption { 70 type = types.str; 71 default = "%hostname-%n"; 72 description = '' 73 The name of the agent. 74 ''; 75 }; 76 77 meta-data = mkOption { 78 type = types.str; 79 default = ""; 80 example = "queue=default,docker=true,ruby2=true"; 81 description = '' 82 Meta data for the agent. This is a comma-separated list of 83 <code>key=value</code> pairs. 84 ''; 85 }; 86 87 extraConfig = mkOption { 88 type = types.lines; 89 default = ""; 90 example = "debug=true"; 91 description = '' 92 Extra lines to be added verbatim to the configuration file. 93 ''; 94 }; 95 96 openssh = 97 { privateKeyPath = mkOption { 98 type = types.path; 99 description = '' 100 Private agent key. 101 102 A run-time path to the key file, which is supposed to be provisioned 103 outside of Nix store. 104 ''; 105 }; 106 publicKeyPath = mkOption { 107 type = types.path; 108 description = '' 109 Public agent key. 110 111 A run-time path to the key file, which is supposed to be provisioned 112 outside of Nix store. 113 ''; 114 }; 115 }; 116 117 hooks = mkHookOptions [ 118 { name = "checkout"; 119 description = '' 120 The `checkout` hook script will replace the default checkout routine of the 121 bootstrap.sh script. You can use this hook to do your own SCM checkout 122 behaviour 123 ''; } 124 { name = "command"; 125 description = '' 126 The `command` hook script will replace the default implementation of running 127 the build command. 128 ''; } 129 { name = "environment"; 130 description = '' 131 The `environment` hook will run before all other commands, and can be used 132 to set up secrets, data, etc. Anything exported in hooks will be available 133 to the build script. 134 135 Note: the contents of this file will be copied to the world-readable 136 Nix store. 137 ''; 138 example = '' 139 export SECRET_VAR=`head -1 /run/keys/secret` 140 ''; } 141 { name = "post-artifact"; 142 description = '' 143 The `post-artifact` hook will run just after artifacts are uploaded 144 ''; } 145 { name = "post-checkout"; 146 description = '' 147 The `post-checkout` hook will run after the bootstrap script has checked out 148 your projects source code. 149 ''; } 150 { name = "post-command"; 151 description = '' 152 The `post-command` hook will run after the bootstrap script has run your 153 build commands 154 ''; } 155 { name = "pre-artifact"; 156 description = '' 157 The `pre-artifact` hook will run just before artifacts are uploaded 158 ''; } 159 { name = "pre-checkout"; 160 description = '' 161 The `pre-checkout` hook will run just before your projects source code is 162 checked out from your SCM provider 163 ''; } 164 { name = "pre-command"; 165 description = '' 166 The `pre-command` hook will run just before your build command runs 167 ''; } 168 { name = "pre-exit"; 169 description = '' 170 The `pre-exit` hook will run just before your build job finishes 171 ''; } 172 ]; 173 174 hooksPath = mkOption { 175 type = types.path; 176 default = hooksDir; 177 defaultText = "generated from services.buildkite-agent.hooks"; 178 description = '' 179 Path to the directory storing the hooks. 180 Consider using <option>services.buildkite-agent.hooks.&lt;name&gt;</option> 181 instead. 182 ''; 183 }; 184 }; 185 }; 186 187 config = mkIf config.services.buildkite-agent.enable { 188 users.users.buildkite-agent = 189 { name = "buildkite-agent"; 190 home = cfg.dataDir; 191 createHome = true; 192 description = "Buildkite agent user"; 193 extraGroups = [ "keys" ]; 194 }; 195 196 environment.systemPackages = [ cfg.package ]; 197 198 systemd.services.buildkite-agent = 199 { description = "Buildkite Agent"; 200 wantedBy = [ "multi-user.target" ]; 201 after = [ "network.target" ]; 202 path = cfg.runtimePackages ++ [ pkgs.coreutils ]; 203 environment = config.networking.proxy.envVars // { 204 HOME = cfg.dataDir; 205 NIX_REMOTE = "daemon"; 206 }; 207 208 ## NB: maximum care is taken so that secrets (ssh keys and the CI token) 209 ## don't end up in the Nix store. 210 preStart = let 211 sshDir = "${cfg.dataDir}/.ssh"; 212 in 213 '' 214 mkdir -m 0700 -p "${sshDir}" 215 cp -f "${toString cfg.openssh.privateKeyPath}" "${sshDir}/id_rsa" 216 cp -f "${toString cfg.openssh.publicKeyPath}" "${sshDir}/id_rsa.pub" 217 chmod 600 "${sshDir}"/id_rsa* 218 219 cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF 220 token="$(cat ${toString cfg.tokenPath})" 221 name="${cfg.name}" 222 meta-data="${cfg.meta-data}" 223 build-path="${cfg.dataDir}/builds" 224 hooks-path="${cfg.hooksPath}" 225 ${cfg.extraConfig} 226 EOF 227 ''; 228 229 serviceConfig = 230 { ExecStart = "${pkgs.buildkite-agent}/bin/buildkite-agent start --config /var/lib/buildkite-agent/buildkite-agent.cfg"; 231 User = "buildkite-agent"; 232 RestartSec = 5; 233 Restart = "on-failure"; 234 TimeoutSec = 10; 235 }; 236 }; 237 238 assertions = [ 239 { assertion = cfg.hooksPath == hooksDir || all isNull (attrValues cfg.hooks); 240 message = '' 241 Options `services.buildkite-agent.hooksPath' and 242 `services.buildkite-agent.hooks.<name>' are mutually exclusive. 243 ''; 244 } 245 ]; 246 }; 247 imports = [ 248 (mkRenamedOptionModule [ "services" "buildkite-agent" "token" ] [ "services" "buildkite-agent" "tokenPath" ]) 249 (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "privateKey" ] [ "services" "buildkite-agent" "openssh" "privateKeyPath" ]) 250 (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "publicKey" ] [ "services" "buildkite-agent" "openssh" "publicKeyPath" ]) 251 ]; 252}