1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 jenkinsCfg = config.services.jenkins; 7 cfg = config.services.jenkins.jobBuilder; 8 9in { 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 accessUser = mkOption { 33 default = ""; 34 type = types.str; 35 description = '' 36 User id in Jenkins used to reload config. 37 ''; 38 }; 39 40 accessToken = mkOption { 41 default = ""; 42 type = types.str; 43 description = '' 44 User token in Jenkins used to reload config. 45 ''; 46 }; 47 48 yamlJobs = mkOption { 49 default = ""; 50 type = types.lines; 51 example = '' 52 - job: 53 name: jenkins-job-test-1 54 builders: 55 - shell: echo 'Hello world!' 56 ''; 57 description = '' 58 Job descriptions for Jenkins Job Builder in YAML format. 59 ''; 60 }; 61 62 jsonJobs = mkOption { 63 default = [ ]; 64 type = types.listOf types.str; 65 example = literalExample '' 66 [ 67 ''' 68 [ { "job": 69 { "name": "jenkins-job-test-2", 70 "builders": [ "shell": "echo 'Hello world!'" ] 71 } 72 } 73 ] 74 ''' 75 ] 76 ''; 77 description = '' 78 Job descriptions for Jenkins Job Builder in JSON format. 79 ''; 80 }; 81 82 nixJobs = mkOption { 83 default = [ ]; 84 type = types.listOf types.attrs; 85 example = literalExample '' 86 [ { job = 87 { name = "jenkins-job-test-3"; 88 builders = [ 89 { shell = "echo 'Hello world!'"; } 90 ]; 91 }; 92 } 93 ] 94 ''; 95 description = '' 96 Job descriptions for Jenkins Job Builder in Nix format. 97 98 This is a trivial wrapper around jsonJobs, using builtins.toJSON 99 behind the scene. 100 ''; 101 }; 102 }; 103 }; 104 105 config = mkIf (jenkinsCfg.enable && cfg.enable) { 106 systemd.services.jenkins-job-builder = { 107 description = "Jenkins Job Builder Service"; 108 # JJB can run either before or after jenkins. We chose after, so we can 109 # always use curl to notify (running) jenkins to reload its config. 110 after = [ "jenkins.service" ]; 111 wantedBy = [ "multi-user.target" ]; 112 113 path = with pkgs; [ jenkins-job-builder curl ]; 114 115 # Q: Why manipulate files directly instead of using "jenkins-jobs upload [...]"? 116 # A: Because this module is for administering a local jenkins install, 117 # and using local file copy allows us to not worry about 118 # authentication. 119 script = 120 let 121 yamlJobsFile = builtins.toFile "jobs.yaml" cfg.yamlJobs; 122 jsonJobsFiles = 123 map (x: (builtins.toFile "jobs.json" x)) 124 (cfg.jsonJobs ++ [(builtins.toJSON cfg.nixJobs)]); 125 jobBuilderOutputDir = "/run/jenkins-job-builder/output"; 126 # Stamp file is placed in $JENKINS_HOME/jobs/$JOB_NAME/ to indicate 127 # ownership. Enables tracking and removal of stale jobs. 128 ownerStamp = ".config-xml-managed-by-nixos-jenkins-job-builder"; 129 reloadScript = '' 130 echo "Asking Jenkins to reload config" 131 CRUMB=$(curl -s 'http://${cfg.accessUser}:${cfg.accessToken}@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)') 132 curl --silent -X POST -H "$CRUMB" http://${cfg.accessUser}:${cfg.accessToken}@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}/reload 133 ''; 134 in 135 '' 136 rm -rf ${jobBuilderOutputDir} 137 cur_decl_jobs=/run/jenkins-job-builder/declarative-jobs 138 rm -f "$cur_decl_jobs" 139 140 # Create / update jobs 141 mkdir -p ${jobBuilderOutputDir} 142 for inputFile in ${yamlJobsFile} ${concatStringsSep " " jsonJobsFiles}; do 143 HOME="${jenkinsCfg.home}" "${pkgs.jenkins-job-builder}/bin/jenkins-jobs" --ignore-cache test -o "${jobBuilderOutputDir}" "$inputFile" 144 done 145 146 for file in "${jobBuilderOutputDir}/"*; do 147 test -f "$file" || continue 148 jobname="$(basename $file)" 149 jobdir="${jenkinsCfg.home}/jobs/$jobname" 150 echo "Creating / updating job \"$jobname\"" 151 mkdir -p "$jobdir" 152 touch "$jobdir/${ownerStamp}" 153 cp "$file" "$jobdir/config.xml" 154 echo "$jobname" >> "$cur_decl_jobs" 155 done 156 157 # Remove stale jobs 158 for file in "${jenkinsCfg.home}"/jobs/*/${ownerStamp}; do 159 test -f "$file" || continue 160 jobdir="$(dirname $file)" 161 jobname="$(basename "$jobdir")" 162 grep --quiet --line-regexp "$jobname" "$cur_decl_jobs" 2>/dev/null && continue 163 echo "Deleting stale job \"$jobname\"" 164 rm -rf "$jobdir" 165 done 166 '' + (if cfg.accessUser != "" then reloadScript else ""); 167 serviceConfig = { 168 User = jenkinsCfg.user; 169 RuntimeDirectory = "jenkins-job-builder"; 170 }; 171 }; 172 }; 173}