require "builder"
module Jenkins
class JobConfigBuilder
attr_accessor :job_type
attr_accessor :steps, :rubies
attr_accessor :triggers
attr_accessor :publishers
attr_accessor :log_rotate
attr_accessor :scm, :public_scm, :scm_branches
attr_accessor :assigned_node, :node_labels # TODO just one of these
attr_accessor :envfile
InvalidTemplate = Class.new(StandardError)
VALID_JOB_TEMPLATES = %w[none rails rails3 ruby rubygem erlang]
JOB_TRIGGER_THRESHOLDS = {
"SUCCESS" => {:ordinal => 0, :color => "BLUE"},
"UNSTABLE" => {:ordinal => 1, :color => "YELLOW"},
"FAILURE" => {:ordinal => 2, :color => "RED"}
}
# +job_type+ - template of default steps to create with the job
# +steps+ - array of [:method, cmd], e.g. [:build_shell_step, "bundle initial"]
# - Default is based on +job_type+.
# +scm+ - URL to the repository. Currently only support git URLs.
# +public_scm+ - convert the +scm+ URL to a publicly accessible URL for the Jenkins job config.
# +scm_branches+ - array of branches to run builds. Default: ['master']
# +rubies+ - list of RVM rubies to run tests (via Jenkins Axes).
# +triggers+ - list of triggers to start the build. Currently only support time triggers
# +assigned_node+ - restrict this job to running on slaves with these labels (space separated)
# +publishers+ - define publishers to be performed after a build
# +log_rotate+ - define log rotation
def initialize(job_type = :ruby, &block)
self.job_type = job_type.to_s if job_type
yield self
self.scm_branches ||= ["master"]
raise InvalidTemplate unless VALID_JOB_TEMPLATES.include?(job_type.to_s)
end
def builder
b = Builder::XmlMarkup.new :indent => 2
b.instruct!
b.tag!(matrix_project? ? "matrix-project" : "project") do
b.actions
b.description
build_log_rotator b
b.keepDependencies false
b.properties
build_scm b
b.assignedNode assigned_node if assigned_node
b.canRoam !assigned_node
b.disabled false
b.blockBuildWhenUpstreamBuilding false
build_triggers b
b.concurrentBuild false
build_axes b if matrix_project?
build_steps b
build_publishers b
build_wrappers b
b.runSequentially false if matrix_project?
end
end
def to_xml
builder.to_s
end
protected
# ...
def build_scm(b)
if scm && scm =~ /git/
scm_url = public_scm ? public_only_git_scm(scm) : scm
b.scm :class => "hudson.plugins.git.GitSCM" do
b.configVersion 1
b.remoteRepositories do
b.tag! "org.spearce.jgit.transport.RemoteConfig" do
b.string "origin"
b.int 5
b.string "fetch"
b.string "+refs/heads/*:refs/remotes/origin/*"
b.string "receivepack"
b.string "git-upload-pack"
b.string "uploadpack"
b.string "git-upload-pack"
b.string "url"
b.string scm_url
b.string "tagopt"
b.string
end
end
if scm_branches
b.branches do
scm_branches.each do |branch|
b.tag! "hudson.plugins.git.BranchSpec" do
b.name branch
end
end
end
end
b.localBranch
b.mergeOptions
b.recursiveSubmodules false
b.doGenerateSubmoduleConfigurations false
b.authorOrCommitter false
b.clean false
b.wipeOutWorkspace false
b.buildChooser :class => "hudson.plugins.git.util.DefaultBuildChooser"
b.gitTool "Default"
b.submoduleCfg :class => "list"
b.relativeTargetDir
b.excludedRegions
b.excludedUsers
end
end
end
def matrix_project?
!(rubies.blank? && node_labels.blank?)
end
#
# RUBY_VERSION
#
# 1.8.7
# 1.9.2
# rbx-head
# jruby
#
#
#
# label
#
# 1.8.7
# ubuntu
#
#
def build_axes(b)
b.axes do
unless rubies.blank?
b.tag! "hudson.matrix.TextAxis" do
b.name "RUBY_VERSION"
b.values do
rubies.each do |rvm_name|
b.string rvm_name
end
end
end
end
unless node_labels.blank?
b.tag! "hudson.matrix.LabelAxis" do
b.name "label"
b.values do
node_labels.each do |label|
b.string label
end
end
end
end
end
end
# Example:
#
#
# /path/to/env/file
#
#
def build_wrappers(b)
if envfile
b.buildWrappers do
self.envfile = [envfile] unless envfile.is_a?(Array)
b.tag! "hudson.plugins.envfile.EnvFileBuildWrapper" do
envfile.each do |file|
b.filePath file
end
end
end
else
b.buildWrappers
end
end
# Example
#
#
# * * * * *
#
#
def build_triggers(b)
if triggers
b.triggers :class => "vector" do
triggers.each do |trigger|
case trigger[:class]
when :timer
b.tag! "hudson.triggers.TimerTrigger" do
b.spec trigger[:spec]
end
end
end
end
else
b.triggers :class => "vector"
end
end
# Example
#
# 14
# -1
# -1
# -1
#
def build_log_rotator(b)
if log_rotate
b.logRotator do
b.daysToKeep log_rotate[:days_to_keep] || -1
b.numToKeep log_rotate[:num_to_keep] || -1
b.artifactDaysToKeep log_rotate[:artifact_days_to_keep] || -1
b.artifactNumToKeep log_rotate[:artifact_num_to_keep] || -1
end
end
end
# Example
#
#
#
#
#
# Dependent Job, Even more dependent job
#
# SUCCESS
# 0
# BLUE
#
#
#
# some.guy@example.com, another.guy@example.com
# false
# true
#
#
def build_publishers(b)
if publishers
b.publishers do
publishers.each do |publisher|
publisher_name, params = publisher.to_a.first
case publisher_name
when :mailer
b.tag! "hudson.tasks.Mailer" do
b.recipients params.join(', ')
b.dontNotifyEveryUnstableBuild false
b.sendToIndividuals true
end
when :job_triggers
b.tag! "hudson.tasks.BuildTrigger" do
b.childProjects params[:projects].join(', ')
b.threshold do
trigger_event = params[:on] || "SUCCESS"
b.name trigger_event
b.ordinal JOB_TRIGGER_THRESHOLDS[trigger_event][:ordinal]
b.color JOB_TRIGGER_THRESHOLDS[trigger_event][:color]
end
end
when :chuck_norris
b.tag! "hudson.plugins.chucknorris.CordellWalkerRecorder" do
b.factGenerator
end
end
end
end
else
b.publishers
end
end
# The important sequence of steps that are run to process a job build.
# Can be defaulted by the +job_type+ using +default_steps(job_type)+,
# or customized via +steps+ array.
def build_steps(b)
b.builders do
self.steps ||= default_steps(job_type)
steps.each do |step|
method, cmd = step
send(method.to_sym, b, cmd) # e.g. build_shell_step(b, "bundle install")
end
end
end
def default_steps(job_type)
steps = case job_type.to_sym
when :rails, :rails3
[
[:build_shell_step, "bundle install"],
[:build_ruby_step, <<-RUBY.gsub(/^ /, '')],
unless File.exist?("config/database.yml")
require 'fileutils'
example = Dir["config/database*"].first
puts "Using \#{example} for config/database.yml"
FileUtils.cp example, "config/database.yml"
end
RUBY
[:build_shell_step, "bundle exec rake db:create:all"],
[:build_shell_step, <<-RUBY.gsub(/^ /, '')],
if [ -f db/schema.rb ]; then
bundle exec rake db:schema:load
else
bundle exec rake db:migrate
fi
RUBY
[:build_shell_step, "bundle exec rake"]
]
when :ruby, :rubygems
[
[:build_shell_step, "bundle install"],
[:build_shell_step, "bundle exec rake"]
]
when :erlang
[
[:build_shell_step, "rebar compile"],
[:build_shell_step, "rebar ct"]
]
else
[ [:build_shell_step, 'echo "THERE ARE NO STEPS! Except this one..."'] ]
end
rubies.blank? ? steps : default_rvm_steps + steps
end
def default_rvm_steps
[
[:build_shell_step, "rvm $RUBY_VERSION"],
[:build_shell_step, "rvm gemset create ruby-$RUBY_VERSION && rvm gemset use ruby-$RUBY_VERSION"]
]
end
#
# echo 'THERE ARE NO STEPS! Except this one...'
#
def build_shell_step(b, command)
b.tag! "hudson.tasks.Shell" do
b.command command.to_xs.gsub("&", '&') #.gsub(%r{"}, '"').gsub(%r{'}, ''')
end
end
#
# unless File.exist?("config/database.yml")
# require 'fileutils'
# example = Dir["config/database*"].first
# puts "Using #{example} for config/database.yml"
# FileUtils.cp example, "config/database.yml"
# end
#
def build_ruby_step(b, command)
b.tag! "hudson.plugins.ruby.Ruby" do
b.command do
b << command.to_xs.gsub(%r{"}, '"').gsub(%r{'}, ''')
end
end
end
# Usage: build_ruby_step b, "db:schema:load"
#
#
# (Default)
#
#
#
# db:schema:load
# false
#
def build_rake_step(b, tasks)
b.tag! "hudson.plugins.rake.Rake" do
b.rakeInstallation "(Default)"
b.rakeFile
b.rakeLibDir
b.rakeWorkingDir
b.tasks tasks
b.silent false
end
end
# Converts git@github.com:drnic/newgem.git into git://github.com/drnic/newgem.git
def public_only_git_scm(scm_url)
if scm_url =~ /git@([\w\-_.]+):(.+)\.git/
"git://#{$1}/#{$2}.git"
else
scm_url
end
end
end
end