jenkins_api_client 0.13.0 → 0.14.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.
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.