diff --git a/README.md b/README.md
index 2a4124cd5..475c64698 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,33 @@ puppet module install rtyler/jenkins
```
Then the service should be running at [http://hostname.example.com:8080/](http://hostname.example.com:8080/).
+### Managing Jenkins jobs
+
+
+Build jobs can be managed using the `jenkins::job` define
+
+#### Creating or updating a build job
+```puppet
+ jenkins::job { 'test-build-job':
+ config => template("${templates}/test-build-job.xml.erb"),
+ }
+```
+
+#### Disabling a build job
+```puppet
+ jenkins::job { 'test-build-job':
+ enabled => 0,
+ config => template("${templates}/test-build-job.xml.erb"),
+ }
+```
+
+#### Removing an existing build job
+```puppet
+ jenkins::job { 'test-build-job':
+ ensure => 'absent',
+ }
+```
+
### Installing Jenkins plugins
diff --git a/contrib/examples/job-configuration/build.pp b/contrib/examples/job-configuration/build.pp
new file mode 100644
index 000000000..2713aa832
--- /dev/null
+++ b/contrib/examples/job-configuration/build.pp
@@ -0,0 +1,20 @@
+class jenkins::job::build(
+ $config = undef,
+ $jobname = $title,
+ $enabled = 1,
+ $ensure = 'present',
+) {
+
+ if $config == undef {
+ $real_content = template('jenkins/job/build.xml.erb')
+ } else {
+ $real_content = $config
+ }
+
+ jenkins::job { 'build':
+ config => $real_content,
+ jobname => $jobname,
+ enabled => $enabled,
+ ensure => $ensure,
+ }
+}
diff --git a/contrib/examples/job-configuration/templates/build.xml.erb b/contrib/examples/job-configuration/templates/build.xml.erb
new file mode 100644
index 000000000..26eab7b3d
--- /dev/null
+++ b/contrib/examples/job-configuration/templates/build.xml.erb
@@ -0,0 +1,17 @@
+
+
+
+
+ false
+
+
+ true
+ false
+ false
+ false
+
+ false
+
+
+
+
diff --git a/manifests/cli.pp b/manifests/cli.pp
index 34f22f63c..6afd9ebfe 100644
--- a/manifests/cli.pp
+++ b/manifests/cli.pp
@@ -18,6 +18,41 @@
path => ['/bin', '/usr/bin'],
cwd => '/tmp',
creates => $jar,
- require => Package['jenkins'],
+ require => Service['jenkins'],
+ }
+
+ file { $jar:
+ ensure => file,
+ require => Exec['jenkins-cli'],
+ }
+
+ # Get the value of JENKINS_PORT from config_hash or default
+ $hash = $::jenkins::config_hash
+ if is_hash($hash) and has_key($hash, 'JENKINS_PORT') and
+ has_key($hash['JENKINS_PORT'], 'value') {
+ $port = $hash['JENKINS_PORT']['value']
+ } else {
+ $port = '8080'
+ }
+
+ # The jenkins cli command with required parameter(s)
+ $cmd = "java -jar ${jar} -s http://localhost:${port}"
+
+ # Reload all Jenkins config from disk (only when notified)
+ exec { 'reload-jenkins':
+ command => "${cmd} reload-configuration",
+ tries => 10,
+ try_sleep => 2,
+ refreshonly => true,
+ require => File[$jar],
+ }
+
+ # Do a safe restart of Jenkins (only when notified)
+ exec { 'safe-restart-jenkins':
+ command => "${cmd} safe-restart && /bin/sleep 10",
+ tries => 10,
+ try_sleep => 2,
+ refreshonly => true,
+ require => File[$jar],
}
}
diff --git a/manifests/init.pp b/manifests/init.pp
index d0aabbc0f..235bea593 100644
--- a/manifests/init.pp
+++ b/manifests/init.pp
@@ -83,6 +83,7 @@
$service_ensure = $jenkins::params::service_ensure,
$config_hash = {},
$plugin_hash = {},
+ $job_hash = {},
$configure_firewall = undef,
$install_java = $jenkins::params::install_java,
$proxy_host = undef,
@@ -131,6 +132,7 @@
include jenkins::firewall
}
}
+
if $cli {
include jenkins::cli
}
@@ -138,10 +140,19 @@
Anchor['jenkins::begin'] ->
Class['jenkins::package'] ->
Class['jenkins::config'] ->
- Class['jenkins::plugins']~>
+ Class['jenkins::plugins'] ~>
Class['jenkins::service'] ->
+ Class['jenkins::jobs'] ->
Anchor['jenkins::end']
+ if $cli {
+ Anchor['jenkins::begin'] ->
+ Class['jenkins::service'] ->
+ Class['jenkins::cli'] ->
+ Class['jenkins::jobs'] ->
+ Anchor['jenkins::end']
+ }
+
if $install_java {
Anchor['jenkins::begin'] ->
Class['java'] ->
diff --git a/manifests/job.pp b/manifests/job.pp
new file mode 100644
index 000000000..6568b82c3
--- /dev/null
+++ b/manifests/job.pp
@@ -0,0 +1,38 @@
+# Define: jenkins::job
+#
+# This class create a new jenkins job given a name and config xml
+#
+# Parameters:
+#
+# config
+# the content of the jenkins job config file (required)
+#
+# jobname = $title
+# the name of the jenkins job
+#
+# enabled = true
+# whether to enable the job
+#
+# ensure = 'present'
+# choose 'absent' to ensure the job is removed
+#
+define jenkins::job(
+ $config,
+ $jobname = $title,
+ $enabled = 1,
+ $ensure = 'present',
+){
+
+ if ($ensure == 'absent') {
+ jenkins::job::absent { $title:
+ jobname => $jobname,
+ }
+ } else {
+ jenkins::job::present { $title:
+ config => $config,
+ jobname => $jobname,
+ enabled => $enabled,
+ }
+ }
+
+}
diff --git a/manifests/job/absent.pp b/manifests/job/absent.pp
new file mode 100644
index 000000000..be22c7585
--- /dev/null
+++ b/manifests/job/absent.pp
@@ -0,0 +1,39 @@
+# Define: jenkins::job::absent
+#
+# Removes a jenkins build job
+#
+# Parameters:
+#
+# config
+# the content of the jenkins job config file (required)
+#
+# jobname = $title
+# the name of the jenkins job
+#
+define jenkins::job::absent(
+ $jobname = $title,
+){
+ include jenkins::cli
+
+ if $jenkins::service_ensure == 'stopped' or $jenkins::service_ensure == false {
+ fail('Management of Jenkins jobs requires \$jenkins::service_ensure to be set to \'running\'')
+ }
+
+ $tmp_config_path = "/tmp/${jobname}-config.xml"
+ $job_dir = "/var/lib/jenkins/jobs/${jobname}"
+ $config_path = "${job_dir}/config.xml"
+
+ # Temp file to use as stdin for Jenkins CLI executable
+ file { $tmp_config_path:
+ ensure => absent,
+ }
+
+ # Delete the job
+ exec { "jenkins delete-job ${jobname}":
+ command => "${jenkins::cli::cmd} delete-job ${jobname}",
+ logoutput => false,
+ onlyif => "test -f ${config_path}",
+ require => Exec['jenkins-cli'],
+ }
+
+}
diff --git a/manifests/job/present.pp b/manifests/job/present.pp
new file mode 100644
index 000000000..a0a2fc4ef
--- /dev/null
+++ b/manifests/job/present.pp
@@ -0,0 +1,100 @@
+# Define: jenkins::job::present
+#
+# Creates or updates a jenkins build job
+#
+# Parameters:
+#
+# config
+# the content of the jenkins job config file (required)
+#
+# jobname = $title
+# the name of the jenkins job
+#
+# enabled = 1
+# if the job should be enabled
+#
+define jenkins::job::present(
+ $config,
+ $jobname = $title,
+ $enabled = 1,
+){
+ include jenkins::cli
+
+ if $jenkins::service_ensure == 'stopped' or $jenkins::service_ensure == false {
+ fail('Management of Jenkins jobs requires \$jenkins::service_ensure to be set to \'running\'')
+ }
+
+ $jenkins_cli = $jenkins::cli::cmd
+ $tmp_config_path = "/tmp/${jobname}-config.xml"
+ $job_dir = "/var/lib/jenkins/jobs/${jobname}"
+ $config_path = "${job_dir}/config.xml"
+
+ Exec {
+ logoutput => false,
+ path => '/bin:/usr/bin:/sbin:/usr/sbin',
+ tries => 5,
+ try_sleep => 5,
+ }
+
+ #
+ # When a Jenkins job is imported via the cli, Jenkins will
+ # re-format the xml file based on its own internal rules.
+ # In order to make job management idempotent, we need to
+ # apply that formatting before the import, so we can do a diff
+ # on any pre-existing job to determine if an update is needed.
+ #
+ # Jenkins likes to change single quotes to double quotes
+ $a = regsubst($config, 'version=\'1.0\' encoding=\'UTF-8\'',
+ 'version="1.0" encoding="UTF-8"')
+ # Change empty tags into self-closing tags
+ $b = regsubst($a, '<([a-z]+)><\/\1>', '<\1/>', 'IG')
+ # Change " to " since Jenkins is wierd like that
+ $c = regsubst($b, '"', '"', 'MG')
+
+ # Temp file to use as stdin for Jenkins CLI executable
+ file { $tmp_config_path:
+ content => $c,
+ require => Exec['jenkins-cli'],
+ }
+
+ # Use Jenkins CLI to create the job
+ $cat_config = "cat ${tmp_config_path}"
+ $create_job = "${jenkins_cli} create-job ${jobname}"
+ exec { "jenkins create-job ${jobname}":
+ command => "${cat_config} | ${create_job}",
+ creates => [$config_path, "${job_dir}/builds"],
+ require => File[$tmp_config_path],
+ }
+
+ # Use Jenkins CLI to update the job if it already exists
+ $update_job = "${jenkins_cli} update-job ${jobname}"
+ exec { "jenkins update-job ${jobname}":
+ command => "${cat_config} | ${update_job}",
+ onlyif => "test -e ${config_path}",
+ unless => "diff -b -q ${config_path} ${tmp_config_path}",
+ require => File[$tmp_config_path],
+ notify => Exec['reload-jenkins'],
+ }
+
+ # Enable or disable the job (if necessary)
+ if ($enabled == 1) {
+ exec { "jenkins enable-job ${jobname}":
+ command => "${jenkins_cli} enable-job ${jobname}",
+ onlyif => "cat ${config_path} | grep 'true'",
+ require => [
+ Exec["jenkins create-job ${jobname}"],
+ Exec["jenkins update-job ${jobname}"],
+ ],
+ }
+ } else {
+ exec { "jenkins disable-job ${jobname}":
+ command => "${jenkins_cli} disable-job ${jobname}",
+ onlyif => "cat ${config_path} | grep 'false'",
+ require => [
+ Exec["jenkins create-job ${jobname}"],
+ Exec["jenkins update-job ${jobname}"],
+ ],
+ }
+ }
+
+}
diff --git a/manifests/jobs.pp b/manifests/jobs.pp
new file mode 100644
index 000000000..3a6aa0d9b
--- /dev/null
+++ b/manifests/jobs.pp
@@ -0,0 +1,11 @@
+# Class: jenkins::jobs
+#
+class jenkins::jobs {
+
+ if $caller_module_name != $module_name {
+ fail("Use of private class ${name} by ${caller_module_name}")
+ }
+
+ create_resources('jenkins::job',$::jenkins::job_hash)
+
+}
diff --git a/spec/classes/jenkins_cli_spec.rb b/spec/classes/jenkins_cli_spec.rb
index 60f9caf5d..7d40f5093 100644
--- a/spec/classes/jenkins_cli_spec.rb
+++ b/spec/classes/jenkins_cli_spec.rb
@@ -9,9 +9,14 @@
end
context '$cli => true' do
- let(:params) { { :cli => true } }
+ let(:params) {{ :cli => true,
+ :config_hash => { 'JENKINS_PORT' => { 'value' => '9000' } }
+ }}
it { should create_class('jenkins::cli') }
it { should contain_exec('jenkins-cli') }
+ it { should contain_exec('reload-jenkins').with_command(/http:\/\/localhost:9000/) }
+ it { should contain_exec('safe-restart-jenkins') }
+ it { should contain_jenkins__sysconfig('JENKINS_PORT').with_value('9000') }
end
end
diff --git a/spec/classes/jenkins_config_spec.rb b/spec/classes/jenkins_config_spec.rb
index e29e10a61..3b8465041 100644
--- a/spec/classes/jenkins_config_spec.rb
+++ b/spec/classes/jenkins_config_spec.rb
@@ -10,7 +10,7 @@
context 'create config' do
let(:params) { { :config_hash => { 'AJP_PORT' => { 'value' => '1234' } } }}
- it { should contain_jenkins__sysconfig('AJP_PORT') }
+ it { should contain_jenkins__sysconfig('AJP_PORT').with_value('1234') }
end
end
diff --git a/spec/classes/jenkins_jobs_spec.rb b/spec/classes/jenkins_jobs_spec.rb
new file mode 100644
index 000000000..58f87db52
--- /dev/null
+++ b/spec/classes/jenkins_jobs_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe 'jenkins', :type => :module do
+ let(:facts) { { :osfamily => 'RedHat', :operatingsystem => 'RedHat' } }
+
+ context 'jobs' do
+ context 'default' do
+ it { should contain_class('jenkins::jobs') }
+ end
+
+ context 'with one job' do
+ let(:params) { { :job_hash => { 'build' => { 'config' => '' } } } }
+ it { should contain_jenkins__job('build').with_config('') }
+ end
+
+ context 'with cli disabled' do
+ let(:params) { { :service_ensure => 'stopped',
+ :cli => false,
+ :job_hash => { 'build' => { 'config' => '' } } } }
+ it { expect { should compile }.to raise_error }
+ end
+
+ end
+
+end
diff --git a/spec/defines/jenkins_job_spec.rb b/spec/defines/jenkins_job_spec.rb
new file mode 100644
index 000000000..f8d8bd6c9
--- /dev/null
+++ b/spec/defines/jenkins_job_spec.rb
@@ -0,0 +1,96 @@
+require 'spec_helper'
+
+describe 'jenkins::job' do
+ let(:title) { 'myjob' }
+
+ describe 'with defaults' do
+ let(:params) {{ :config => '' }}
+ it { should contain_exec('jenkins create-job myjob') }
+ it { should contain_exec('jenkins update-job myjob') }
+ it { should contain_exec('jenkins enable-job myjob') }
+ it { should_not contain_exec('jenkins disable-job myjob') }
+ it { should_not contain_exec('jenkins delete-job myjob') }
+ end
+
+ describe 'with job enabled' do
+ let(:params) {{ :enabled => 1 , :config => '' }}
+ it { should contain_exec('jenkins create-job myjob') }
+ it { should contain_exec('jenkins update-job myjob') }
+ it { should contain_exec('jenkins enable-job myjob') }
+ it { should_not contain_exec('jenkins disable-job myjob') }
+ it { should_not contain_exec('jenkins delete-job myjob') }
+ end
+
+ describe 'with job disabled' do
+ let(:params) {{ :enabled => 0 , :config => '' }}
+ it { should contain_exec('jenkins create-job myjob') }
+ it { should contain_exec('jenkins update-job myjob') }
+ it { should_not contain_exec('jenkins enable-job myjob') }
+ it { should contain_exec('jenkins disable-job myjob') }
+ it { should_not contain_exec('jenkins delete-job myjob') }
+ end
+
+ describe 'with job present' do
+ let(:params) {{ :ensure => 'present', :config => '' }}
+ it { should contain_exec('jenkins create-job myjob') }
+ it { should contain_exec('jenkins update-job myjob') }
+ it { should contain_exec('jenkins enable-job myjob') }
+ it { should_not contain_exec('jenkins disable-job myjob') }
+ it { should_not contain_exec('jenkins delete-job myjob') }
+ end
+
+ describe 'with job absent' do
+ let(:params) {{ :ensure => 'absent', :config => '' }}
+ it { should_not contain_exec('jenkins create-job myjob') }
+ it { should_not contain_exec('jenkins update-job myjob') }
+ it { should_not contain_exec('jenkins enable-job myjob') }
+ it { should_not contain_exec('jenkins disable-job myjob') }
+ it { should contain_exec('jenkins delete-job myjob') }
+ end
+
+ describe 'with unformatted config' do
+ unformatted_config = <
+
+ ...
+
+ "..."
+
+eos
+ formatted_config = <
+
+ ...
+
+ "..."
+
+eos
+
+ let(:params) {{ :ensure => 'present',
+ :config => unformatted_config }}
+ it { should contain_file('/tmp/myjob-config.xml')\
+ .with_content(formatted_config) }
+ end
+
+ describe 'with config with single quotes' do
+ quotes = ""
+ let(:params) {{ :ensure => 'present', :config => quotes }}
+ it { should contain_file('/tmp/myjob-config.xml')\
+ .with_content(/version="1\.0" encoding="UTF-8"/) }
+ end
+
+ describe 'with config with empty tags' do
+ empty_tags = ''
+ let(:params) {{ :ensure => 'present', :config => empty_tags }}
+ it { should contain_file('/tmp/myjob-config.xml')\
+ .with_content('') }
+ end
+
+ describe 'with config with "' do
+ quotes = "the dog said "woof""
+ let(:params) {{ :ensure => 'present', :config => quotes }}
+ it { should contain_file('/tmp/myjob-config.xml')\
+ .with_content('the dog said "woof"') }
+ end
+
+end
diff --git a/tests/RedHatEnterpriseServer.pp b/tests/RedHatEnterpriseServer.pp
index 82da0777e..c9afbcef4 100644
--- a/tests/RedHatEnterpriseServer.pp
+++ b/tests/RedHatEnterpriseServer.pp
@@ -5,4 +5,25 @@
'ansicolor' :
version => '0.3.1';
}
+
+ jenkins::job {
+ 'build' :
+ config => '
+
+
+
+ false
+
+
+ true
+ false
+ false
+ false
+
+ false
+
+
+
+';
+ }
}
diff --git a/tests/Ubuntu.pp b/tests/Ubuntu.pp
index 82da0777e..c9afbcef4 100644
--- a/tests/Ubuntu.pp
+++ b/tests/Ubuntu.pp
@@ -5,4 +5,25 @@
'ansicolor' :
version => '0.3.1';
}
+
+ jenkins::job {
+ 'build' :
+ config => '
+
+
+
+ false
+
+
+ true
+ false
+ false
+ false
+
+ false
+
+
+
+';
+ }
}