hudson 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. data/.gitignore +4 -0
  2. data/Changelog.md +24 -0
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +4 -50
  5. data/features/default_host.feature +19 -0
  6. data/features/development.feature +2 -2
  7. data/features/launch_server.feature +1 -0
  8. data/features/manage_jobs.feature +109 -1
  9. data/features/manage_slave_nodes.feature +39 -4
  10. data/features/step_definitions/common_steps.rb +6 -0
  11. data/features/step_definitions/hudson_steps.rb +13 -2
  12. data/features/support/hudson_helpers.rb +6 -0
  13. data/hudson.gemspec +26 -56
  14. data/lib/hudson.rb +1 -1
  15. data/lib/hudson/api.rb +38 -24
  16. data/lib/hudson/cli.rb +81 -49
  17. data/lib/hudson/installation.rb +136 -0
  18. data/lib/hudson/job_config_builder.rb +68 -21
  19. data/lib/hudson/project_scm.rb +3 -3
  20. data/lib/hudson/version.rb +1 -1
  21. data/spec/fixtures/rails.single.config.xml +1 -0
  22. data/spec/fixtures/ruby.multi-ruby-multi-labels.config.xml +84 -0
  23. data/spec/fixtures/{rubygem.config.xml → ruby.multi.config.xml} +18 -1
  24. data/spec/job_config_builder_spec.rb +47 -11
  25. metadata +41 -93
  26. data/fixtures/projects/non-bundler/Rakefile +0 -4
  27. data/fixtures/projects/rails-3/Gemfile +0 -30
  28. data/fixtures/projects/rails-3/Gemfile.lock +0 -74
  29. data/fixtures/projects/rails-3/README +0 -256
  30. data/fixtures/projects/rails-3/Rakefile +0 -7
  31. data/fixtures/projects/rails-3/app/controllers/application_controller.rb +0 -3
  32. data/fixtures/projects/rails-3/app/helpers/application_helper.rb +0 -2
  33. data/fixtures/projects/rails-3/app/views/layouts/application.html.erb +0 -14
  34. data/fixtures/projects/rails-3/config.ru +0 -4
  35. data/fixtures/projects/rails-3/config/application.rb +0 -42
  36. data/fixtures/projects/rails-3/config/boot.rb +0 -13
  37. data/fixtures/projects/rails-3/config/database.yml +0 -22
  38. data/fixtures/projects/rails-3/config/environment.rb +0 -5
  39. data/fixtures/projects/rails-3/config/environments/development.rb +0 -26
  40. data/fixtures/projects/rails-3/config/environments/production.rb +0 -49
  41. data/fixtures/projects/rails-3/config/environments/test.rb +0 -35
  42. data/fixtures/projects/rails-3/config/initializers/backtrace_silencers.rb +0 -7
  43. data/fixtures/projects/rails-3/config/initializers/inflections.rb +0 -10
  44. data/fixtures/projects/rails-3/config/initializers/mime_types.rb +0 -5
  45. data/fixtures/projects/rails-3/config/initializers/secret_token.rb +0 -7
  46. data/fixtures/projects/rails-3/config/initializers/session_store.rb +0 -8
  47. data/fixtures/projects/rails-3/config/locales/en.yml +0 -5
  48. data/fixtures/projects/rails-3/config/routes.rb +0 -58
  49. data/fixtures/projects/rails-3/db/seeds.rb +0 -7
  50. data/fixtures/projects/rails-3/doc/README_FOR_APP +0 -2
  51. data/fixtures/projects/rails-3/log/development.log +0 -0
  52. data/fixtures/projects/rails-3/log/production.log +0 -0
  53. data/fixtures/projects/rails-3/log/server.log +0 -0
  54. data/fixtures/projects/rails-3/log/test.log +0 -0
  55. data/fixtures/projects/rails-3/public/404.html +0 -26
  56. data/fixtures/projects/rails-3/public/422.html +0 -26
  57. data/fixtures/projects/rails-3/public/500.html +0 -26
  58. data/fixtures/projects/rails-3/public/favicon.ico +0 -0
  59. data/fixtures/projects/rails-3/public/images/rails.png +0 -0
  60. data/fixtures/projects/rails-3/public/index.html +0 -239
  61. data/fixtures/projects/rails-3/public/javascripts/application.js +0 -2
  62. data/fixtures/projects/rails-3/public/javascripts/controls.js +0 -965
  63. data/fixtures/projects/rails-3/public/javascripts/dragdrop.js +0 -974
  64. data/fixtures/projects/rails-3/public/javascripts/effects.js +0 -1123
  65. data/fixtures/projects/rails-3/public/javascripts/prototype.js +0 -6001
  66. data/fixtures/projects/rails-3/public/javascripts/rails.js +0 -175
  67. data/fixtures/projects/rails-3/public/robots.txt +0 -5
  68. data/fixtures/projects/rails-3/script/rails +0 -6
  69. data/fixtures/projects/rails-3/test/performance/browsing_test.rb +0 -9
  70. data/fixtures/projects/rails-3/test/test_helper.rb +0 -13
  71. data/fixtures/projects/ruby/Gemfile +0 -3
  72. data/fixtures/projects/ruby/Gemfile.lock +0 -10
  73. data/fixtures/projects/ruby/Rakefile +0 -4
  74. data/lib/hudson/hudson.war +0 -0
  75. data/lib/hudson/hudson_version.rb +0 -4
  76. data/lib/hudson/plugins/envfile.hpi +0 -0
  77. data/lib/hudson/plugins/git.hpi +0 -0
  78. data/lib/hudson/plugins/github.hpi +0 -0
  79. data/lib/hudson/plugins/greenballs.hpi +0 -0
  80. data/lib/hudson/plugins/rake.hpi +0 -0
  81. data/lib/hudson/plugins/ruby.hpi +0 -0
  82. data/tasks/upgrade.rake +0 -83
