diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/continuous-integration/jenkins')
3 files changed, 501 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix new file mode 100644 index 000000000000..1eca45fbd570 --- /dev/null +++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix @@ -0,0 +1,228 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.services.jenkins; +in { + options = { + services.jenkins = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable the jenkins continuous integration server. + ''; + }; + + user = mkOption { + default = "jenkins"; + type = types.str; + description = '' + User the jenkins server should execute under. + ''; + }; + + group = mkOption { + default = "jenkins"; + type = types.str; + description = '' + If the default user "jenkins" is configured then this is the primary + group of that user. + ''; + }; + + extraGroups = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "wheel" "dialout" ]; + description = '' + List of extra groups that the "jenkins" user should be a part of. + ''; + }; + + home = mkOption { + default = "/var/lib/jenkins"; + type = types.path; + description = '' + The path to use as JENKINS_HOME. If the default user "jenkins" is configured then + this is the home of the "jenkins" user. + ''; + }; + + listenAddress = mkOption { + default = "0.0.0.0"; + example = "localhost"; + type = types.str; + description = '' + Specifies the bind address on which the jenkins HTTP interface listens. + The default is the wildcard address. + ''; + }; + + port = mkOption { + default = 8080; + type = types.int; + description = '' + Specifies port number on which the jenkins HTTP interface listens. + The default is 8080. + ''; + }; + + prefix = mkOption { + default = ""; + example = "/jenkins"; + type = types.str; + description = '' + Specifies a urlPrefix to use with jenkins. + If the example /jenkins is given, the jenkins server will be + accessible using localhost:8080/jenkins. + ''; + }; + + package = mkOption { + default = pkgs.jenkins; + defaultText = "pkgs.jenkins"; + type = types.package; + description = "Jenkins package to use."; + }; + + packages = mkOption { + default = [ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ]; + defaultText = "[ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ]"; + type = types.listOf types.package; + description = '' + Packages to add to PATH for the jenkins process. + ''; + }; + + environment = mkOption { + default = { }; + type = with types; attrsOf str; + description = '' + Additional environment variables to be passed to the jenkins process. + As a base environment, jenkins receives NIX_PATH from + <option>environment.sessionVariables</option>, NIX_REMOTE is set to + "daemon" and JENKINS_HOME is set to the value of + <option>services.jenkins.home</option>. + This option has precedence and can be used to override those + mentioned variables. + ''; + }; + + plugins = mkOption { + default = null; + type = types.nullOr (types.attrsOf types.package); + description = '' + A set of plugins to activate. Note that this will completely + remove and replace any previously installed plugins. If you + have manually-installed plugins that you want to keep while + using this module, set this option to + <literal>null</literal>. You can generate this set with a + tool such as <literal>jenkinsPlugins2nix</literal>. + ''; + example = literalExample '' + import path/to/jenkinsPlugins2nix-generated-plugins.nix { inherit (pkgs) fetchurl stdenv; } + ''; + }; + + extraOptions = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "--debug=9" ]; + description = '' + Additional command line arguments to pass to Jenkins. + ''; + }; + + extraJavaOptions = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "-Xmx80m" ]; + description = '' + Additional command line arguments to pass to the Java run time (as opposed to Jenkins). + ''; + }; + }; + }; + + config = mkIf cfg.enable { + # server references the dejavu fonts + environment.systemPackages = [ + pkgs.dejavu_fonts + ]; + + users.groups = optional (cfg.group == "jenkins") { + name = "jenkins"; + gid = config.ids.gids.jenkins; + }; + + users.users = optional (cfg.user == "jenkins") { + name = "jenkins"; + description = "jenkins user"; + createHome = true; + home = cfg.home; + group = cfg.group; + extraGroups = cfg.extraGroups; + useDefaultShell = true; + uid = config.ids.uids.jenkins; + }; + + systemd.services.jenkins = { + description = "Jenkins Continuous Integration Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + environment = + let + selectedSessionVars = + lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ]) + config.environment.sessionVariables; + in + selectedSessionVars // + { JENKINS_HOME = cfg.home; + NIX_REMOTE = "daemon"; + } // + cfg.environment; + + path = cfg.packages; + + # Force .war (re)extraction, or else we might run stale Jenkins. + + preStart = + let replacePlugins = + if isNull cfg.plugins + then "" + else + let pluginCmds = lib.attrsets.mapAttrsToList + (n: v: "cp ${v} ${cfg.home}/plugins/${n}.hpi") + cfg.plugins; + in '' + rm -r ${cfg.home}/plugins || true + mkdir -p ${cfg.home}/plugins + ${lib.strings.concatStringsSep "\n" pluginCmds} + ''; + in '' + rm -rf ${cfg.home}/war + ${replacePlugins} + ''; + + # For reference: https://wiki.jenkins.io/display/JENKINS/JenkinsLinuxStartupScript + script = '' + ${pkgs.jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${cfg.package}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \ + --httpPort=${toString cfg.port} \ + --prefix=${cfg.prefix} \ + -Djava.awt.headless=true \ + ${concatStringsSep " " cfg.extraOptions} + ''; + + postStart = '' + until [[ $(${pkgs.curl.bin}/bin/curl -L -s --head -w '\n%{http_code}' http://${cfg.listenAddress}:${toString cfg.port}${cfg.prefix} | tail -n1) =~ ^(200|403)$ ]]; do + sleep 1 + done + ''; + + serviceConfig = { + User = cfg.user; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix new file mode 100644 index 000000000000..5d1bfe4ec407 --- /dev/null +++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix @@ -0,0 +1,205 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + jenkinsCfg = config.services.jenkins; + cfg = config.services.jenkins.jobBuilder; + +in { + options = { + services.jenkins.jobBuilder = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Whether or not to enable the Jenkins Job Builder (JJB) service. It + allows defining jobs for Jenkins in a declarative manner. + + Jobs managed through the Jenkins WebUI (or by other means) are left + unchanged. + + Note that it really is declarative configuration; if you remove a + previously defined job, the corresponding job directory will be + deleted. + + Please see the Jenkins Job Builder documentation for more info: + <link xlink:href="http://docs.openstack.org/infra/jenkins-job-builder/"> + http://docs.openstack.org/infra/jenkins-job-builder/</link> + ''; + }; + + accessUser = mkOption { + default = ""; + type = types.str; + description = '' + User id in Jenkins used to reload config. + ''; + }; + + accessToken = mkOption { + default = ""; + type = types.str; + description = '' + User token in Jenkins used to reload config. + WARNING: This token will be world readable in the Nix store. To keep + it secret, use the <option>accessTokenFile</option> option instead. + ''; + }; + + accessTokenFile = mkOption { + default = ""; + type = types.str; + example = "/run/keys/jenkins-job-builder-access-token"; + description = '' + File containing the API token for the <option>accessUser</option> + user. + ''; + }; + + yamlJobs = mkOption { + default = ""; + type = types.lines; + example = '' + - job: + name: jenkins-job-test-1 + builders: + - shell: echo 'Hello world!' + ''; + description = '' + Job descriptions for Jenkins Job Builder in YAML format. + ''; + }; + + jsonJobs = mkOption { + default = [ ]; + type = types.listOf types.str; + example = literalExample '' + [ + ''' + [ { "job": + { "name": "jenkins-job-test-2", + "builders": [ "shell": "echo 'Hello world!'" ] + } + } + ] + ''' + ] + ''; + description = '' + Job descriptions for Jenkins Job Builder in JSON format. + ''; + }; + + nixJobs = mkOption { + default = [ ]; + type = types.listOf types.attrs; + example = literalExample '' + [ { job = + { name = "jenkins-job-test-3"; + builders = [ + { shell = "echo 'Hello world!'"; } + ]; + }; + } + ] + ''; + description = '' + Job descriptions for Jenkins Job Builder in Nix format. + + This is a trivial wrapper around jsonJobs, using builtins.toJSON + behind the scene. + ''; + }; + }; + }; + + config = mkIf (jenkinsCfg.enable && cfg.enable) { + assertions = [ + { assertion = + if cfg.accessUser != "" + then (cfg.accessToken != "" && cfg.accessTokenFile == "") || + (cfg.accessToken == "" && cfg.accessTokenFile != "") + else true; + message = '' + One of accessToken and accessTokenFile options must be non-empty + strings, but not both. Current values: + services.jenkins.jobBuilder.accessToken = "${cfg.accessToken}" + services.jenkins.jobBuilder.accessTokenFile = "${cfg.accessTokenFile}" + ''; + } + ]; + + systemd.services.jenkins-job-builder = { + description = "Jenkins Job Builder Service"; + # JJB can run either before or after jenkins. We chose after, so we can + # always use curl to notify (running) jenkins to reload its config. + after = [ "jenkins.service" ]; + wantedBy = [ "multi-user.target" ]; + + path = with pkgs; [ jenkins-job-builder curl ]; + + # Q: Why manipulate files directly instead of using "jenkins-jobs upload [...]"? + # A: Because this module is for administering a local jenkins install, + # and using local file copy allows us to not worry about + # authentication. + script = + let + yamlJobsFile = builtins.toFile "jobs.yaml" cfg.yamlJobs; + jsonJobsFiles = + map (x: (builtins.toFile "jobs.json" x)) + (cfg.jsonJobs ++ [(builtins.toJSON cfg.nixJobs)]); + jobBuilderOutputDir = "/run/jenkins-job-builder/output"; + # Stamp file is placed in $JENKINS_HOME/jobs/$JOB_NAME/ to indicate + # ownership. Enables tracking and removal of stale jobs. + ownerStamp = ".config-xml-managed-by-nixos-jenkins-job-builder"; + reloadScript = '' + echo "Asking Jenkins to reload config" + curl_opts="--silent --fail --show-error" + access_token=${if cfg.accessTokenFile != "" + then "$(cat '${cfg.accessTokenFile}')" + else cfg.accessToken} + jenkins_url="http://${cfg.accessUser}:$access_token@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}" + crumb=$(curl $curl_opts "$jenkins_url"'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)') + curl $curl_opts -X POST -H "$crumb" "$jenkins_url"/reload + ''; + in + '' + rm -rf ${jobBuilderOutputDir} + cur_decl_jobs=/run/jenkins-job-builder/declarative-jobs + rm -f "$cur_decl_jobs" + + # Create / update jobs + mkdir -p ${jobBuilderOutputDir} + for inputFile in ${yamlJobsFile} ${concatStringsSep " " jsonJobsFiles}; do + HOME="${jenkinsCfg.home}" "${pkgs.jenkins-job-builder}/bin/jenkins-jobs" --ignore-cache test -o "${jobBuilderOutputDir}" "$inputFile" + done + + for file in "${jobBuilderOutputDir}/"*; do + test -f "$file" || continue + jobname="$(basename $file)" + jobdir="${jenkinsCfg.home}/jobs/$jobname" + echo "Creating / updating job \"$jobname\"" + mkdir -p "$jobdir" + touch "$jobdir/${ownerStamp}" + cp "$file" "$jobdir/config.xml" + echo "$jobname" >> "$cur_decl_jobs" + done + + # Remove stale jobs + for file in "${jenkinsCfg.home}"/jobs/*/${ownerStamp}; do + test -f "$file" || continue + jobdir="$(dirname $file)" + jobname="$(basename "$jobdir")" + grep --quiet --line-regexp "$jobname" "$cur_decl_jobs" 2>/dev/null && continue + echo "Deleting stale job \"$jobname\"" + rm -rf "$jobdir" + done + '' + (if cfg.accessUser != "" then reloadScript else ""); + serviceConfig = { + User = jenkinsCfg.user; + RuntimeDirectory = "jenkins-job-builder"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix new file mode 100644 index 000000000000..92deabc3dd3b --- /dev/null +++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix @@ -0,0 +1,68 @@ +{ config, lib, ... }: +with lib; +let + cfg = config.services.jenkinsSlave; + masterCfg = config.services.jenkins; +in { + options = { + services.jenkinsSlave = { + # todo: + # * assure the profile of the jenkins user has a JRE and any specified packages. This would + # enable ssh slaves. + # * Optionally configure the node as a jenkins ad-hoc slave. This would imply configuration + # properties for the master node. + enable = mkOption { + type = types.bool; + default = false; + description = '' + If true the system will be configured to work as a jenkins slave. + If the system is also configured to work as a jenkins master then this has no effect. + In progress: Currently only assures the jenkins user is configured. + ''; + }; + + user = mkOption { + default = "jenkins"; + type = types.str; + description = '' + User the jenkins slave agent should execute under. + ''; + }; + + group = mkOption { + default = "jenkins"; + type = types.str; + description = '' + If the default slave agent user "jenkins" is configured then this is + the primary group of that user. + ''; + }; + + home = mkOption { + default = "/var/lib/jenkins"; + type = types.path; + description = '' + The path to use as JENKINS_HOME. If the default user "jenkins" is configured then + this is the home of the "jenkins" user. + ''; + }; + }; + }; + + config = mkIf (cfg.enable && !masterCfg.enable) { + users.groups = optional (cfg.group == "jenkins") { + name = "jenkins"; + gid = config.ids.gids.jenkins; + }; + + users.users = optional (cfg.user == "jenkins") { + name = "jenkins"; + description = "jenkins user"; + createHome = true; + home = cfg.home; + group = cfg.group; + useDefaultShell = true; + uid = config.ids.uids.jenkins; + }; + }; +} |