jenkins_api_client 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -17,6 +17,9 @@ doc
17
17
  #
18
18
  Gemfile.lock
19
19
 
20
+ # graphviz dot file
21
+ *.dot
22
+
20
23
  # MacOS:
21
24
  .DS_Store
22
25
 
@@ -24,6 +24,7 @@ script:
24
24
  - travis/setup_crumb.sh
25
25
  - bundle exec rake func_tests
26
26
  notifications:
27
+ webhooks: https://www.buildheroes.com/api/projects/588c9dffdc8f0deff4d89d87bf370e8dba8a8a95/builds
27
28
  email: false
28
29
  irc:
29
30
  on_success: change
@@ -4,6 +4,19 @@ CHANGELOG
4
4
  upcoming
5
5
  --------
6
6
 
7
+ v0.14.0 [07-AUG-2013]
8
+ ----------------------
9
+ * Fixed a bug where a space was missing in the exec_cli method argument list.
10
+ Credit: @missedone
11
+ * Refactored create/update jobs by introducing create_or_update methods.
12
+ Credit: @riywo
13
+ * Enhancement to crumb processing - auto detect the change of crumb setting and
14
+ do proper exception handling. Credit: dougforpress
15
+ * Added a `User` class which will handle jenkins users related functions.
16
+ Credit: dougforpres
17
+ * Added a method `Job#poll` which will poll for SCM changes programatically.
18
+ * Added a shortcut method `System#restart!` for force restart.
19
+
7
20
  v0.13.0 [09-JUL-2013]
8
21
  ----------------------
9
22
  * Jenkins XSS disable option is now supported. No inputs are required - the
data/Gemfile CHANGED
@@ -1,14 +1,15 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gem "nokogiri", "~> 1.5.0"
4
4
  gem "thor", ">= 0.16.0"
5
5
  gem "json", ">= 0"
6
6
  gem "terminal-table", ">= 1.4.0"
7
- gem "mixlib-shellout", "~> 1.1.0"
7
+ gem "mixlib-shellout", ">= 1.1.0"
8
8
 
9
9
  group :development do
10
10
  gem "bundler", ">= 1.0"
11
11
  gem "jeweler", ">= 1.6.4"
12
+ gem "rspec", "~> 2.13.0"
12
13
  gem "simplecov"
13
- gem "rspec"
14
+ gem "yard"
14
15
  end