@@ -4,7 +4,7 @@ module Hudson
4
4
  end
5
5
 
6
6
  require 'hudson/version'
7
- require 'hudson/hudson_version'
8
7
  require 'hudson/api'
9
8
  require 'hudson/job_config_builder'
10
9
  require 'hudson/project_scm'
10
+ require 'hudson/installation'
@@ -48,10 +48,17 @@ module Hudson
48
48
  cache_base_uri
49
49
  true
50
50
  else
51
- # TODO - what are the errors we get?
52
- puts "Server error:"
53
- p res.code
54
- puts res.body
51
+ require "hpricot"
52
+ doc = Hpricot(res.body)
53
+ error_msg = doc.search("td#main-panel p")
54
+ unless error_msg.inner_text.blank?
55
+ $stderr.puts error_msg.inner_text
56
+ else
57
+ # TODO - what are the errors we get?
58
+ puts "Server error:"
59
+ p res.code
60
+ puts res.body
61
+ end
55
62
  false
56
63
  end
57
64
  rescue REXML::ParseException => e
@@ -87,9 +94,7 @@ module Hudson
87
94
  json = get "/job/#{name}/api/json"
88
95
  cache_base_uri
89
96
  json
90
- rescue Errno::ECONNREFUSED
91
- false
92
- rescue Errno::EAFNOSUPPORT
97
+ rescue Crack::ParseError
93
98
  false
94
99
  end
95
100
  end
@@ -98,30 +103,39 @@ module Hudson
98
103
  json = get "/computer/api/json"
99
104
  cache_base_uri
100
105
  json
101
- rescue Errno::ECONNREFUSED => e
102
- false
103
- rescue Errno::EAFNOSUPPORT
104
- false
105
106
  end
106
107
 
107
108
  # Adds SSH nodes only, for now
108
109
  def self.add_node(options = {})
109
110
  options = options.with_clean_keys
110
111
  default_options = Hash.new
