jenkins 0.6.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 (101) hide show
  1. data/.gitignore +6 -0
  2. data/Changelog.md +40 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +58 -0
  5. data/README.md +154 -0
  6. data/Rakefile +82 -0
  7. data/bin/jenkins +10 -0
  8. data/features/default_host.feature +19 -0
  9. data/features/development.feature +14 -0
  10. data/features/launch_server.feature +16 -0
  11. data/features/listing_jobs.feature +34 -0
  12. data/features/manage_jobs.feature +208 -0
  13. data/features/manage_slave_nodes.feature +82 -0
  14. data/features/step_definitions/common_steps.rb +192 -0
  15. data/features/step_definitions/fixture_project_steps.rb +8 -0
  16. data/features/step_definitions/jenkins_steps.rb +104 -0
  17. data/features/step_definitions/scm_steps.rb +12 -0
  18. data/features/support/common.rb +37 -0
  19. data/features/support/env.rb +19 -0
  20. data/features/support/hooks.rb +16 -0
  21. data/features/support/jenkins_helpers.rb +6 -0
  22. data/features/support/matchers.rb +10 -0
  23. data/fixtures/jenkins/envfile.hpi +0 -0
  24. data/fixtures/jenkins/git.hpi +0 -0
  25. data/fixtures/jenkins/github.hpi +0 -0
  26. data/fixtures/jenkins/greenballs.hpi +0 -0
  27. data/fixtures/jenkins/rake.hpi +0 -0
  28. data/fixtures/jenkins/ruby.hpi +0 -0
  29. data/fixtures/projects/non-bundler/Rakefile +4 -0
  30. data/fixtures/projects/rails-3/.gitignore +4 -0
  31. data/fixtures/projects/rails-3/Gemfile +30 -0
  32. data/fixtures/projects/rails-3/Gemfile.lock +74 -0
  33. data/fixtures/projects/rails-3/README +256 -0
  34. data/fixtures/projects/rails-3/Rakefile +7 -0
  35. data/fixtures/projects/rails-3/app/controllers/application_controller.rb +3 -0
  36. data/fixtures/projects/rails-3/app/helpers/application_helper.rb +2 -0
  37. data/fixtures/projects/rails-3/app/views/layouts/application.html.erb +14 -0
  38. data/fixtures/projects/rails-3/config.ru +4 -0
  39. data/fixtures/projects/rails-3/config/application.rb +42 -0
  40. data/fixtures/projects/rails-3/config/boot.rb +13 -0
  41. data/fixtures/projects/rails-3/config/database.yml +22 -0
  42. data/fixtures/projects/rails-3/config/environment.rb +5 -0
  43. data/fixtures/projects/rails-3/config/environments/development.rb +26 -0
  44. data/fixtures/projects/rails-3/config/environments/production.rb +49 -0
  45. data/fixtures/projects/rails-3/config/environments/test.rb +35 -0
  46. data/fixtures/projects/rails-3/config/initializers/backtrace_silencers.rb +7 -0
  47. data/fixtures/projects/rails-3/config/initializers/inflections.rb +10 -0
  48. data/fixtures/projects/rails-3/config/initializers/mime_types.rb +5 -0
  49. data/fixtures/projects/rails-3/config/initializers/secret_token.rb +7 -0
  50. data/fixtures/projects/rails-3/config/initializers/session_store.rb +8 -0
  51. data/fixtures/projects/rails-3/config/locales/en.yml +5 -0
  52. data/fixtures/projects/rails-3/config/routes.rb +58 -0
  53. data/fixtures/projects/rails-3/db/seeds.rb +7 -0
  54. data/fixtures/projects/rails-3/doc/README_FOR_APP +2 -0
  55. data/fixtures/projects/rails-3/lib/tasks/.gitkeep +0 -0
  56. data/fixtures/projects/rails-3/public/404.html +26 -0
  57. data/fixtures/projects/rails-3/public/422.html +26 -0
  58. data/fixtures/projects/rails-3/public/500.html +26 -0
  59. data/fixtures/projects/rails-3/public/favicon.ico +0 -0
  60. data/fixtures/projects/rails-3/public/images/rails.png +0 -0
  61. data/fixtures/projects/rails-3/public/index.html +239 -0
  62. data/fixtures/projects/rails-3/public/javascripts/application.js +2 -0
  63. data/fixtures/projects/rails-3/public/javascripts/controls.js +965 -0
  64. data/fixtures/projects/rails-3/public/javascripts/dragdrop.js +974 -0
  65. data/fixtures/projects/rails-3/public/javascripts/effects.js +1123 -0
  66. data/fixtures/projects/rails-3/public/javascripts/prototype.js +6001 -0
  67. data/fixtures/projects/rails-3/public/javascripts/rails.js +175 -0
  68. data/fixtures/projects/rails-3/public/robots.txt +5 -0
  69. data/fixtures/projects/rails-3/public/stylesheets/.gitkeep +0 -0
  70. data/fixtures/projects/rails-3/script/rails +6 -0
  71. data/fixtures/projects/rails-3/test/performance/browsing_test.rb +9 -0
  72. data/fixtures/projects/rails-3/test/test_helper.rb +13 -0
  73. data/fixtures/projects/rails-3/vendor/plugins/.gitkeep +0 -0
  74. data/fixtures/projects/ruby/Gemfile +3 -0
  75. data/fixtures/projects/ruby/Gemfile.lock +10 -0
  76. data/fixtures/projects/ruby/Rakefile +4 -0
  77. data/jenkins.gemspec +34 -0
  78. data/lib/jenkins.rb +6 -0
  79. data/lib/jenkins/api.rb +219 -0
  80. data/lib/jenkins/cli.rb +249 -0
  81. data/lib/jenkins/cli/formatting.rb +53 -0
  82. data/lib/jenkins/config.rb +27 -0
  83. data/lib/jenkins/core_ext/hash.rb +9 -0
  84. data/lib/jenkins/core_ext/object/blank.rb +77 -0
  85. data/lib/jenkins/hudson-cli.jar +0 -0
  86. data/lib/jenkins/job_config_builder.rb +287 -0
  87. data/lib/jenkins/project_scm.rb +22 -0
  88. data/lib/jenkins/remote.rb +11 -0
  89. data/lib/jenkins/version.rb +3 -0
  90. data/spec/fixtures/ec2_global.config.xml +103 -0
  91. data/spec/fixtures/rails.multi.config.xml +82 -0
  92. data/spec/fixtures/rails.single.config.triggers.xml +84 -0
  93. data/spec/fixtures/rails.single.config.xml +80 -0
  94. data/spec/fixtures/ruby.multi-ruby-multi-labels.config.xml +84 -0
  95. data/spec/fixtures/ruby.multi.config.xml +77 -0
  96. data/spec/fixtures/ruby.single.config.xml +58 -0
  97. data/spec/fixtures/therubyracer.config.xml +77 -0
  98. data/spec/hash_key_cleaner_spec.rb +25 -0
  99. data/spec/job_config_builder_spec.rb +137 -0
  100. data/spec/spec_helper.rb +15 -0
  101. metadata +355 -0
