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