111
- default_options.merge!(
112
- :slave_port => 22,
113
- :master_key => "/home/deploy/.ssh/id_rsa", # FIXME - hardcoded master username assumption
114
- :slave_fs => "/data/hudson-slave/",
115
- :description => "Automatically created by Hudson.rb",
116
- :executors => 2,
117
- :exclusive => true
118
- )
119
-
112
+ if options[:vagrant]
113
+ default_options.merge!(
114
+ :slave_port => 2222,
115
+ :slave_user => 'vagrant',
116
+ :master_key => "/Library/Ruby/Gems/1.8/gems/vagrant-0.6.7/keys/vagrant", # FIXME - hardcoded master username assumption
117
+ :slave_fs => "/vagrant/tmp/hudson-slave/",
118
+ :description => "Automatically created by Hudson.rb",
119
+ :executors => 2,
120
+ :exclusive => true
121
+ )
122
+ else
123
+ default_options.merge!(
124
+ :slave_port => 22,
125
+ :slave_user => 'deploy',
126
+ :master_key => "/home/deploy/.ssh/id_rsa", # FIXME - hardcoded master username assumption
127
+ :slave_fs => "/data/hudson-slave/",
128
+ :description => "Automatically created by Hudson.rb",
129
+ :executors => 2,
130
+ :exclusive => true
131
+ )
132
+ end
133
+ options = default_options.merge(options)
134
+
120
135
  slave_host = options[:slave_host]
121
136
  name = options[:name] || slave_host
137
+ labels = options[:labels].split(/\s*,\s*/).join(' ') if options[:labels]
122
138
 
123
- options = default_options.merge(options)
124
-
125
139
  type = "hudson.slaves.DumbSlave$DescriptorImpl"
126
140
 