@@ -0,0 +1,175 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+ })();
@@ -0,0 +1,5 @@
1
+ # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2
+ #
3
+ # To ban all spiders from the entire site uncomment the next two lines:
4
+ # User-Agent: *
5
+ # Disallow: /
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'rails/performance_test_help'
3
+
4
+ # Profiling results for each test method are written to tmp/performance.
5
+ class BrowsingTest < ActionDispatch::PerformanceTest
6
+ def test_homepage
7
+ get '/'
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
7
+ #
8
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
9
+ # -- they do not yet inherit this setting
10
+ fixtures :all
11
+
12
+ # Add more helper methods to be used by all tests here...
13
+ end
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rack"
@@ -0,0 +1,10 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ rack (1.2.1)
5
+
6
+ PLATFORMS
7
+ ruby
8
+
9
+ DEPENDENCIES
10
+ rack
@@ -0,0 +1,4 @@
1
+ desc "Default task runs tests"
2
+ task :default do
3
+ puts "Tests ran successfully!"
4
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "jenkins/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "jenkins"
7
+ s.version = Jenkins::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Charles Lowell", "Nic Williams"]
10
+ s.email = ["cowboyd@thefrontside.net", "drnicwilliams@gmail.com"]
11
+ s.homepage = "http://github.com/cowboyd/jenkins.rb"
12
+ s.summary = %q{Painless Continuous Integration with Jenkins Server}
13
+ s.description = %q{A suite of utilities for bringing continous integration to your projects (not the other way around) with jenkins CI}
14
+
15
+ s.rubyforge_project = "jenkins"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("term-ansicolor", [">= 1.0.4"])
23
+ s.add_dependency("yajl-ruby", [">= 0.7.6"])
24
+ s.add_dependency("httparty", ["~> 0.6.1"])
25
+ s.add_dependency("builder", ["~> 2.1.2"])
26
+ s.add_dependency("thor", ["~> 0.14.2"])
27
+ s.add_dependency("hpricot")
28
+ s.add_development_dependency("jenkins-war", ">= 1.396")
29
+ s.add_development_dependency("rake", ["~> 0.8.7"])
30
+ s.add_development_dependency("cucumber", ["~> 0.9.0"])
31
+ s.add_development_dependency("rspec", ["~> 2.0.0"])
32
+ s.add_development_dependency("json", ["~>1.4.0"])
33
+ s.add_development_dependency("awesome_print")
34
+ end
@@ -0,0 +1,6 @@
1
+ module Jenkins
2
+ require 'jenkins/version'
3
+ require 'jenkins/api'
4
+ require 'jenkins/job_config_builder'
5
+ require 'jenkins/project_scm'
6
+ end
@@ -0,0 +1,219 @@
1
+ require 'httparty'
2
+ require 'cgi'
3
+ require 'uri'
4
+ require 'json'
5
+
6
+ require 'jenkins/core_ext/hash'
7
+ require 'jenkins/config'
8
+
9
+ module Jenkins
10
+ module Api
11
+ include HTTParty
12
+
13
+ headers 'content-type' => 'application/json'
14
+ format :json
15
+ # http_proxy 'localhost', '8888'
16
+
17
+ JobAlreadyExistsError = Class.new(Exception)
18
+
19
+ def self.setup_base_url(options)
20
+ options = options.with_clean_keys
21
+ # Thor's HashWithIndifferentAccess is based on string keys which URI::HTTP.build ignores
22
+ options = options.inject({}) { |mem, (key, val)| mem[key.to_sym] = val; mem }
23
+ options[:host] ||= ENV['JENKINS_HOST']
24
+ options[:port] ||= ENV['JENKINS_PORT']
25
+ options[:port] &&= options[:port].to_i
26
+ return false unless options[:host] || Jenkins::Config.config["base_uri"]
27
+ uri = options[:host] ? URI::HTTP.build(options) : Jenkins::Config.config["base_uri"]
28
+ base_uri uri.to_s
29
+ uri
30
+ end
31
+
32
+ # returns true if successfully create a new job on Jenkins
33
+ # +job_config+ is a Jenkins::JobConfigBuilder instance
34
+ # +options+ are:
35
+ # :override - true, will delete any existing job with same name, else error
36
+ #
37
+ # returns true if successful, else false
38
+ #
39
+ # TODO Exceptions?
40
+ def self.create_job(name, job_config, options = {})
41
+ options = options.with_clean_keys
42
+ delete_job(name) if options[:override]
43
+ begin
44
+ res = post "/createItem/api/xml?name=#{CGI.escape(name)}", {
45
+ :body => job_config.to_xml, :format => :xml, :headers => { 'content-type' => 'application/xml' }
46
+ }
47
+ if res.code.to_i == 200
48
+ cache_base_uri
49
+ true
50
+ else
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
62
+ false
63
+ end
64
+ rescue REXML::ParseException => e
65
+ # For some reason, if the job exists we get back half a page of HTML
66
+ raise JobAlreadyExistsError.new(name)
67
+ end
68
+ end
69
+
70
+ # Attempts to delete a job +name+
71
+ def self.delete_job(name)
72
+ res = post_plain "#{job_url name}/doDelete"
73
+ res.code.to_i == 302
74
+ end
75
+
76
+ def self.build_job(name)
77
+ res = get_plain "/job/#{name}/build"
78
+ res.code.to_i == 302
79
+ end
80
+
81
+ def self.summary
82
+ json = get "/api/json"
83
+ cache_base_uri
84
+ json
85
+ end
86
+
87
+ def self.job_names
88
+ summary["jobs"].map {|job| job["name"]}
89
+ end
90
+
91
+ # Return hash of job statuses
92
+ def self.job(name)
93
+ begin
94
+ json = get "/job/#{name}/api/json"
95
+ cache_base_uri
96
+ json
97
+ rescue Crack::ParseError
98
+ false
99
+ end
100
+ end
101
+
102
+ def self.nodes
103
+ json = get "/computer/api/json"
104
+ cache_base_uri
105
+ json
106
+ end
107
+
108
+ # Adds SSH nodes only, for now
109
+ def self.add_node(options = {})
110
+ options = options.with_clean_keys
111
+ default_options = Hash.new
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/jenkins-slave/",
118
+ :description => "Automatically created by Jenkins.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/jenkins-slave/",
128
+ :description => "Automatically created by Jenkins.rb",
129
+ :executors => 2,
130
+ :exclusive => true
131
+ )
132
+ end
133
+ options = default_options.merge(options)
134
+
135
+ slave_host = options[:slave_host]
136
+ name = options[:name] || slave_host
137
+ labels = options[:labels].split(/\s*,\s*/).join(' ') if options[:labels]
138
+
139
+ type = "hudson.slaves.DumbSlave$DescriptorImpl"
140
+
141
+ fields = {
142
+ "name" => name,
143
+ "type" => type,
144
+
145
+ "json" => {
146
+ "name" => name,
147
+ "nodeDescription" => options[:description],
148
+ "numExecutors" => options[:executors],
149
+ "remoteFS" => options[:slave_fs],
150
+ "labelString" => labels,
151
+ "mode" => options[:exclusive] ? "EXCLUSIVE" : "NORMAL",
152
+ "type" => type,
153
+ "retentionStrategy" => { "stapler-class" => "hudson.slaves.RetentionStrategy$Always" },
154
+ "nodeProperties" => { "stapler-class-bag" => "true" },
155
+ "launcher" => {
156
+ "stapler-class" => "hudson.plugins.sshslaves.SSHLauncher",
157
+ "host" => slave_host,
158
+ "port" => options[:slave_port],
159
+ "username" => options[:slave_user],
160
+ "privatekey" => options[:master_key],
161
+ }
162
+ }.to_json
163
+ }
164
+
165
+ url = URI.parse("#{base_uri}/computer/doCreateItem")
166
+
167
+ req = Net::HTTP::Post.new(url.path)
168
+ req.set_form_data(fields)
169
+
170
+ http = Net::HTTP.new(url.host, url.port)
171
+
172
+ response = http.request(req)
173
+ case response
174
+ when Net::HTTPFound
175
+ { :name => name, :slave_host => slave_host }
176
+ else
177
+ # error message looks like:
178
+ # <td id="main-panel">
179
+ # <h1>Error</h1><p>Slave called 'localhost' already exists</p>
180
+ require "hpricot"
181
+ error = Hpricot(response.body).search("td#main-panel p").text
182
+ unless error.blank?
183
+ puts error
184
+ else
185
+ puts response.body # so we can find other errors
186
+ end
187
+ false
188
+ end
189
+ end
190
+
191
+ def self.delete_node(name)
192
+ post_plain("#{base_uri}/computer/#{CGI::escape(name).gsub('+', '%20')}/doDelete/api/json")
193
+ end
194
+
195
+ # Helper for POST that don't barf at Jenkins's crappy API responses
196
+ def self.post_plain(path, options = {})
197
+ options = options.with_clean_keys
198
+ uri = URI.parse base_uri
199
+ res = Net::HTTP.start(uri.host, uri.port) { |http| http.post(path, options) }
200
+ end
201
+
202
+ # Helper for GET that don't barf at Jenkins's crappy API responses
203
+ def self.get_plain(path, options = {})
204
+ options = options.with_clean_keys
205
+ uri = URI.parse base_uri
206
+ res = Net::HTTP.start(uri.host, uri.port) { |http| http.get(path, options) }
207
+ end
208
+
209
+ private
210
+ def self.cache_base_uri
211
+ Jenkins::Config.config["base_uri"] = base_uri
212
+ Jenkins::Config.store!
213
+ end
214
+
215
+ def self.job_url(name)
216
+ "#{base_uri}/job/#{name}"
217
+ end
218
+ end
219
+ end