data/README.md CHANGED
@@ -9,7 +9,7 @@ Copyright © 2012-2013, Kannan Manickam [![endorse](http://api.coderwall.com
9
9
 
10
10
  Client libraries for communicating with a Jenkins CI server and programatically managing jobs.
11
11
 
12
- IRC Channel: ##jenkins-api-client
12
+ IRC Channel: ##jenkins-api-client (on freenode)
13
13
 
14
14
  Mailing list: jenkins_api_client@googlegroups.com
15
15
 
@@ -67,6 +67,14 @@ file. To use the client without credentials, just leave out the
67
67
  <tt>username</tt> and <tt>password</tt> parameters. The <tt>password</tt>
68
68
  parameter is only required if <tt>username</tt> is specified.
69
69
 
70
+ ### Cross-site Scripting (XSS) and Crumb Support
71
+
72
+ Support for Jenkins crumbs has been added. These allow an application to
73
+ use the Jenkins API POST methods without requiring the 'Prevent Cross Site
74
+ Request Forgery exploits' to be disabled. The API will check in with the
75
+ Jenkins server to determine whether crumbs are enabled or not, and use them
76
+ if appropriate.
77
+
70
78
  ### Basic Usage
71
79
 
72
80
  As discussed earlier, you can either specify all the credentials and server
@@ -174,7 +182,8 @@ puts @client.exec_cli("version")
174
182
  ```
175
183
 
176
184
  * authentication with public/private key file
177
- remember to upload the public key to http://#{server_ip}:#{server_port}/user/#{my_username}/configure
185
+ remember to upload the public key to
186
+ http://<Server IP>:<Server Port>/user/<Username>/configure
178
187
 
179
188
  ```ruby
180
189
  @client = JenkinsApi::Client.new(:server_ip => '127.0.0.1',
data/Rakefile CHANGED
@@ -3,6 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require 'jenkins_api_client/version'
4
4
  require 'rake'
5
5
  require 'jeweler'
6
+ require 'yard'
6
7
 
7
8
  Jeweler::Tasks.new do |gemspec|
8
9
  gemspec.name = 'jenkins_api_client'
@@ -41,4 +42,65 @@ RSpec::Core::RakeTask.new(:test) do |spec|
41
42
  spec.rspec_opts = ['--color', '--format documentation']
42
43
  end
43
44
 
44
- task :default => [:unit_tests]
45
+ YARD::Config.load_plugin 'thor'
46
+ YARD::Rake::YardocTask.new do |t|
47
+ t.files = ['lib/**/*.rb', 'lib/**/**/*.rb']
48
+ end
49
+
50
+ namespace :doc do
51
+ # This task requires that graphviz is installed locally. For more info:
52
+ # http://www.graphviz.org/
53
+ desc "Generates the class diagram using the yard generated dot file"
54
+ task :generate_class_diagram do
55
+ puts "Generating the dot file..."
56
+ `yard graph --file jenkins_api_client.dot`
57
+ puts "Generating class diagram from the dot file..."
58
+ `dot jenkins_api_client.dot -Tpng -o jenkins_api_client_class_diagram.png`
59
+ end
60
+
61
+ desc "Applies Google Analytics tracking script to all generated html files"
62
+ task :apply_google_analytics do
63
+ files = Dir.glob("**/*.html")
64
+
65
+ string_to_replace = "</body>"
66
+ string_to_replace_with = <<-EOF
67
+ <script type="text/javascript">
68
+ var _gaq = _gaq || [];
69
+ _gaq.push(['_setAccount', 'UA-37519629-2']);
70
+ _gaq.push(['_trackPageview']);
71
+
72
+ (function() {
73
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
74
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
75
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
76
+ })();
77
+ </script>
78
+ </body>
79
+ EOF
80
+
81
+ files.each do |html_file|
82
+ puts "Processing file: #{html_file}"
83
+ contents = ""
84
+ # Read the file contents
85
+ file = File.open(html_file)
86
+ file.each { |line| contents << line }
87
+ file.close
88
+
89
+ # If the file already has google analytics tracking info, skip it.
90
+ if contents.include?(string_to_replace_with)
91
+ puts "Skipped..."
92
+ next
93
+ end
94
+
95
+ # Apply google analytics tracking info to the html file
96
+ contents.gsub!(string_to_replace, string_to_replace_with)
97
+
98
+ # Write the contents with the google analytics info to the file
99
+ file = File.open(html_file, "w")
100
+ file.write(contents)
101
+ file.close
102
+ end
103
+ end
104
+ end
105
+
106
+ task :default => [:unit_tests]
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "jenkins_api_client"
8
- s.version = "0.13.0"
8
+ s.version = "0.14.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Kannan Manickam"]
12
- s.date = "2013-07-10"
12
+ s.date = "2013-08-08"
13
13
  s.description = "\nThis is a simple and easy-to-use Jenkins Api client with features focused on\nautomating Job configuration programaticaly and so forth"
14
14
  s.email = ["arangamani.kannan@gmail.com"]
15
15
  s.executables = ["jenkinscli"]
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  "config/login.yml.example",
29
29
  "java_deps/jenkins-cli.jar",
30
30
  "jenkins_api_client.gemspec",
31
+ "jenkins_api_client_class_diagram.png",
31
32
  "lib/jenkins_api_client.rb",
32
33
  "lib/jenkins_api_client/build_queue.rb",
33
34
  "lib/jenkins_api_client/cli/base.rb",
@@ -40,6 +41,7 @@ Gem::Specification.new do |s|
40
41
  "lib/jenkins_api_client/job.rb",
41
42
  "lib/jenkins_api_client/node.rb",
42
43
  "lib/jenkins_api_client/system.rb",
44
+ "lib/jenkins_api_client/user.rb",
43
45
  "lib/jenkins_api_client/version.rb",
44
46
  "lib/jenkins_api_client/view.rb",
45
47
  "scripts/login_with_irb.rb",
@@ -48,6 +50,7 @@ Gem::Specification.new do |s|
48
50
  "spec/func_tests/node_spec.rb",
49
51
  "spec/func_tests/spec_helper.rb",
50
52
  "spec/func_tests/system_spec.rb",
53
+ "spec/func_tests/user_spec.rb",
51
54
  "spec/func_tests/view_spec.rb",
52
55
  "spec/unit_tests/build_queue_spec.rb",
53
56
  "spec/unit_tests/client_spec.rb",
@@ -57,6 +60,7 @@ Gem::Specification.new do |s|
57
60
  "spec/unit_tests/node_spec.rb",
58
61
  "spec/unit_tests/spec_helper.rb",
59
62
  "spec/unit_tests/system_spec.rb",
63
+ "spec/unit_tests/user_spec.rb",
60
64
  "spec/unit_tests/view_spec.rb",
61
65
  "travis/jenkins_config.xml",
62
66
  "travis/jenkins_config_with_crumb.xml",
@@ -78,32 +82,35 @@ Gem::Specification.new do |s|
78
82
  s.add_runtime_dependency(%q<thor>, [">= 0.16.0"])
79
83
  s.add_runtime_dependency(%q<json>, [">= 0"])
80
84
  s.add_runtime_dependency(%q<terminal-table>, [">= 1.4.0"])
81
- s.add_runtime_dependency(%q<mixlib-shellout>, ["~> 1.1.0"])
85
+ s.add_runtime_dependency(%q<mixlib-shellout>, [">= 1.1.0"])
82
86
  s.add_development_dependency(%q<bundler>, [">= 1.0"])
83
87
  s.add_development_dependency(%q<jeweler>, [">= 1.6.4"])
88
+ s.add_development_dependency(%q<rspec>, ["~> 2.13.0"])
84
89
  s.add_development_dependency(%q<simplecov>, [">= 0"])
85
- s.add_development_dependency(%q<rspec>, [">= 0"])
90
+ s.add_development_dependency(%q<yard>, [">= 0"])
86
91
  else
87
92
  s.add_dependency(%q<nokogiri>, ["~> 1.5.0"])
88
93
  s.add_dependency(%q<thor>, [">= 0.16.0"])
89
94
  s.add_dependency(%q<json>, [">= 0"])
90
95
  s.add_dependency(%q<terminal-table>, [">= 1.4.0"])
91
- s.add_dependency(%q<mixlib-shellout>, ["~> 1.1.0"])
96
+ s.add_dependency(%q<mixlib-shellout>, [">= 1.1.0"])
92
97
  s.add_dependency(%q<bundler>, [">= 1.0"])
93
98
  s.add_dependency(%q<jeweler>, [">= 1.6.4"])
99
+ s.add_dependency(%q<rspec>, ["~> 2.13.0"])
94
100
  s.add_dependency(%q<simplecov>, [">= 0"])
95
- s.add_dependency(%q<rspec>, [">= 0"])
101
+ s.add_dependency(%q<yard>, [">= 0"])
96
102
  end
97
103
  else
98
104
  s.add_dependency(%q<nokogiri>, ["~> 1.5.0"])
99
105
  s.add_dependency(%q<thor>, [">= 0.16.0"])
100
106
  s.add_dependency(%q<json>, [">= 0"])
101
107
  s.add_dependency(%q<terminal-table>, [">= 1.4.0"])
102
- s.add_dependency(%q<mixlib-shellout>, ["~> 1.1.0"])
108
+ s.add_dependency(%q<mixlib-shellout>, [">= 1.1.0"])
103
109
  s.add_dependency(%q<bundler>, [">= 1.0"])
104
110
  s.add_dependency(%q<jeweler>, [">= 1.6.4"])
111
+ s.add_dependency(%q<rspec>, ["~> 2.13.0"])
105
112
  s.add_dependency(%q<simplecov>, [">= 0"])
106
- s.add_dependency(%q<rspec>, [">= 0"])
113
+ s.add_dependency(%q<yard>, [">= 0"])
107
114
  end
108
115
  end
109
116
 
@@ -28,6 +28,7 @@ require 'jenkins_api_client/node'
28
28
  require 'jenkins_api_client/system'
29
29
  require 'jenkins_api_client/view'
30
30
  require 'jenkins_api_client/build_queue'
31
+ require 'jenkins_api_client/user'
31
32
 
32
33
  require 'jenkins_api_client/cli/helper'
33
34
  require 'jenkins_api_client/cli/base'
@@ -30,7 +30,9 @@ module JenkinsApi
30
30
 
31
31
  # Initializes a new BuildQueue object.
32
32
  #
33
- # @param [Object] client a reference to Client
33
+ # @param client [Client] the client object
34
+ #
35
+ # @return [BuildQueue] the build queue object
34
36
  #
35
37
  def initialize(client)
36
38
  @client = client
@@ -208,7 +210,7 @@ module JenkinsApi
208
210
  #
209
211
  # @param [String] task_name name of the task
210
212
  #
211
- # @return [TrueClass|FalseClass] buildable or not
213
+ # @return [Boolean] buildable or not
212
214
  #
213
215
  def is_buildable?(task_name)
214
216
  @logger.info "Checking if task '#{task_name}' from the build queue" +
@@ -225,7 +227,7 @@ module JenkinsApi
225
227
  #
226
228
  # @param [String] task_name name of the task
227
229
  #
228
- # @return [TrueClass|FalseClass] blocked or not
230
+ # @return [Boolean] blocked or not
229
231
  #
230
232
  def is_blocked?(task_name)
231
233
  @logger.info "Checking if task '#{task_name}' from the build queue" +
@@ -242,7 +244,7 @@ module JenkinsApi
242
244
  #
243
245
  # @param [String] task_name name of the task
244
246
  #
245
- # @return [TrueClass|FalseClass] stuck or not
247
+ # @return [Boolean] stuck or not
246
248
  #
247
249
  def is_stuck?(task_name)
248
250
  @logger.info "Checking if task '#{task_name}' from the build queue" +
@@ -83,23 +83,25 @@ module JenkinsApi
83
83
  # @option args [String] :identity_file
84
84
  # the priviate key file for Jenkins CLI authentication,
85
85
  # it is used only for executing CLI commands.
86
- # also remember to upload the public key to http://#{server_ip}:#{server_port}/user/#{my_username}/configure
86
+ # also remember to upload the public key to
87
+ # http://<Server IP>:<Server Port>/user/<Username>/configure
87
88
  # @option args [String] :proxy_ip
88
89
  # the proxy IP address
89
90
  # @option args [String] :proxy_port
90
91
  # the proxy port
91
- # @option args [String] :jenkins_path
92
+ # @option args [String] :jenkins_path ("/")
92
93
  # the optional context path for Jenkins
93
- # @option args [Boolean] :ssl
94
- # indicates if Jenkins is accessible over HTTPS (defaults to false)
94
+ # @option args [Boolean] :ssl (false)
95
+ # indicates if Jenkins is accessible over HTTPS
95
96
  # @option args [Boolean] :follow_redirects
96
97
  # This argument causes the client to follow a redirect (jenkins can
97
98
  # return a 30x when starting a build)
98
- # @option args [Fixnum] :timeout
99
- # This argument sets the timeout for the jenkins system to become ready
100
- # @option args [String] :log_location
101
- # The location for the log file (Defaults to STDOUT)
102
- # @option args [Fixnum] :log_level
99
+ # @option args [Fixnum] :timeout (120)
100
+ # This argument sets the timeout for operations that take longer (in
101
+ # seconds)
102
+ # @option args [String] :log_location (STDOUT)
103
+ # The location for the log file
104
+ # @option args [Fixnum] :log_level (Logger::INFO)
103
105
  # The level for messages to be logged. Should be one of:
104
106
  # Logger::DEBUG (0), Logger::INFO (1), Logger::WARN (2), Logger::ERROR
105
107
  # (2), Logger::FATAL (3) (Defaults to Logger::INFO)
@@ -210,6 +212,14 @@ module JenkinsApi
210
212
  JenkinsApi::Client::BuildQueue.new(self)
211
213
  end
212
214
 
215
+ # Creates an instance of the User class by passing a reference to self
216
+ #
217
+ # @return [JenkinsApi::Client::User] An object of User subclass
218
+ #
219
+ def user
220
+ JenkinsApi::Client::User.new(self)
221
+ end
222
+
213
223
  # Returns a string representing the class name
214
224
  #
215
225
  # @return [String] string representation of class name
@@ -323,9 +333,7 @@ module JenkinsApi
323
333
  def api_post_request(url_prefix, form_data = {}, raw_response = false)
324
334
  retries = @crumb_max_retries
325
335
  begin
326
- # Identify whether to use crumbs if this is the first POST request.
327
- @crumbs_enabled = use_crumbs? if @crumbs_enabled.nil?
328
- @crumb = get_crumb if @crumbs_enabled && @crumb.empty?
336
+ refresh_crumbs
329
337
 
330
338
  # Added form_data default {} instead of nil to help with proxies
331
339
  # that barf with empty post
@@ -343,13 +351,22 @@ module JenkinsApi
343
351
  else
344
352
  handle_exception(response)
345
353
  end
346
- rescue Exceptions::ForbiddenException
347
- @logger.info "Crumb expired. Refetching from the server. Trying" +
348
- " #{@crumb_max_retries - retries + 1} out of #{@crumb_max_retries}" +
349
- " times..."
350
- @crumb = get_crumb
351
- retries -= 1
352
- retries > 0 ? retry : raise
354
+ rescue Exceptions::ForbiddenException => e
355
+ refresh_crumbs(true)
356
+
357
+ if @crumbs_enabled
358
+ @logger.info "Retrying: #{@crumb_max_retries - retries + 1} out of" +
359
+ " #{@crumb_max_retries} times..."
360
+ retries -= 1
361
+
362
+ if retries > 0
363
+ retry
364
+ else
365
+ raise Exceptions::ForbiddenWithCrumb.new(@logger, e.message)
366
+ end
367
+ else
368
+ raise
369
+ end
353
370
  end
354
371
  end
355
372
 
@@ -377,9 +394,7 @@ module JenkinsApi
377
394
  def post_config(url_prefix, xml)
378
395
  retries = @crumb_max_retries
379
396
  begin
380
- # Identify whether to use crumbs if this is the first POST request.
381
- @crumbs_enabled = use_crumbs? if @crumbs_enabled.nil?
382
- @crumb = get_crumb if @crumbs_enabled && @crumb.empty?
397
+ refresh_crumbs
383
398
 
384
399
  url_prefix = URI.escape("#{@jenkins_path}#{url_prefix}")
385
400
  request = Net::HTTP::Post.new("#{url_prefix}")
@@ -391,13 +406,22 @@ module JenkinsApi
391
406
  end
392
407
  response = make_http_request(request)
393
408
  handle_exception(response)
394
- rescue Exceptions::ForbiddenException
395
- @logger.info "Crumb expired. Refetching from the server. Trying" +
396
- " #{@crumb_max_retries - retries + 1} out of #{@crumb_max_retries}" +
397
- " times..."
398
- @crumb = get_crumb
399
- retries -= 1
400
- retries > 0 ? retry : raise
409
+ rescue Exceptions::ForbiddenException => e
410
+ refresh_crumbs(true)
411
+
412
+ if @crumbs_enabled
413
+ @logger.info "Retrying: #{@crumb_max_retries - retries + 1} out of" +
414
+ " #{@crumb_max_retries} times..."
415
+ retries -= 1
416
+
417
+ if retries > 0
418
+ retry
419
+ else
420
+ raise Exceptions::ForbiddenWithCrumb.new(@logger, e.message)
421
+ end
422
+ else
423
+ raise
424
+ end
401
425
  end
402
426
  end
403
427
 
@@ -463,7 +487,8 @@ module JenkinsApi
463
487
  cmd = "java -jar #{base_dir}/../../java_deps/jenkins-cli.jar -s #{server_url}"
464
488
  cmd << " -i #{@identity_file}" if @identity_file && !@identity_file.empty?
465
489
  cmd << " #{command}"
466
- cmd << " --username #{@username} --password #{@password} " if @identity_file.nil? || @identity_file.empty?
490
+ cmd << " --username #{@username} --password #{@password}" if @identity_file.nil? || @identity_file.empty?
491
+ cmd << ' '
467
492
  cmd << args.join(' ')
468
493
  java_cmd = Mixlib::ShellOut.new(cmd)
469
494
 
@@ -506,6 +531,46 @@ module JenkinsApi
506
531
  end
507
532
  end
508
533
 
534
+ # Used to determine whether crumbs are enabled, and populate/clear our
535
+ # local crumb accordingly.
536
+ #
537
+ # @param force_refresh [Boolean] determines whether the check is
538
+ # cursory or deeper. The default is cursory - i.e. if crumbs
539
+ # enabled is 'nil' then figure out what to do, otherwise skip
540
+ # If 'true' the method will check to see if the crumbs require-
541
+ # ment has changed (by querying Jenkins), and updating crumb
542
+ # (refresh, delete, create) as appropriate.
543
+ #
544
+ def refresh_crumbs(force_refresh = false)
545
+ # Quick check to see if someone has changed XSS settings and not
546
+ # restarted us
547
+ if force_refresh || @crumbs_enabled.nil?
548
+ old_crumbs_setting = @crumbs_enabled
549
+ new_crumbs_setting = use_crumbs?
550
+
551
+ if old_crumbs_setting != new_crumbs_setting
552
+ @crumbs_enabled = new_crumbs_setting
553
+ end
554
+
555
+ # Get or clear crumbs setting appropriately
556
+ # Works as refresh if crumbs still enabled
557
+ if @crumbs_enabled
558
+ if old_crumbs_setting
559
+ @logger.info "Crumb expired. Refetching from the server."
560
+ else
561
+ @logger.info "Crumbs turned on. Fetching from the server."
562
+ end
563
+
564
+ @crumb = get_crumb if force_refresh || !old_crumbs_setting
565
+ else
566
+ if old_crumbs_setting
567
+ @logger.info "Crumbs turned off. Clearing crumb."
568
+ @crumb.clear
569
+ end
570
+ end
571
+ end
572
+ end
573
+
509
574
  # Private method that handles the exception and raises with proper error
510
575
  # message with the type of exception and returns the required values if no
511
576
  # exceptions are raised.