lol

nixos: add services.jenkins.jobBuilder option

This option allows to define (declarative) Jenkins jobs, using Jenkins
Job Builder (JJB) as backend.

Example:

services.jenkins = {
enable = true;
jobBuilder = {
enable = true;
yamlJobs = ''
- job:
name: jenkins-job-test
builders:
- shell: echo 'Hello world!'
'';
};
};

Jobs can be defined using YAML, JSON and Nix.

Note that it really is declarative configuration; if you remove a
previously defined job, the module will remove the jobdir under
$JENKINS_HOME.

Jobs managed through the Jenkins WebUI (or by other means) are not
touched by this module.

Changes v1 -> v2:
* add nixJobs
* let jsonJobs take a list of strings (allows merge)
* 4 space indent in shell code

+156
+1
nixos/modules/module-list.nix
··· 118 118 ./services/computing/slurm/slurm.nix 119 119 ./services/continuous-integration/jenkins/default.nix 120 120 ./services/continuous-integration/jenkins/slave.nix 121 + ./services/continuous-integration/jenkins/job-builder.nix 121 122 ./services/databases/4store-endpoint.nix 122 123 ./services/databases/4store.nix 123 124 ./services/databases/couchdb.nix
+155
nixos/modules/services/continuous-integration/jenkins/job-builder.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + with lib; 4 + 5 + let 6 + jenkinsCfg = config.services.jenkins; 7 + cfg = config.services.jenkins.jobBuilder; 8 + 9 + in { 10 + options = { 11 + services.jenkins.jobBuilder = { 12 + enable = mkOption { 13 + type = types.bool; 14 + default = false; 15 + description = '' 16 + Whether or not to enable the Jenkins Job Builder (JJB) service. It 17 + allows defining jobs for Jenkins in a declarative manner. 18 + 19 + Jobs managed through the Jenkins WebUI (or by other means) are left 20 + unchanged. 21 + 22 + Note that it really is declarative configuration; if you remove a 23 + previously defined job, the corresponding job directory will be 24 + deleted. 25 + 26 + Please see the Jenkins Job Builder documentation for more info: 27 + <link xlink:href="http://docs.openstack.org/infra/jenkins-job-builder/"> 28 + http://docs.openstack.org/infra/jenkins-job-builder/</link> 29 + ''; 30 + }; 31 + 32 + yamlJobs = mkOption { 33 + default = ""; 34 + type = types.lines; 35 + example = '' 36 + - job: 37 + name: jenkins-job-test-1 38 + builders: 39 + - shell: echo 'Hello world!' 40 + ''; 41 + description = '' 42 + Job descriptions for Jenkins Job Builder in YAML format. 43 + ''; 44 + }; 45 + 46 + jsonJobs = mkOption { 47 + default = [ ]; 48 + type = types.listOf types.str; 49 + example = literalExample '' 50 + [ 51 + ''' 52 + [ { "job": 53 + { "name": "jenkins-job-test-2", 54 + "builders": [ "shell": "echo 'Hello world!'" ] 55 + } 56 + } 57 + ] 58 + ''' 59 + ] 60 + ''; 61 + description = '' 62 + Job descriptions for Jenkins Job Builder in JSON format. 63 + ''; 64 + }; 65 + 66 + nixJobs = mkOption { 67 + default = [ ]; 68 + type = types.listOf types.attrs; 69 + example = literalExample '' 70 + [ { job = 71 + { name = "jenkins-job-test-3"; 72 + builders = [ 73 + { shell = "echo 'Hello world!'"; } 74 + ]; 75 + }; 76 + } 77 + ]; 78 + ''; 79 + description = '' 80 + Job descriptions for Jenkins Job Builder in Nix format. 81 + 82 + This is a trivial wrapper around jsonJobs, using builtins.toJSON 83 + behind the scene. 84 + ''; 85 + }; 86 + }; 87 + }; 88 + 89 + config = mkIf (jenkinsCfg.enable && cfg.enable) { 90 + systemd.services.jenkins-job-builder = { 91 + description = "Jenkins Job Builder Service"; 92 + # JJB can run either before or after jenkins. We chose after, so we can 93 + # always use curl to notify (running) jenkins to reload its config. 94 + after = [ "jenkins.service" ]; 95 + wantedBy = [ "multi-user.target" ]; 96 + 97 + path = with pkgs; [ jenkins-job-builder curl ]; 98 + 99 + # Q: Why manipulate files directly instead of using "jenkins-jobs upload [...]"? 100 + # A: Because this module is for administering a local jenkins install, 101 + # and using local file copy allows us to not worry about 102 + # authentication. 103 + script = 104 + let 105 + yamlJobsFile = builtins.toFile "jobs.yaml" cfg.yamlJobs; 106 + jsonJobsFiles = 107 + map (x: (builtins.toFile "jobs.json" x)) 108 + (cfg.jsonJobs ++ [(builtins.toJSON cfg.nixJobs)]); 109 + jobBuilderOutputDir = "/run/jenkins-job-builder/output"; 110 + # Stamp file is placed in $JENKINS_HOME/jobs/$JOB_NAME/ to indicate 111 + # ownership. Enables tracking and removal of stale jobs. 112 + ownerStamp = ".config-xml-managed-by-nixos-jenkins-job-builder"; 113 + in 114 + '' 115 + rm -rf ${jobBuilderOutputDir} 116 + cur_decl_jobs=/run/jenkins-job-builder/declarative-jobs 117 + rm -f "$cur_decl_jobs" 118 + 119 + # Create / update jobs 120 + mkdir -p ${jobBuilderOutputDir} 121 + for inputFile in ${yamlJobsFile} ${concatStringsSep " " jsonJobsFiles}; do 122 + HOME="${jenkinsCfg.home}" "${pkgs.jenkins-job-builder}/bin/jenkins-jobs" --ignore-cache test -o "${jobBuilderOutputDir}" "$inputFile" 123 + done 124 + 125 + for file in "${jobBuilderOutputDir}/"*; do 126 + test -f "$file" || continue 127 + jobname="$(basename $file)" 128 + jobdir="${jenkinsCfg.home}/jobs/$jobname" 129 + echo "Creating / updating job \"$jobname\"" 130 + mkdir -p "$jobdir" 131 + touch "$jobdir/${ownerStamp}" 132 + cp "$file" "$jobdir/config.xml" 133 + echo "$jobname" >> "$cur_decl_jobs" 134 + done 135 + 136 + # Remove stale jobs 137 + for file in "${jenkinsCfg.home}"/jobs/*/${ownerStamp}; do 138 + test -f "$file" || continue 139 + jobdir="$(dirname $file)" 140 + jobname="$(basename "$jobdir")" 141 + grep --quiet --line-regexp "$jobname" "$cur_decl_jobs" 2>/dev/null && continue 142 + echo "Deleting stale job \"$jobname\"" 143 + rm -rf "$jobdir" 144 + done 145 + 146 + echo "Asking Jenkins to reload config" 147 + curl --silent -X POST http://localhost:${toString jenkinsCfg.port}/reload 148 + ''; 149 + serviceConfig = { 150 + User = jenkinsCfg.user; 151 + RuntimeDirectory = "jenkins-job-builder"; 152 + }; 153 + }; 154 + }; 155 + }