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 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://${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}/reload
148 '';
149 serviceConfig = {
150 User = jenkinsCfg.user;
151 RuntimeDirectory = "jenkins-job-builder";
152 };
153 };
154 };
155}