127
141
  fields = {
@@ -133,7 +147,7 @@ module Hudson
133
147
  "nodeDescription" => options[:description],
134
148
  "numExecutors" => options[:executors],
135
149
  "remoteFS" => options[:slave_fs],
136
- "labelString" => options[:label],
150
+ "labelString" => labels,
137
151
  "mode" => options[:exclusive] ? "EXCLUSIVE" : "NORMAL",
138
152
  "type" => type,
139
153
  "retentionStrategy" => { "stapler-class" => "hudson.slaves.RetentionStrategy$Always" },
@@ -158,7 +172,7 @@ module Hudson
158
172
  response = http.request(req)
159
173
  case response
160
174
  when Net::HTTPFound
161
- true
175
+ { :name => name, :slave_host => slave_host }
162
176
  else
163
177
  # error message looks like:
164
178
  # <td id="main-panel">
@@ -16,63 +16,61 @@ module Hudson
16
16
  end
17
17
 
18
18
  desc "server [options]", "run a hudson server"
19
- method_option :home, :type => :string, :default => File.join(ENV['HOME'], ".hudson", "server"), :banner => "PATH", :desc => "use this directory to store server data"
20
- method_option :port, :type => :numeric, :default => 3001, :desc => "run hudson server on this port", :aliases => "-p"
21
- method_option :control, :type => :numeric, :default => 3002, :desc => "set the shutdown/control port", :aliases => "-c"
22
- method_option :daemon, :type => :boolean, :default => false, :desc => "fork into background and run as a daemon"
23
- method_option :kill, :type => :boolean, :desc => "send shutdown signal to control port", :aliases => "-k"
24
- method_option :logfile, :type => :string, :banner => "PATH", :desc => "redirect log messages to this file"
19
+ method_option :home, :desc => "use this directory to store server data", :type => :string, :default => File.join(ENV['HOME'], ".hudson", "server"), :banner => "PATH"
20
+ method_option :port, :desc => "run hudson server on this port", :type => :numeric, :default => 3001, :aliases => "-p"
21
+ method_option :control, :desc => "set the shutdown/control port", :type => :numeric, :default => 3002, :aliases => "-c"
22
+ method_option :daemon, :desc => "fork into background and run as a daemon", :type => :boolean, :default => false
23
+ method_option :kill, :desc => "send shutdown signal to control port", :type => :boolean, :aliases => "-k"
24
+ method_option :logfile, :desc => "redirect log messages to this file", :type => :string, :banner => "PATH"
25
+ method_option :upgrade, :desc => "upgrade hudson server and plugins to latest version", :type => :boolean
25
26
  def server
27
+ installation = Hudson::Installation.new(shell, options)
26
28
  if options[:kill]
27
- require 'socket'
28
- TCPSocket.open("localhost", options[:control]) do |sock|
29
- sock.write("0")
30
- end
31
- exit
29
+ installation.kill!
30
+ exit(0)
31
+ elsif options[:upgrade]
32
+ installation.upgrade!
33
+ exit(0)
34
+ else
35
+ installation.launch!
32
36
  end
33
-
34
- serverhome = File.join(options[:home])
35
- javatmp = File.join(serverhome, "javatmp")
36
- FileUtils.mkdir_p serverhome
37
- FileUtils.mkdir_p javatmp
38
- FileUtils.cp_r Hudson::PLUGINS, serverhome
39
- ENV['HUDSON_HOME'] = serverhome
40
- cmd = ["java", "-Djava.io.tmpdir=#{javatmp}", "-jar", Hudson::WAR]
41
- cmd << "--daemon" if options[:daemon]
42
- cmd << "--logfile=#{File.expand_path(options[:logfile])}" if options[:logfile]
43
- cmd << "--httpPort=#{options[:port]}"
44
- cmd << "--controlPort=#{options[:control]}"
45
- shell.say cmd.join(" ")
46
- exec(*cmd)
47
37
  end
48
38
 
49
39
  desc "create project_path [options]", "create a build for your project"
50
40
  common_options
51
- method_option :"no-build", :desc => "create job without initial build", :type => :boolean, :default => false
52
- method_option :override, :desc => "override if job exists", :type => :boolean, :default => false
53
- method_option :"public-scm", :desc => "use public scm URL", :type => :boolean, :default => false
54
- method_option :"assigned-node", :desc => "only use slave nodes with this label"
55
- method_option :template, :desc => "template of job steps (available: #{JobConfigBuilder::VALID_JOB_TEMPLATES.join ','})", :default => 'ruby'
41
+ method_option :rubies, :desc => "run tests against multiple explicit rubies via RVM", :type => :string
42
+ method_option :"node-labels", :desc => "run tests against multiple slave nodes by their label (comma separated)"
43
+ method_option :"assigned-node", :desc => "only use slave nodes with this label (similar to --node-labels)"
44
+ method_option :"no-build", :desc => "create job without initial build", :type => :boolean, :default => false
45
+ method_option :override, :desc => "override if job exists", :type => :boolean, :default => false
46
+ method_option :"scm", :desc => "specific SCM URI", :type => :string
47
+ method_option :"scm-branches", :desc => "list of branches to build from (comma separated)", :type => :string, :default => "master"
48
+ method_option :"public-scm", :desc => "use public scm URL", :type => :boolean, :default => false
49
+ method_option :template, :desc => "template of job steps (available: #{JobConfigBuilder::VALID_JOB_TEMPLATES.join ','})", :default => 'ruby'
50
+ method_option :"no-template", :desc => "do not use a template of default steps; avoids Gemfile requirement", :type => :boolean, :default => false
56
51
  def create(project_path)
57
52
  select_hudson_server(options)
58
53
  FileUtils.chdir(project_path) do
59
- unless scm = Hudson::ProjectScm.discover
54
+ unless scm = Hudson::ProjectScm.discover(options[:scm])
60
55
  error "Cannot determine project SCM. Currently supported: #{Hudson::ProjectScm.supported}"
61
56
  end
62
- unless File.exists?("Gemfile")
57
+ unless (options[:template] == "none" || options[:"no-template"]) || File.exists?("Gemfile")
63
58
  error "Ruby/Rails projects without a Gemfile are currently unsupported."
64
59
  end
65
60
  begin
66
- template = options[:template]
61
+ template = options[:"no-template"] ? 'none' : options[:template]
67
62
  job_config = Hudson::JobConfigBuilder.new(template) do |c|
68
- c.scm = scm.url
63
+ c.rubies = options[:rubies].split(/\s*,\s*/) if options[:rubies]
64
+ c.node_labels = options[:"node-labels"].split(/\s*,\s*/) if options[:"node-labels"]
65
+ c.scm = scm.url
66
+ c.scm_branches = options[:"scm-branches"].split(/\s*,\s*/)
69
67
  c.assigned_node = options[:"assigned-node"] if options[:"assigned-node"]
70
- c.public_scm = options[:"public-scm"]
68
+ c.public_scm = options[:"public-scm"]
71
69
  end
72
70
  name = File.basename(FileUtils.pwd)
73
71
  if Hudson::Api.create_job(name, job_config, options)
74
72
  build_url = "#{@uri}/job/#{name.gsub(/\s/,'%20')}/build"
75
- shell.say "Added #{template} project '#{name}' to Hudson.", :green
73
+ shell.say "Added#{' ' + template unless template == 'none'} project '#{name}' to Hudson.", :green
76
74
  unless options[:"no-build"]
77
75
  shell.say "Triggering initial build..."
78
76
  Hudson::Api.build_job(name)
@@ -120,6 +118,30 @@ module Hudson
120
118
  end
121
119
  end
122
120
  end
121
+
122
+ desc "job NAME", "Display job details"
123
+ method_option :hash, :desc => 'Dump as formatted Ruby hash format'
124
+ method_option :json, :desc => 'Dump as JSON format'
125
+ method_option :yaml, :desc => 'Dump as YAML format'
126
+ common_options
127
+ def job(name)
128
+ select_hudson_server(options)
129
+ if job = Hudson::Api.job(name)
130
+ if options[:hash]
131
+ require "ap"
132
+ ap job.parsed_response
133
+ elsif options[:json]
134
+ puts job.parsed_response.to_json
135
+ elsif options[:yaml]
136
+ require "yaml"
137
+ puts job.parsed_response.to_yaml
138
+ else
139
+ error "Select an output format: --json, --xml, --yaml, --hash"
140
+ end
141
+ else
142
+ error "Cannot find project '#{name}'."
143
+ end
144
+ end
123
145
 
124
146
  desc "list [options]", "list jobs on a hudson server"
125
147
  common_options
@@ -129,11 +151,10 @@ module Hudson
129
151
  unless summary["jobs"].blank?
130
152
  shell.say "#{@uri}:", :bold
131
153
  summary["jobs"].each do |job|
132
- color = job['color']
133
- bold = color =~ /anime/
134
- color = 'red' if color == 'red_anime'
135
- color = 'green' if color == 'blue' || color == 'blue_anime'
136
- color = 'yellow' if color == 'grey' || color == 'disabled'
154
+ bold = job['color'] =~ /anime/
155
+ color = 'red' if job['color'] =~ /red/
156
+ color = 'green' if job['color'] =~ /(blue|green)/
157
+ color ||= 'yellow' # if color =~ /grey/ || color == 'disabled'
137
158
  shell.say "* "; shell.say(shell.set_color(job['name'], color.to_sym, bold), nil, true)
138
159
  end
139
160
  shell.say ""
@@ -154,21 +175,31 @@ module Hudson
154
175
  end
155
176
 
156
177
  desc "add_node SLAVE_HOST", "add a URI (user@host:port) server as a slave node"
157
- method_option :label, :desc => 'Labels for a job --assigned_node to match against to select a slave. Space separated list.'
158
- method_option :"slave-user", :desc => 'SSH user for Hudson to connect to slave node', :default => "deploy"
159
- method_option :"slave-port", :desc => 'SSH port for Hudson to connect to slave node', :default => 22
178
+ method_option :labels, :desc => 'Labels for a job --assigned_node to match against to select a slave (comma separated)'
179
+ method_option :"slave-user", :desc => 'SSH user for Hudson to connect to slave node (default: deploy)'
180
+ method_option :"slave-port", :desc => 'SSH port for Hudson to connect to slave node (default: 22)'
160
181
  method_option :"master-key", :desc => 'Location of master public key or identity file'
161
- method_option :"slave-fs", :desc => 'Location of file system on slave for Hudson to use'
162
- method_option :name, :desc => 'Name of slave node (default SLAVE_HOST)'
182
+ method_option :"slave-fs", :desc => 'Location of file system on slave for Hudson to use'
183
+ method_option :name, :desc => 'Name of slave node (default SLAVE_HOST)'
184
+ method_option :vagrant, :desc => 'Use settings for a Vagrant VM', :type => :boolean, :default => false
163
185
  common_options
164
186
  def add_node(slave_host)
165
187
  select_hudson_server(options)
166
- if Hudson::Api.add_node({:slave_host => slave_host}.merge(options))
167
- shell.say "Added slave node #{slave_host}", :green
188
+ if results = Hudson::Api.add_node({:slave_host => slave_host}.merge(options))
189
+ shell.say "Added slave node '#{results[:name]}' to #{results[:slave_host]}", :green
168
190
  else
169
191
  error "Failed to add slave node #{slave_host}"
170
192
  end
171
193
  end
194
+
195
+ desc "default_host", "display current default host:port URI"
196
+ def default_host
197
+ if select_hudson_server({})
198
+ display Hudson::Api.base_uri
199
+ else
200
+ display "No default host yet. Use '--host host --port port' on your first request."
201
+ end
202
+ end
172
203
 
173
204
  desc "help [command]", "show help for hudson or for a specific command"
174
205
  def help(*args)
@@ -177,7 +208,7 @@ module Hudson
177
208
 
178
209
  desc "version", "show version information"
179
210
  def version
180
- shell.say "#{Hudson::VERSION} (Hudson Server #{Hudson::HUDSON_VERSION})"
211
+ shell.say "#{Hudson::VERSION}"
181
212
  end
182
213
 
183
214
  def self.help(shell, *)
@@ -202,6 +233,7 @@ USEAGE
202
233
  unless @uri = Hudson::Api.setup_base_url(options)
203
234
  error "Either use --host or add remote servers."
204
235
  end
236
+ @uri
205
237
  end
206
238
 
207
239
  def display(text)
@@ -0,0 +1,136 @@
1
+ module Hudson
2
+ class Installation
3
+ attr_reader :directory
4
+
5
+ def initialize(shell, opts = {})
6
+ @shell = shell
7
+ @options = opts
8
+ @serverhome = File.expand_path(@options[:home])
9
+ @warfile = File.join(File.dirname(@serverhome), "hudson.war")
10
+ @versionfile = "#{@serverhome}/version.txt"
11
+ @control_port = opts[:control]
12
+ end
13
+
14
+ def launch!
15
+ unless warfile?
16
+ @shell.say "no server currently installed."
17
+ upgrade!
18
+ end
19
+ javatmp = File.join(@serverhome, "javatmp")
20
+ FileUtils.mkdir_p javatmp
21
+ ENV['HUDSON_HOME'] = @serverhome
22
+ cmd = ["java", "-Djava.io.tmpdir=#{javatmp}", "-jar", @warfile]
23
+ cmd << "--daemon" if @options[:daemon]
24
+ cmd << "--logfile=#{File.expand_path(@options[:logfile])}" if @options[:logfile]
25
+ cmd << "--httpPort=#{@options[:port]}"
26
+ cmd << "--controlPort=#{@control_port}"
27
+ @shell.say cmd.join(" ")
28
+ exec(*cmd)
29
+ end
30
+
31
+ def kill!
32
+ require 'socket'
33
+ TCPSocket.open("localhost", @control_port) do |sock|
34
+ sock.write("0")
35
+ end
36
+ exit
37
+ end
38
+
39
+ def upgrade!
40
+ FileUtils.mkdir_p @serverhome
41
+ hudson_stock ? upgrade_from_fixture_stock : upgrade_from_network_stock
42
+ end
43
+
44
+ def hudson_stock
45
+ ENV['HUDSON_STOCK']
46
+ end
47
+
48
+ private
49
+
50
+ def warfile?
51
+ File.exists?(@warfile) && system("unzip -l #{@warfile} > /dev/null 2>/dev/null")
52
+ end
53
+
54
+ def upgrade_from_fixture_stock
55
+ FileUtils.cp File.join(hudson_stock, "hudson.war"), @warfile
56
+ FileUtils.cp_r File.join(hudson_stock, "plugins"), @serverhome
57
+ end
58
+
59
+ def upgrade_from_network_stock
60
+ require 'ostruct'
61
+ progress = Progress.new
62
+ latest = progress.means "grabbing the latest metadata from hudson-ci.org" do |step|
63
+ JSON.parse(HTTParty.get("http://hudson-ci.org/update-center.json").lines.to_a[1..-2].join("\n"))
64
+ end
65
+ progress.means "downloading hudson server" do |step|
66
+ latest_version = latest["core"]["version"]
67
+ if latest_version > current_server_version
68
+ puts
69
+ `curl -L --progress-bar #{latest["core"]["url"]} -o #{@warfile}`
70
+ self.current_server_version = latest_version
71
+ step.ok("#{current_server_version} -> #{latest_version}")
72
+ else
73
+ step.ok("Up-to-date at #{current_server_version}")
74
+ end
75
+ end
76
+
77
+ plugins_dir = File.join(@serverhome, 'plugins')
78
+ plugins = if File.exists?(plugins_dir)
79
+ Dir.chdir(plugins_dir) do
80
+ Dir['*.hpi'].map {|entry| File.basename(entry,".hpi")}
81
+ end
82
+ else
83
+ %w(envfile git github greenballs rake ruby)
84
+ end
85
+ FileUtils.mkdir_p(plugins_dir)
86
+ for plugin in plugins do
87
+ metadata = OpenStruct.new(latest['plugins'][plugin])
88
+ progress.means "downloading #{plugin} plugin" do |step|
89
+ system("curl -L --silent #{metadata.url} > #{plugins_dir}/#{plugin}.hpi")
90
+ step.ok(metadata.version)
91
+ end
92
+ end unless progress.aborted
93
+ end
94
+
95
+ def current_server_version
96
+ File.exists?(@versionfile) ? File.read(@versionfile) : "0"
97
+ end
98
+
99
+ def current_server_version=(version)
100
+ File.open(@versionfile, "w") do |f|
101
+ f.write(version)
102
+ end
103
+ end
104
+
105
+ class Progress
106
+ attr_reader :aborted
107
+ def initialize
108
+ @shell = Thor::Shell::Color.new
109
+ @aborted = false
110
+ end
111
+
112
+ def means(step)
113
+ return if @aborted
114
+ begin
115
+ @shell.say(step + "... ")
116
+ yield(self).tap do
117
+ @shell.say("[OK]", :green)
118
+ end
119
+ rescue Ok => ok
120
+ @shell.say("[OK#{ok.message ? " - #{ok.message}" : ''}]", :green)
121
+ rescue StandardError => e
122
+ @shell.say("[FAIL - #{e.message}]", :red)
123
+ @aborted = true
124
+ false
125
+ end
126
+ end
127
+
128
+ def ok(msg = nil)
129
+ raise Ok.new(msg)
130
+ end
131
+
132
+ class Ok < StandardError
133
+ end
134
+ end
135
+ end
136
+ end
@@ -2,29 +2,31 @@ require "builder"
2
2
 
3
3
  module Hudson
4
4
  class JobConfigBuilder
5
- attr_accessor :job_type, :matrix_project
6
- attr_accessor :steps
5
+ attr_accessor :job_type
6
+ attr_accessor :steps, :rubies
7
+ attr_accessor :scm, :public_scm, :scm_branches
7
8
  attr_accessor :scm, :public_scm, :git_branches
8
- attr_accessor :assigned_node
9
+ attr_accessor :assigned_node, :node_labels # TODO just one of these
9
10
  attr_accessor :envfile
10
11
 
11
12
  InvalidTemplate = Class.new(StandardError)
12
13
 
13
- VALID_JOB_TEMPLATES = %w[rails rails3 ruby rubygem]
14
+ VALID_JOB_TEMPLATES = %w[none rails rails3 ruby rubygem]
14
15
 
15
16
  # +job_type+ - template of default steps to create with the job
16
17
  # +steps+ - array of [:method, cmd], e.g. [:build_shell_step, "bundle initial"]
17
18
  # - Default is based on +job_type+.
18
19
  # +scm+ - URL to the repository. Currently only support git URLs.
19
20
  # +public_scm+ - convert the +scm+ URL to a publicly accessible URL for the Hudson job config.
20
- # +git_branches+ - array of branches to run builds. Default: ['master']
21
+ # +scm_branches+ - array of branches to run builds. Default: ['master']
22
+ # +rubies+ - list of RVM rubies to run tests (via Hudson Axes).
21
23
  # +assigned_node+ - restrict this job to running on slaves with these labels (space separated)
22
24
  def initialize(job_type = :ruby, &block)
23
25
  self.job_type = job_type.to_s if job_type
24
26
 
25
27
  yield self
26
28
 
27
- self.git_branches ||= ["master"]
29
+ self.scm_branches ||= ["master"]
28
30
  raise InvalidTemplate unless VALID_JOB_TEMPLATES.include?(job_type.to_s)
29
31
  end
30
32
 
@@ -41,7 +43,7 @@ module Hudson
41
43
  b.canRoam !assigned_node
42
44
  b.disabled false
43
45
  b.blockBuildWhenUpstreamBuilding false
44
- # build_triggers b
46
+ b.triggers :class => "vector"
45
47
  b.concurrentBuild false
46
48
  build_axes b if matrix_project?
47
49
  build_steps b
@@ -57,11 +59,6 @@ module Hudson
57
59
 
58
60
  protected
59
61
 
60
- def matrix_project?
61
- matrix_project ||
62
- %w[rubygem].include?(job_type)
63
- end
64
-
65
62
  # <scm class="hudson.plugins.git.GitSCM"> ... </scm>
66
63
  def build_scm(b)
67
64
  if scm && scm =~ /git/
@@ -85,9 +82,9 @@ module Hudson
85
82
  end
86
83
  end
87
84
 
88
- if git_branches
85
+ if scm_branches
89
86
  b.branches do
90
- git_branches.each do |branch|
87
+ scm_branches.each do |branch|
91
88
  b.tag! "hudson.plugins.git.BranchSpec" do
92
89
  b.name branch
93
90
  end
@@ -112,9 +109,49 @@ module Hudson
112
109
  end
113
110
  end
114
111
 
115
- # TODO
112
+ def matrix_project?
113
+ !(rubies.blank? && node_labels.blank?)
114
+ end
115
+
116
+ # <hudson.matrix.TextAxis>
117
+ # <name>RUBY_VERSION</name>
118
+ # <values>
119
+ # <string>1.8.7</string>
120
+ # <string>1.9.2</string>
121
+ # <string>rbx-head</string>
122
+ # <string>jruby</string>
123
+ # </values>
124
+ # </hudson.matrix.TextAxis>
125
+ # <hudson.matrix.LabelAxis>
126
+ # <name>label</name>
127
+ # <values>
128
+ # <string>1.8.7</string>
129
+ # <string>ubuntu</string>
130
+ # </values>
131
+ # </hudson.matrix.LabelAxis>
116
132
  def build_axes(b)
117
- b.axes
133
+ b.axes do
134
+ unless rubies.blank?
135
+ b.tag! "hudson.matrix.TextAxis" do
136
+ b.name "RUBY_VERSION"
137
+ b.values do
138
+ rubies.each do |rvm_name|
139
+ b.string rvm_name
140
+ end
141
+ end
142
+ end
143
+ end
144
+ unless node_labels.blank?
145
+ b.tag! "hudson.matrix.LabelAxis" do
146
+ b.name "label"
147
+ b.values do
148
+ node_labels.each do |label|
149
+ b.string label
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
118
155
  end
119
156
 
120
157
  # Example:
@@ -152,7 +189,7 @@ module Hudson
152
189
  end
153
190
 
154
191
  def default_steps(job_type)
155
- case job_type.to_sym
192
+ steps = case job_type.to_sym
156
193
  when :rails, :rails3
157
194
  [
158
195
  [:build_shell_step, "bundle install"],
@@ -174,20 +211,30 @@ module Hudson
174
211
  RUBY
175
212
  [:build_shell_step, "bundle exec rake"]
176
213
  ]
177
- else
214
+ when :ruby, :rubygems
178
215
  [
179
216
  [:build_shell_step, "bundle install"],
180
217
  [:build_shell_step, "bundle exec rake"]
181
218
  ]
219
+ else
220
+ [ [:build_shell_step, 'echo "THERE ARE NO STEPS! Except this one..."'] ]
182
221
  end
222
+ rubies.blank? ? steps : default_rvm_steps + steps
183
223
  end
184
-
224
+
225
+ def default_rvm_steps
226
+ [
227
+ [:build_shell_step, "rvm $RUBY_VERSION"],
228
+ [:build_shell_step, "rvm gemset create ruby-$RUBY_VERSION && rvm gemset use ruby-$RUBY_VERSION"]
229
+ ]
230
+ end
231
+
185
232
  # <hudson.tasks.Shell>
186
- # <command>bundle install</command>
233
+ # <command>echo &apos;THERE ARE NO STEPS! Except this one...&apos;</command>
187
234
  # </hudson.tasks.Shell>
188
235
  def build_shell_step(b, command)
189
236
  b.tag! "hudson.tasks.Shell" do
190
- b.command command.to_xs.gsub(%r{"}, '&quot;').gsub(%r{'}, '&apos;')
237
+ b.command command.to_xs.gsub("&amp;", '&') #.gsub(%r{"}, '&quot;').gsub(%r{'}, '&apos;')
191
238
  end
192
239
  end
193
240