akqa-jenkins 0.6.4.1

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 (106) hide show
  1. data/.gitignore +8 -0
  2. data/Changelog.md +54 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +56 -0
  5. data/README.md +159 -0
  6. data/Rakefile +82 -0
  7. data/bin/jenkins +11 -0
  8. data/cucumber.yml +8 -0
  9. data/features/build_details.feature +42 -0
  10. data/features/configure.feature +36 -0
  11. data/features/default_host.feature +26 -0
  12. data/features/development.feature +14 -0
  13. data/features/launch_server.feature +16 -0
  14. data/features/listing_jobs.feature +34 -0
  15. data/features/manage_jobs.feature +214 -0
  16. data/features/manage_slave_nodes.feature +82 -0
  17. data/features/step_definitions/common_steps.rb +192 -0
  18. data/features/step_definitions/fixture_project_steps.rb +8 -0
  19. data/features/step_definitions/jenkins_steps.rb +116 -0
  20. data/features/step_definitions/scm_steps.rb +12 -0
  21. data/features/support/common.rb +37 -0
  22. data/features/support/env.rb +19 -0
  23. data/features/support/hooks.rb +16 -0
  24. data/features/support/jenkins_helpers.rb +6 -0
  25. data/features/support/matchers.rb +10 -0
  26. data/fixtures/jenkins/envfile.hpi +0 -0
  27. data/fixtures/jenkins/git.hpi +0 -0
  28. data/fixtures/jenkins/github.hpi +0 -0
  29. data/fixtures/jenkins/greenballs.hpi +0 -0
  30. data/fixtures/jenkins/rake.hpi +0 -0
  31. data/fixtures/jenkins/ruby.hpi +0 -0
  32. data/fixtures/projects/erlang/rebar.config +1 -0
  33. data/fixtures/projects/non-bundler/Rakefile +4 -0
  34. data/fixtures/projects/rails-3/.gitignore +4 -0
  35. data/fixtures/projects/rails-3/Gemfile +30 -0
  36. data/fixtures/projects/rails-3/Gemfile.lock +74 -0
  37. data/fixtures/projects/rails-3/README +256 -0
  38. data/fixtures/projects/rails-3/Rakefile +7 -0
  39. data/fixtures/projects/rails-3/app/controllers/application_controller.rb +3 -0
  40. data/fixtures/projects/rails-3/app/helpers/application_helper.rb +2 -0
  41. data/fixtures/projects/rails-3/app/views/layouts/application.html.erb +14 -0
  42. data/fixtures/projects/rails-3/config.ru +4 -0
  43. data/fixtures/projects/rails-3/config/application.rb +42 -0
  44. data/fixtures/projects/rails-3/config/boot.rb +13 -0
  45. data/fixtures/projects/rails-3/config/database.yml +22 -0
  46. data/fixtures/projects/rails-3/config/environment.rb +5 -0
  47. data/fixtures/projects/rails-3/config/environments/development.rb +26 -0
  48. data/fixtures/projects/rails-3/config/environments/production.rb +49 -0
  49. data/fixtures/projects/rails-3/config/environments/test.rb +35 -0
  50. data/fixtures/projects/rails-3/config/initializers/backtrace_silencers.rb +7 -0
  51. data/fixtures/projects/rails-3/config/initializers/inflections.rb +10 -0
  52. data/fixtures/projects/rails-3/config/initializers/mime_types.rb +5 -0
  53. data/fixtures/projects/rails-3/config/initializers/secret_token.rb +7 -0
  54. data/fixtures/projects/rails-3/config/initializers/session_store.rb +8 -0
  55. data/fixtures/projects/rails-3/config/locales/en.yml +5 -0
  56. data/fixtures/projects/rails-3/config/routes.rb +58 -0
  57. data/fixtures/projects/rails-3/db/seeds.rb +7 -0
  58. data/fixtures/projects/rails-3/doc/README_FOR_APP +2 -0
  59. data/fixtures/projects/rails-3/lib/tasks/.gitkeep +0 -0
  60. data/fixtures/projects/rails-3/public/404.html +26 -0
  61. data/fixtures/projects/rails-3/public/422.html +26 -0
  62. data/fixtures/projects/rails-3/public/500.html +26 -0
  63. data/fixtures/projects/rails-3/public/favicon.ico +0 -0
  64. data/fixtures/projects/rails-3/public/images/rails.png +0 -0
  65. data/fixtures/projects/rails-3/public/index.html +239 -0
  66. data/fixtures/projects/rails-3/public/javascripts/application.js +2 -0
  67. data/fixtures/projects/rails-3/public/javascripts/controls.js +965 -0
  68. data/fixtures/projects/rails-3/public/javascripts/dragdrop.js +974 -0
  69. data/fixtures/projects/rails-3/public/javascripts/effects.js +1123 -0
  70. data/fixtures/projects/rails-3/public/javascripts/prototype.js +6001 -0
  71. data/fixtures/projects/rails-3/public/javascripts/rails.js +175 -0
  72. data/fixtures/projects/rails-3/public/robots.txt +5 -0
  73. data/fixtures/projects/rails-3/public/stylesheets/.gitkeep +0 -0
  74. data/fixtures/projects/rails-3/script/rails +6 -0
  75. data/fixtures/projects/rails-3/test/performance/browsing_test.rb +9 -0
  76. data/fixtures/projects/rails-3/test/test_helper.rb +13 -0
  77. data/fixtures/projects/rails-3/vendor/plugins/.gitkeep +0 -0
  78. data/fixtures/projects/ruby/Gemfile +3 -0
  79. data/fixtures/projects/ruby/Gemfile.lock +10 -0
  80. data/fixtures/projects/ruby/Rakefile +4 -0
  81. data/jenkins.gemspec +33 -0
  82. data/lib/jenkins.rb +6 -0
  83. data/lib/jenkins/api.rb +250 -0
  84. data/lib/jenkins/cli.rb +291 -0
  85. data/lib/jenkins/cli/formatting.rb +53 -0
  86. data/lib/jenkins/config.rb +27 -0
  87. data/lib/jenkins/core_ext/hash.rb +9 -0
  88. data/lib/jenkins/core_ext/object/blank.rb +77 -0
  89. data/lib/jenkins/hudson-cli.jar +0 -0
  90. data/lib/jenkins/job_config_builder.rb +292 -0
  91. data/lib/jenkins/project_scm.rb +22 -0
  92. data/lib/jenkins/remote.rb +11 -0
  93. data/lib/jenkins/version.rb +3 -0
  94. data/spec/fixtures/ec2_global.config.xml +103 -0
  95. data/spec/fixtures/erlang.single.config.xml +59 -0
  96. data/spec/fixtures/rails.multi.config.xml +82 -0
  97. data/spec/fixtures/rails.single.config.triggers.xml +84 -0
  98. data/spec/fixtures/rails.single.config.xml +80 -0
  99. data/spec/fixtures/ruby.multi-ruby-multi-labels.config.xml +84 -0
  100. data/spec/fixtures/ruby.multi.config.xml +77 -0
  101. data/spec/fixtures/ruby.single.config.xml +58 -0
  102. data/spec/fixtures/therubyracer.config.xml +77 -0
  103. data/spec/hash_key_cleaner_spec.rb +25 -0
  104. data/spec/job_config_builder_spec.rb +150 -0
  105. data/spec/spec_helper.rb +15 -0
  106. metadata +283 -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,33 @@
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 = "akqa-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 = "akqa-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("httparty", ["~> 0.6.1"])
24
+ s.add_dependency("builder", ["~> 2.1.2"])
25
+ s.add_dependency("thor", ["~> 0.14.2"])
26
+ s.add_dependency("hpricot")
27
+ s.add_dependency("json_pure", ">= 1.5.1")
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.10.0"])
31
+ s.add_development_dependency("rspec", ["~> 2.0.0"])
32
+ s.add_development_dependency("awesome_print")
33
+ 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,250 @@
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 = setup_authentication(options)
24
+ options[:host] ||= ENV['JENKINS_HOST']
25
+ options[:port] ||= ENV['JENKINS_PORT']
26
+ options[:port] &&= options[:port].to_i
27
+ return false unless options[:host] || Jenkins::Config.config["base_uri"]
28
+ uri_class = options.delete(:ssl) ? URI::HTTPS : URI::HTTP
29
+ uri = options[:host] ? uri_class.build(options) : Jenkins::Config.config["base_uri"]
30
+ base_uri uri.to_s
31
+ uri
32
+ end
33
+
34
+ # returns true if successfully create a new job on Jenkins
35
+ # +job_config+ is a Jenkins::JobConfigBuilder instance
36
+ # +options+ are:
37
+ # :override - true, will delete any existing job with same name, else error
38
+ #
39
+ # returns true if successful, else false
40
+ #
41
+ # TODO Exceptions?
42
+ def self.create_job(name, job_config, options = {})
43
+ options = options.with_clean_keys
44
+ delete_job(name) if options[:override]
45
+ begin
46
+ res = post "/createItem/api/xml?name=#{CGI.escape(name)}", {
47
+ :body => job_config.to_xml, :format => :xml, :headers => { 'content-type' => 'application/xml' }
48
+ }
49
+ if res.code.to_i == 200
50
+ cache_configuration!
51
+ true
52
+ else
53
+ require "hpricot"
54
+ doc = Hpricot(res.body)
55
+ error_msg = doc.search("td#main-panel p")
56
+ unless error_msg.inner_text.blank?
57
+ $stderr.puts error_msg.inner_text
58
+ else
59
+ # TODO - what are the errors we get?
60
+ puts "Server error:"
61
+ p res.code
62
+ puts res.body
63
+ end
64
+ false
65
+ end
66
+ rescue REXML::ParseException => e
67
+ # For some reason, if the job exists we get back half a page of HTML
68
+ raise JobAlreadyExistsError.new(name)
69
+ end
70
+ end
71
+
72
+ # Attempts to delete a job +name+
73
+ def self.delete_job(name)
74
+ res = post_plain "#{job_url name}/doDelete"
75
+ res.code.to_i == 302
76
+ end
77
+
78
+ def self.build_job(name)
79
+ res = get_plain "/job/#{name}/build"
80
+ res.code.to_i == 302
81
+ end
82
+
83
+ def self.summary
84
+ json = get "/api/json"
85
+ cache_configuration!
86
+ json
87
+ end
88
+
89
+ def self.job_names
90
+ summary["jobs"].map {|job| job["name"]}
91
+ end
92
+
93
+ # Return hash of job statuses
94
+ def self.job(name)
95
+ begin
96
+ json = get "/job/#{name}/api/json"
97
+ cache_configuration!
98
+ json
99
+ rescue Crack::ParseError
100
+ false
101
+ end
102
+ end
103
+
104
+ # Return a hash of information about a build.
105
+ def self.build_details(job_name, build_number)
106
+ begin
107
+ json = get "/job/#{job_name}/#{build_number}/api/json"
108
+ cache_configuration!
109
+ json
110
+ rescue Crack::ParseError
111
+ false
112
+ end
113
+ end
114
+
115
+ def self.nodes
116
+ json = get "/computer/api/json"
117
+ cache_configuration!
118
+ json
119
+ end
120
+
121
+ # Adds SSH nodes only, for now
122
+ def self.add_node(options = {})
123
+ options = options.with_clean_keys
124
+ default_options = Hash.new
125
+ if options[:vagrant]
126
+ default_options.merge!(
127
+ :slave_port => 2222,
128
+ :slave_user => 'vagrant',
129
+ :master_key => "/Library/Ruby/Gems/1.8/gems/vagrant-0.6.7/keys/vagrant", # FIXME - hardcoded master username assumption
130
+ :slave_fs => "/vagrant/tmp/jenkins-slave/",
131
+ :description => "Automatically created by Jenkins.rb",
132
+ :executors => 2,
133
+ :exclusive => true
134
+ )
135
+ else
136
+ default_options.merge!(
137
+ :slave_port => 22,
138
+ :slave_user => 'deploy',
139
+ :master_key => "/home/deploy/.ssh/id_rsa", # FIXME - hardcoded master username assumption
140
+ :slave_fs => "/data/jenkins-slave/",
141
+ :description => "Automatically created by Jenkins.rb",
142
+ :executors => 2,
143
+ :exclusive => true
144
+ )
145
+ end
146
+ options = default_options.merge(options)
147
+
148
+ slave_host = options[:slave_host]
149
+ name = options[:name] || slave_host
150
+ labels = options[:labels].split(/\s*,\s*/).join(' ') if options[:labels]
151
+
152
+ type = "hudson.slaves.DumbSlave$DescriptorImpl"
153
+
154
+ fields = {
155
+ "name" => name,
156
+ "type" => type,
157
+
158
+ "json" => {
159
+ "name" => name,
160
+ "nodeDescription" => options[:description],
161
+ "numExecutors" => options[:executors],
162
+ "remoteFS" => options[:slave_fs],
163
+ "labelString" => labels,
164
+ "mode" => options[:exclusive] ? "EXCLUSIVE" : "NORMAL",
165
+ "type" => type,
166
+ "retentionStrategy" => { "stapler-class" => "hudson.slaves.RetentionStrategy$Always" },
167
+ "nodeProperties" => { "stapler-class-bag" => "true" },
168
+ "launcher" => {
169
+ "stapler-class" => "hudson.plugins.sshslaves.SSHLauncher",
170
+ "host" => slave_host,
171
+ "port" => options[:slave_port],
172
+ "username" => options[:slave_user],
173
+ "privatekey" => options[:master_key],
174
+ }
175
+ }.to_json
176
+ }
177
+
178
+ url = URI.parse("#{base_uri}/computer/doCreateItem")
179
+
180
+ req = Net::HTTP::Post.new(url.path)
181
+ req.set_form_data(fields)
182
+
183
+ http = Net::HTTP.new(url.host, url.port)
184
+
185
+ response = http.request(req)
186
+ case response
187
+ when Net::HTTPFound
188
+ { :name => name, :slave_host => slave_host }
189
+ else
190
+ # error message looks like:
191
+ # <td id="main-panel">
192
+ # <h1>Error</h1><p>Slave called 'localhost' already exists</p>
193
+ require "hpricot"
194
+ error = Hpricot(response.body).search("td#main-panel p").text
195
+ unless error.blank?
196
+ puts error
197
+ else
198
+ puts response.body # so we can find other errors
199
+ end
200
+ false
201
+ end
202
+ end
203
+
204
+ def self.delete_node(name)
205
+ post_plain("#{base_uri}/computer/#{CGI::escape(name).gsub('+', '%20')}/doDelete/api/json")
206
+ end
207
+
208
+ # Helper for POST that don't barf at Jenkins's crappy API responses
209
+ def self.post_plain(path, data = "", options = {})
210
+ options = options.with_clean_keys
211
+ uri = URI.parse base_uri
212
+ res = Net::HTTP.start(uri.host, uri.port) do |http|
213
+ if RUBY_VERSION =~ /1.8/
214
+ http.post(path, options)
215
+ else
216
+ http.post(path, data, options)
217
+ end
218
+ end
219
+ end
220
+
221
+ # Helper for GET that don't barf at Jenkins's crappy API responses
222
+ def self.get_plain(path, options = {})
223
+ options = options.with_clean_keys
224
+ uri = URI.parse base_uri
225
+ res = Net::HTTP.start(uri.host, uri.port) { |http| http.get(path, options) }
226
+ end
227
+
228
+ def self.cache_configuration!
229
+ Jenkins::Config.config["base_uri"] = base_uri
230
+ Jenkins::Config.config["basic_auth"] = default_options[:basic_auth]
231
+ Jenkins::Config.store!
232
+ end
233
+
234
+ private
235
+ def self.setup_authentication(options)
236
+ username, password = options.delete(:username), options.delete(:password)
237
+ if username && password
238
+ basic_auth username, password
239
+ elsif Jenkins::Config.config["basic_auth"]
240
+ basic_auth Jenkins::Config.config["basic_auth"]["username"],
241
+ Jenkins::Config.config["basic_auth"]["password"]
242
+ end
243
+ options
244
+ end
245
+
246
+ def self.job_url(name)
247
+ "#{base_uri}/job/#{name}"
248
+ end
249
+ end
250
+ end