jenkins-capistrano 0.0.5.2 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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: