jenkins-capistrano 0.0.5.2 → 0.0.6

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/README.rst CHANGED
@@ -2,7 +2,12 @@
2
2
  jenkins-capistrano
3
3
  ==================
4
4
 
5
- The capistrano tasks for Jenkins CI Server
5
+ The capistrano tasks for Jenkins CI Server. Supports to manage following things:
6
+
7
+ * Job
8
+ * Node
9
+ * View
10
+ * Plugins(Experimental)
6
11
 
7
12
  Installation
8
13
  ============
@@ -110,10 +115,94 @@ deploy.rb::
110
115
 
111
116
  before 'deploy', 'jenkins:config_nodes'
112
117
 
118
+ View Configuration
119
+ ~~~~~~~~~~~~~~~~~~
120
+
121
+ config directory structure(name your json file as a node name)::
122
+
123
+ config
124
+ ├── deploy.rb
125
+ └── jenkins
126
+    └── views
127
+    ├── view1.xml
128
+    ├── view2.xml
129
+    └── view3.xml
130
+
131
+ sample view configuration::
132
+
133
+ <listView>
134
+ <name>view1</name>
135
+ <filterExecutors>false</filterExecutors>
136
+ <filterQueue>false</filterQueue>
137
+ <properties class="hudson.model.View$PropertyList"/>
138
+ <jobNames class="tree-set">
139
+ <comparator class="hudson.util.CaseInsensitiveComparator" reference="../../../hudson.plugins.view.dashboard.Dashboard/jobNames/comparator"/>
140
+ </jobNames>
141
+ <jobFilters/>
142
+ <columns>
143
+ <hudson.views.StatusColumn/>
144
+ <hudson.views.WeatherColumn/>
145
+ <hudson.views.JobColumn/>
146
+ <hudson.views.LastSuccessColumn/>
147
+ <hudson.views.LastFailureColumn/>
148
+ <hudson.views.LastDurationColumn/>
149
+ <hudson.views.BuildButtonColumn/>
150
+ </columns>
151
+ <includeRegex>job.*</includeRegex>
152
+ </listView>
153
+
154
+ deploy.rb::
155
+
156
+ set :application, "your-awesome-app"
157
+ set :scm, :git
158
+ set :repository, "https://github.com/your/repository.git"
159
+
160
+ set :jenkins_host, 'http://localhost:8080'
161
+ # set :jenkins_username, '' # default empty
162
+ # set :jenkins_password, '' # default empty
163
+ # set :jenkins_node_config_dir, 'config/jenkins/nodes'
164
+
165
+ before 'deploy', 'jenkins:config_views'
166
+
167
+
168
+ Don't know how to write config.xml for view?
169
+ --------------------------------------------
170
+
171
+ Create or configure the view you want to manage via usual operation through the Jenkins UI.
172
+ Then, open the `JENKINS_HOME/config.xml` and copy the desired configuration from `<views>` section, and
173
+ ommit `<owner class="hudson" reference="../../.."/>` line.
174
+
175
+ Plugin Configuration(experimental)
176
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
177
+
178
+ Note
179
+ ----
180
+
181
+ This feature is may change its API without any notice.
182
+ Use at your own risk.
183
+
184
+ deploy.rb::
185
+
186
+ set :application, "your-awesome-app"
187
+ set :scm, :git
188
+ set :repository, "https://github.com/your/repository.git"
189
+
190
+ set :jenkins_plugins, %w(cron_column envinject join)
191
+ # you can specify version as follows:
192
+ # set :jenkins_plugins, %w(cron_column@1.1.2 envinject join@1.0.0)
193
+ set :jenkins_install_timeout, 60 * 5 # default: 5min
194
+ set :jenkins_plugin_enable_update, false # dafault: false
195
+ set :jenkins_plugin_enable_restart, false # default: false
196
+
197
+ before 'deploy', 'jenkins:install_plugins'
113
198
 
114
199
  Release Notes
115
200
  =============
116
201
 
202
+ 0.0.6
203
+ * Support view management (`726ad3ef <https://github.com/cynipe/jenkins-capistrano/commit/726ad3ef817ba15a2d66503ce0dd4bc961ed92e2>`_)
204
+ * Support plugin management(Experimental) (`4d9964c0 <https://github.com/cynipe/jenkins-capistrano/commit/4d9964c00ff95798915484ceb8b5c837b2aa03e8>`_)
205
+
117
206
  0.0.5.2
118
207
  * Fix cgi library loading error
119
208
 
@@ -19,6 +19,12 @@ Capistrano::Configuration.instance(:must_exist).load do
19
19
 
20
20
  _cset(:jenkins_job_config_dir) { 'config/jenkins/jobs' }
21
21
  _cset(:jenkins_node_config_dir) { 'config/jenkins/nodes' }
22
+ _cset(:jenkins_view_config_dir) { 'config/jenkins/views' }
23
+
24
+ _cset(:jenkins_plugins) { [] }
25
+ _cset(:jenkins_install_timeout) { 60 * 5 }
26
+ _cset(:jenkins_plugin_enable_update) { false }
27
+ _cset(:jenkins_plugin_enable_restart) { false }
22
28
 
23
29
  _cset(:disabled_jobs) { [] }
24
30
 
@@ -36,6 +42,20 @@ Capistrano::Configuration.instance(:must_exist).load do
36
42
  Dir.glob("#{jenkins_node_config_dir}/*.json")
37
43
  end
38
44
 
45
+ def view_configs
46
+ abort "Please create the jenkins_view_config_dir first: #{jenkins_node_config_dir}" unless Dir.exists? jenkins_node_config_dir
47
+ Dir.glob("#{jenkins_view_config_dir}/*.xml")
48
+ end
49
+
50
+ def missing_plugins
51
+ installed = client.plugin_names
52
+ jenkins_plugins.select do |plugin|
53
+ missing = !installed.include?(plugin.split('@'))
54
+ logger.info "#{plugin} is already installed." unless missing
55
+ missing
56
+ end
57
+ end
58
+
39
59
  # minimum configurations
40
60
  #
41
61
  # role :jenkins, 'localhost:8080'
@@ -93,9 +113,104 @@ Capistrano::Configuration.instance(:must_exist).load do
93
113
  client.config_node(name, opts)
94
114
  logger.trace "node #{name} created."
95
115
  end
116
+ end
117
+
118
+ desc <<-DESC
119
+ Configure the views to Jenkins server -- meaning create or update --
96
120
 
121
+ Configuration
122
+ -------------
123
+ jenkins_view_config_dir
124
+ the directory path where the view's configuration stored.
125
+ default: 'config/jenkins/views'
126
+ DESC
127
+ task :config_views do
128
+ logger.info "configuring jenkins views to #{jenkins_host}"
129
+ logger.important "no view configs found." if view_configs.empty?
130
+ view_configs.each do |file|
131
+ name = File.basename(file, '.xml')
132
+ msg = StringIO.new
133
+ client.create_or_update_view(name, File.read(file))
134
+ logger.trace "view #{name} created."
135
+ end
97
136
  end
98
137
 
138
+ desc <<-DESC
139
+ Install plugins to Jenkins server
140
+
141
+ Configuration
142
+ -------------
143
+ jenkins_plugins
144
+ the hash array contains plugin's name and version.
145
+
146
+ jenkins_install_timeout
147
+ a timeout seconds to wait for plugin installation.
148
+ default: 5 min
149
+
150
+ jenkins_plugin_enable_update
151
+ whether to update or ignore when the plugin already installed.
152
+ default: false
153
+
154
+ jenkins_plugin_enable_restart
155
+ whether to restart or ignore when the plugin installation requires restarting.
156
+ default: false
157
+ DESC
158
+ task :install_plugins do
159
+ logger.important "installing plugins to #{jenkins_host}"
160
+ if jenkins_plugins.empty?
161
+ logger.info "no plugin config found."
162
+ next
163
+ end
164
+
165
+ logger.info "calcurating plugins to install..."
166
+ candidates = client.prevalidate_plugin_config(missing_plugins)
167
+ plugins_to_install = candidates.reduce([]) do |mem, candidate|
168
+ plugin = "#{candidate['name']}@#{candidate['version']}"
169
+ mode = candidate['mode']
170
+ case
171
+ when mode == 'missing'
172
+ logger.debug "#{plugin} marked to be installed."
173
+ mem << candidate
174
+ when mode == 'old'
175
+ if jenkins_plugin_enable_update
176
+ logger.debug "#{plugin} marked to be updated."
177
+ mem << candidate
178
+ end
179
+ end
180
+ mem
181
+ end
182
+ if plugins_to_install.empty?
183
+ logger.info "all plugins already installed."
184
+ next
185
+ end
186
+
187
+ logger.info "installing the plugins, this could be take a while..."
188
+ client.install_plugin(plugins_to_install)
189
+
190
+ names = plugins_to_install.map {|v| v['name'] }
191
+ client.wait_for_complete(jenkins_install_timeout) do |job|
192
+ result = job['status'] == true
193
+ # skip unknown jobs
194
+ if result and names.include?(job['name'])
195
+ names.delete job['name']
196
+ logger.debug "#{job['name']}@#{job['version']} installed."
197
+ end
198
+ result
199
+ end
200
+ logger.info "all plugins successfully installed."
201
+
202
+ if client.restart_required?
203
+ if jenkins_plugin_enable_restart
204
+ logger.important "restarting jenkins."
205
+ client.safe_restart! do
206
+ logger.debug "waiting for Jenkins to restart..."
207
+ end
208
+ logger.info "Jenkins is successfully restarted."
209
+ else
210
+ logger.important "restarting is disabled, please restart the jenkins manually for complete the installation."
211
+ end
212
+ end
213
+ end
99
214
  end
100
215
 
101
216
  end
@@ -3,6 +3,9 @@ require 'cgi'
3
3
  require 'json'
4
4
  require 'jenkins-capistrano/client/node'
5
5
  require 'jenkins-capistrano/client/job'
6
+ require 'jenkins-capistrano/client/view'
7
+ require 'jenkins-capistrano/client/update_center'
8
+ require 'jenkins-capistrano/client/plugin_manager'
6
9
 
7
10
  module Jenkins
8
11
  class Client
@@ -10,8 +13,10 @@ module Jenkins
10
13
 
11
14
  headers 'content-type' => 'application/json'
12
15
  format :json
16
+ follow_redirects false
13
17
 
14
18
  ServerError = Class.new(Exception)
19
+ TimeoutError = Class.new(Exception)
15
20
 
16
21
  def initialize(host, opts = {})
17
22
  self.class.base_uri host
@@ -25,8 +30,36 @@ module Jenkins
25
30
  error_msg.inner_text.empty? ? doc.search("body").text : error_msg
26
31
  end
27
32
 
33
+ def session
34
+ self.class.get('/api/json?tree=nothing').headers['X-JENKINS-SESSION']
35
+ rescue Errno::ECONNRESET, Errno::ECONNREFUSED => e
36
+ nil
37
+ end
38
+
39
+ def safe_restart!(timeout = 60 * 5)
40
+ if block_given?
41
+ due = Time.now + timeout
42
+ origin = session
43
+ end
44
+
45
+ res = self.class.post("/safeRestart")
46
+ raise ServerError, parse_error_message(res) unless res.code.to_i == 302
47
+ return unless block_given?
48
+
49
+ loop do
50
+ new = session
51
+ break if new and new != origin
52
+ yield
53
+ raise TimeoutError, "Restating timeout: #{timeout}" if Time.now > due
54
+ sleep 5
55
+ end
56
+ end
57
+
28
58
  include Node
29
59
  include Job
60
+ include View
61
+ include UpdateCenter
62
+ include PluginManager
30
63
 
31
64
  end
32
65
  end
@@ -0,0 +1,51 @@
1
+
2
+ module Jenkins
3
+ class Client
4
+ module PluginManager
5
+
6
+ def plugin_names
7
+ self.class.get("/pluginManager/api/json?tree=plugins[shortName]")['plugins'].map {|plugin| plugin['name'] }
8
+ end
9
+
10
+ def prevalidate_plugin_config(plugins)
11
+ config = generate_config(plugins)
12
+ res = self.class.post("/pluginManager/prevalidateConfig", xml_body(config))
13
+ raise ServerError, parse_error_message(res) unless res.code.to_i == 200
14
+ # why does HTTParty parses the response as xml?
15
+ JSON.parse(res.body)
16
+ end
17
+
18
+ def install_plugin(plugins, timeout = 60)
19
+ config = generate_config(plugins)
20
+ res = self.class.post("/pluginManager/installNecessaryPlugins", xml_body(config))
21
+ raise ServerError, parse_error_message(res) unless res.code.to_i == 302
22
+ end
23
+
24
+ private
25
+ def xml_body(xml_str)
26
+ {
27
+ :body => xml_str,
28
+ :format => :xml,
29
+ :headers => { 'content-type' => 'application/xml' }
30
+ }
31
+ end
32
+
33
+ def generate_config(config)
34
+ plugins = config.map do |v|
35
+ plugin = case
36
+ when v.instance_of?(String)
37
+ v.include?('@') ? v : v + '@*'
38
+ when v.instance_of?(Hash)
39
+ "#{v['name']}@#{v['version']}"
40
+ else
41
+ raise "Unknown value for plugin config: #{v}"
42
+ end
43
+ "<installation plugin='#{plugin}' />"
44
+ end.join("\n")
45
+ "<installations>#{plugins}</installations>"
46
+ end
47
+
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,43 @@
1
+
2
+ module Jenkins
3
+ class Client
4
+ module UpdateCenter
5
+
6
+ def installation_jobs
7
+ job = self.class.get("/updateCenter/api/json?tree=jobs[id,type,status[success],plugin[name,version]]")['jobs']
8
+ job.reduce([]) do |mem, job|
9
+ if job['type'] == 'InstallationJob'
10
+ mem << {
11
+ 'id' => job['id'],
12
+ 'type' => job['type'],
13
+ 'status' => job['status']['success'],
14
+ 'name' => job['plugin']['name'],
15
+ 'version' => job['plugin']['version']
16
+ }
17
+ end
18
+ mem
19
+ end
20
+ end
21
+
22
+ def wait_for_complete(timeout = 60 * 5)
23
+ due = Time.now + timeout
24
+ loop do
25
+ complete = installation_jobs.reduce(true) do |comp, job|
26
+ comp & (block_given? ? yield(job) : job['status'] == true)
27
+ end
28
+ break if complete
29
+ raise TimeoutError, "Installation timeout: #{timeout}" if Time.now > due
30
+ sleep 1
31
+ end
32
+ end
33
+
34
+ def restart_required?
35
+ res = self.class.get("/pluginManager/api/json?tree=restartRequiredForCompletion")
36
+ raise ServerError, parse_error_message(res) unless res.code.to_i == 200
37
+ res['restartRequiredForCompletion'] == true
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,40 @@
1
+
2
+ module Jenkins
3
+ class Client
4
+ module View
5
+
6
+ def view_names
7
+ self.class.get("/api/json")['views'].map {|view| view['name'] }
8
+ end
9
+
10
+ def create_view(name, config)
11
+ res = self.class.post("/createView/?name=#{CGI.escape(name)}", xml_body(config))
12
+ raise ServerError, parse_error_message(res) unless res.code.to_i == 200
13
+ rescue => e
14
+ raise ServerError, "Failed to create view: #{name}, make sure you have specified auth info properly"
15
+ end
16
+
17
+ def update_view(name, config)
18
+ res = self.class.post("/view/#{CGI.escape(name)}/config.xml", xml_body(config))
19
+ raise ServerError, parse_error_message(res) unless res.code.to_i == 200
20
+ rescue => e
21
+ raise ServerError, "Failed to create view: #{name}, make sure you have specified auth info properly"
22
+ end
23
+
24
+ def create_or_update_view(name, config)
25
+ view_names.include?(name) ? update_view(name, config) : create_view(name, config)
26
+ end
27
+
28
+ private
29
+ def xml_body(xml_str)
30
+ {
31
+ :body => xml_str,
32
+ :format => :xml,
33
+ :headers => { 'content-type' => 'application/xml' }
34
+ }
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+
@@ -1,5 +1,5 @@
1
1
  module Jenkins
2
2
  module Capistrano
3
- VERSION = "0.0.5.2"
3
+ VERSION = "0.0.6"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,95 +1,103 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: jenkins-capistrano
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 0
8
- - 5
9
- - 2
10
- version: 0.0.5.2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - cynipe
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2013-02-12 00:00:00 +09:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2013-02-16 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: capistrano
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 0
30
- version: "0"
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
31
22
  type: :runtime
32
- version_requirements: *id001
33
- - !ruby/object:Gem::Dependency
34
- name: httparty
35
23
  prerelease: false
36
- requirement: &id002 !ruby/object:Gem::Requirement
37
- requirements:
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: httparty
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
38
35
  - - ~>
39
- - !ruby/object:Gem::Version
40
- segments:
41
- - 0
42
- - 8
43
- - 3
36
+ - !ruby/object:Gem::Version
44
37
  version: 0.8.3
45
38
  type: :runtime
46
- version_requirements: *id002
47
- - !ruby/object:Gem::Dependency
48
- name: hpricot
49
39
  prerelease: false
50
- requirement: &id003 !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- segments:
55
- - 0
56
- version: "0"
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.8.3
46
+ - !ruby/object:Gem::Dependency
47
+ name: hpricot
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
57
54
  type: :runtime
58
- version_requirements: *id003
59
- - !ruby/object:Gem::Dependency
60
- name: rake
61
55
  prerelease: false
62
- requirement: &id004 !ruby/object:Gem::Requirement
63
- requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- segments:
67
- - 0
68
- version: "0"
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
69
70
  type: :development
70
- version_requirements: *id004
71
- - !ruby/object:Gem::Dependency
72
- name: pry
73
71
  prerelease: false
74
- requirement: &id005 !ruby/object:Gem::Requirement
75
- requirements:
76
- - - ">="
77
- - !ruby/object:Gem::Version
78
- segments:
79
- - 0
80
- version: "0"
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: pry
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
81
86
  type: :development
82
- version_requirements: *id005
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
83
94
  description: The capistrano tasks for Jenkins CI Server
84
- email:
95
+ email:
85
96
  - cynipe@gmail.com
86
97
  executables: []
87
-
88
98
  extensions: []
89
-
90
99
  extra_rdoc_files: []
91
-
92
- files:
100
+ files:
93
101
  - .gitignore
94
102
  - Gemfile
95
103
  - LICENSE
@@ -100,36 +108,33 @@ files:
100
108
  - lib/jenkins-capistrano/client.rb
101
109
  - lib/jenkins-capistrano/client/job.rb
102
110
  - lib/jenkins-capistrano/client/node.rb
111
+ - lib/jenkins-capistrano/client/plugin_manager.rb
112
+ - lib/jenkins-capistrano/client/update_center.rb
113
+ - lib/jenkins-capistrano/client/view.rb
103
114
  - lib/jenkins-capistrano/version.rb
104
- has_rdoc: true
105
115
  homepage: https://github.com/cynipe/jenkins-capistrano
106
116
  licenses: []
107
-
108
117
  post_install_message:
109
118
  rdoc_options: []
110
-
111
- require_paths:
119
+ require_paths:
112
120
  - lib
113
- required_ruby_version: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- segments:
118
- - 0
119
- version: "0"
120
- required_rubygems_version: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- segments:
125
- - 0
126
- version: "0"
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
127
133
  requirements: []
128
-
129
134
  rubyforge_project:
130
- rubygems_version: 1.3.6
135
+ rubygems_version: 1.8.24
131
136
  signing_key:
132
137
  specification_version: 3
133
- summary: ""
138
+ summary: ''
134
139
  test_files: []
135
-
140
+ has_rdoc: