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}