lita-rundeck 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a05e15d4f0a1ba9383674cf2660e484e21314818
4
+ data.tar.gz: 45cc66b54ccb75da8e7e8b2220ef3a0ee266f85d
5
+ SHA512:
6
+ metadata.gz: ec1be3ae856e800ece20de568b48ded35de604642bd3d7353531de37dd0b3bf5692a082017371c0a06bcc65478357a8f23b6c422f610708888be27ee366b3201
7
+ data.tar.gz: 08a903ce8c3d4b8a8cf721a635f30c6119d4de34f8556ae131b859077e23c24ab03623e9edeabcccdeae8dd13818fd7293dfeb70ead70353d0d9e51d77bdb241
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ script: bundle exec rake
5
+ before_install:
6
+ - gem update --system
7
+ services:
8
+ - redis-server
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Harlan Barnes
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # lita-rundeck
2
+
3
+ [![Build Status](https://travis-ci.org/harlanbarnes/lita-rundeck.png?branch=master)](https://travis-ci.org/harlanbarnes/lita-rundeck)
4
+ [![Coverage Status](https://coveralls.io/repos/harlanbarnes/lita-rundeck/badge.png)](https://coveralls.io/r/harlanbarnes/lita-rundeck)
5
+
6
+ **lita-rundeck** is a handler for [Lita](https://github.com/jimmycuadra/lita) that interacts with a [Rundeck](http://rundeck.org/) server.
7
+
8
+ ## Installation
9
+
10
+ Add lita-rundeck to your Lita instance's Gemfile:
11
+
12
+ ``` ruby
13
+ gem "lita-rundeck"
14
+ ```
15
+
16
+ ## Configuration
17
+
18
+ ### Required Attributes
19
+
20
+ * ```url``` (String) - URL to then Rundeck server. Default: ```nil```
21
+ * ```token``` (String) - API token for access to the Rundeck server. Default: ```nil```
22
+
23
+ ### Optional Attributes
24
+
25
+ * ```api_debug``` (Boolean) - When ```conf.robot.log_level``` is set to ```:debug``` enable verbose details of the API interaction. Default: ```false```
26
+
27
+ ### Example
28
+
29
+ ```ruby
30
+ Lita.configure do |config|
31
+ config.handlers.rundeck.url = "https://rundeck.mycompany.org"
32
+ config.handlers.rundeck.token = "abcdefghijzlmnopqrstuvwxyz"
33
+ config.handlers.rundeck.api_debug = true
34
+ end
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ ### Run
40
+
41
+ Start a job execution
42
+
43
+ ```
44
+ Lita > lita rundeck run aliasfoo
45
+ Execution 297 is running
46
+
47
+ Lita > rundeck run aliasfoo --options SECONDS=60
48
+ Execution 298 is running
49
+
50
+ Lita > rundeck run --project Litatest --job dateoutput
51
+ Execution 299 is running
52
+
53
+ Lita > rundeck run --project Litatest --job dateoutput --options SECONDS=60
54
+ Execution 300 is running
55
+
56
+ Lita > rundeck run --project Litatest --job dateoutput --options SECONDS=60,FORMAT=iso8601
57
+ Execution 301 is running
58
+ ```
59
+
60
+ * Users must be [added by a Lita admin](http://docs.lita.io/getting-started/usage/#authorization-groups) to the rundeck_users group to execute jobs
61
+ * Job names with spaces need to be quoted
62
+ * Multiple options can be submitted via comma-delimited key pairs
63
+ * Jobs identified via fully qualified project and job names (i.e. --project and --job) or via a registered alias
64
+
65
+ ### Projects
66
+
67
+ List projects
68
+
69
+ ```
70
+ Lita > lita rundeck projects
71
+ [Litatest] - https://rundeck.mycompany.org/api/10/project/Litatest
72
+ ```
73
+
74
+ ### Jobs
75
+
76
+ List jobs
77
+
78
+ ```
79
+ Lita > lita rundeck jobs
80
+ [Litatest] - dateoutput
81
+ [Litatest] - refreshcache
82
+ ```
83
+
84
+ ### Executions
85
+
86
+ List execuctions (activity)
87
+
88
+ ```
89
+ Lita > lita rundeck execuctions
90
+ 295 succeeded Shell User [Litatest] dateoutput SECONDS:600 start:2014-08-16T04:36:43Z end:2014-08-16T04:46:46Z
91
+ 296 succeeded Shell User [Litatest] dateoutput SECONDS:60 start:2014-08-16T05:17:07Z end:2014-08-16T05:18:09Z
92
+ ```
93
+
94
+ * Fields are: id, status, submitter, project, job, options, start and end
95
+
96
+ Optionally, limit the output to a number of executions
97
+
98
+ ```
99
+ Lita > lita rundeck execuctions 1
100
+ 296 succeeded Shell User [Litatest] dateoutput SECONDS:60 start:2014-08-16T05:17:07Z end:2014-08-16T05:18:09Z
101
+ ```
102
+
103
+ ### Running
104
+
105
+ List currently running executions
106
+
107
+ ```
108
+ Lita > lita rundeck running
109
+ 297 running Shell User [Litatest] dateoutput SECONDS:60 start:2014-08-16T05:46:32Z
110
+ ```
111
+
112
+ ### Options
113
+
114
+ List options for a job in detail
115
+
116
+ ```
117
+ Lita > lita rundeck options aliasfoo
118
+ [Litatest] - dateoutput
119
+ * SECONDS (REQUIRED) - Number of seconds to run
120
+ ```
121
+
122
+ * Aliases or full qualified job and project names (i.e. --project and --name) can be used
123
+
124
+ ### Aliases
125
+
126
+ List aliases with
127
+
128
+ ```
129
+ Lita > lita rundeck aliases
130
+ Alias = [Project] - Job
131
+ aliasfoo = [Litatest] - dateoutput
132
+ ```
133
+
134
+ Register a new alias
135
+
136
+ ```
137
+ Lita > rundeck alias register aliasfoo --project Litatest --job dateoutput
138
+ Alias registered
139
+ ```
140
+
141
+ Forget (remove) an alias
142
+
143
+ ```
144
+ Lita > lita rundeck alias forget aliasfoo
145
+ Alias removed
146
+ ```
147
+
148
+ ### Info
149
+
150
+ Lists the server version and users allowed to execute jobs
151
+
152
+ ```
153
+ Lita > lita rundeck info
154
+ System Stats for Rundeck 2.0.4 on node rundeck.mycompany.org
155
+ Users allowed to execute jobs: Shell User
156
+ ```
157
+
158
+ ## License
159
+
160
+ [MIT](http://opensource.org/licenses/MIT)
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,7 @@
1
+ require "lita"
2
+
3
+ Lita.load_locales Dir[File.expand_path(
4
+ File.join("..", "..", "locales", "*.yml"), __FILE__
5
+ )]
6
+
7
+ require "lita/handlers/rundeck"
@@ -0,0 +1,648 @@
1
+ require 'xmlsimple'
2
+ require 'lita-keyword-arguments'
3
+
4
+ module Lita
5
+ module Handlers
6
+ class Rundeck < Handler
7
+
8
+ route /rundeck info/i,
9
+ :info, command: true, help: {
10
+ t("help.info_key") => t("help.info_value")
11
+ }
12
+
13
+ route /rundeck project(?:s)?/i,
14
+ :projects, command: true, help: {
15
+ t("help.projects_key") => t("help.projects_value")
16
+ }
17
+
18
+ route /rundeck job(?:s)?/i,
19
+ :jobs, command: true, help: {
20
+ t("help.jobs_key") => t("help.jobs_value")
21
+ }
22
+
23
+ route /rundeck exec(?:utions)?(?: (\d+)?)?/i,
24
+ :executions, command: true, help: {
25
+ t("help.exec_key") => t("help.exec_value")
26
+ }
27
+
28
+ route /rundeck running(?: (\d+)?)?/i,
29
+ :running, command: true, help: {
30
+ t("help.running_key") => t("help.running_value")
31
+ }
32
+
33
+ route /rundeck alias(?:es)?\s*$/i,
34
+ :aliases, command: true, help: {
35
+ t("help.alias_key") => t("help.alias_value")
36
+ }
37
+
38
+ route /rundeck alias register ([a-zA-Z0-9\-+.]+)\s*/i,
39
+ :alias_register,
40
+ command: true,
41
+ kwargs: {
42
+ project: {
43
+ short: "p"
44
+ },
45
+ job: {
46
+ short: "j"
47
+ },
48
+ options: {
49
+ short: "o"
50
+ }
51
+ },
52
+ help: {
53
+ t("help.alias_register_key") => t("help.alias_register_value")
54
+ }
55
+
56
+ route /rundeck alias forget ([a-zA-Z0-9\-+.]+)?\s*/i,
57
+ :alias_forget, command: true, help: {
58
+ t("help.alias_forget_key") => t("help.alias_forget_value")
59
+ }
60
+
61
+ route /rundeck run(?!ning)(?: ([a-zA-Z0-9\-+.]+))?\s*/i,
62
+ :run,
63
+ command: true,
64
+ kwargs: {
65
+ project: {
66
+ short: "p"
67
+ },
68
+ job: {
69
+ short: "j"
70
+ },
71
+ options: {
72
+ short: "o"
73
+ }
74
+ },
75
+ help: {
76
+ t("help.run_key") => t("help.run_value")
77
+ }
78
+
79
+ route /rundeck options(?: ([a-zA-Z0-9\-+.]+))?\s*/i,
80
+ :options,
81
+ command: true,
82
+ kwargs: {
83
+ project: {
84
+ short: "p"
85
+ },
86
+ job: {
87
+ short: "j"
88
+ }
89
+ },
90
+ help: {
91
+ t("help.options_key") => t("help.options_value")
92
+ }
93
+
94
+
95
+ def self.default_config(config)
96
+ config.url = nil
97
+ config.token = nil
98
+ config.api_debug = false
99
+ end
100
+
101
+ def info(response)
102
+ text = []
103
+ text.push(client.info)
104
+ if users
105
+ text.push(t("info.users_allowed") +
106
+ users.map{ |u| u.name }.join(",") )
107
+ else
108
+ text.push(t("info.no_users"))
109
+ end
110
+ response.reply text.join("\n")
111
+ end
112
+
113
+ def projects(response)
114
+ text = []
115
+ client.projects.each do |p|
116
+ text.push("[#{p.name}] - #{p.href}")
117
+ end
118
+ if text.empty?
119
+ response.reply t("projects.none")
120
+ else
121
+ response.reply text.join("\n")
122
+ end
123
+ end
124
+
125
+ def jobs(response)
126
+ text = []
127
+ client.jobs.each do |j|
128
+ line = ""
129
+ if alias_name = aliasdb.reverse(j.project,j.name)
130
+ line = "#{alias_name} = "
131
+ end
132
+ text.push(line + "[#{j.project}] - #{j.name}")
133
+ end
134
+ if text.empty?
135
+ response.reply t("jobs.none")
136
+ else
137
+ response.reply text.join("\n")
138
+ end
139
+ end
140
+
141
+ def executions(response)
142
+ max = response.matches[0][0] if response.matches[0][0]
143
+
144
+ text = []
145
+ client.executions(max).each do |e|
146
+ text.push(e.pretty_print)
147
+ end
148
+
149
+ if text.empty?
150
+ response.reply t("executions.none")
151
+ else
152
+ response.reply text.join("\n")
153
+ end
154
+ end
155
+
156
+ def running(response)
157
+ max = response.matches[0][0] if response.matches[0][0]
158
+
159
+ text = []
160
+ client.running(max).each do |e|
161
+ text.push(e.pretty_print)
162
+ end
163
+
164
+ if text.empty?
165
+ response.reply t("executions.none")
166
+ else
167
+ response.reply text.join("\n")
168
+ end
169
+ end
170
+
171
+ def run(response)
172
+ unless user_in_group?(response.user)
173
+ response.reply t("run.unauthorized")
174
+ return
175
+ end
176
+
177
+ args = response.extensions[:kwargs]
178
+ name = response.matches[0][0]
179
+
180
+ project = args[:project]
181
+ job = args[:job]
182
+ user = response.user.name || response.user.id || robot.name
183
+ options = parse_options(args[:options]) if args[:options]
184
+
185
+ # keywoard arguments win over an alias (if someone happens to give both)
186
+ unless project && job
187
+ project, job = aliasdb.forward(name)
188
+ end
189
+
190
+ unless project && job
191
+ response.reply t("misc.job_not_found")
192
+ return
193
+ end
194
+
195
+ response.reply resolve(client.run(project,job,options,user))
196
+ end
197
+
198
+ def resolve(e)
199
+ case e.status
200
+ when "running"
201
+ t("run.success", id: e.id)
202
+ when "api.error.execution.conflict"
203
+ t("run.conflict")
204
+ when "api.error.item.unauthorized"
205
+ t("run.token_unauthorized")
206
+ when "api.error.job.options-invalid"
207
+ e.message.gsub(/\n/,"")
208
+ end
209
+ end
210
+
211
+ def parse_options(string)
212
+ options = {}
213
+ pairs = string.split(/\|/)
214
+ pairs.each do |p|
215
+ if p =~ /\=/
216
+ k,v = p.split(/\=/)
217
+ options[k] = v
218
+ end
219
+ end
220
+ options
221
+ end
222
+
223
+ def options(response)
224
+ args = response.extensions[:kwargs]
225
+ name = response.matches[0][0]
226
+
227
+ project = args[:project]
228
+ job = args[:job]
229
+
230
+ # keywoard arguments win over an alias (if someone happens to give both)
231
+ unless project && job
232
+ project, job = aliasdb.forward(name)
233
+ end
234
+
235
+ unless project && job
236
+ response.reply t("misc.job_not_found")
237
+ return
238
+ end
239
+
240
+ response.reply "[#{project}] - #{job}\n" +
241
+ client.definition(project,job).pretty_print_options
242
+ end
243
+
244
+ def aliases(response)
245
+ all = aliasdb.all
246
+ if all.empty?
247
+ response.reply t("alias.none")
248
+ else
249
+ text = [ t('alias.list') ]
250
+ text.push(all.map{ |a| " #{a["id"]} = [#{a["project"]}] - #{a["job"]}" })
251
+ response.reply text.join("\n")
252
+ end
253
+ end
254
+
255
+ def alias_register(response)
256
+ args = response.extensions[:kwargs]
257
+ name = response.matches[0][0]
258
+ project = args[:project]
259
+ job = args[:job]
260
+
261
+ if name && project && job
262
+ begin
263
+ aliasdb.register(name,project,job)
264
+ response.reply t("alias.registered")
265
+ rescue ArgumentError
266
+ response.reply t("alias.exists")
267
+ end
268
+ else
269
+ response.reply t("alias.format")
270
+ end
271
+ end
272
+
273
+ def alias_forget(response)
274
+ name = response.matches[0][0]
275
+ begin
276
+ aliasdb.forget(name)
277
+ response.reply t("alias.forgotten")
278
+ rescue ArgumentError
279
+ response.reply t('alias.notexists')
280
+ end
281
+ end
282
+
283
+ def user_in_group?(user)
284
+ Lita::Authorization.user_in_group?(user,:rundeck_users)
285
+ end
286
+
287
+ def users
288
+ Lita::Authorization.groups_with_users[:rundeck_users]
289
+ end
290
+
291
+ def url
292
+ @url ||= Lita.config.handlers.rundeck.url
293
+ end
294
+
295
+ def token
296
+ @token ||= Lita.config.handlers.rundeck.token
297
+ end
298
+
299
+ def api_debug
300
+ @api_debug ||= Lita.config.handlers.rundeck.api_debug
301
+ end
302
+
303
+ def client
304
+ @client ||= API::Client.new(url,token,http,log,api_debug)
305
+ end
306
+
307
+ def aliasdb
308
+ @aliasdb ||= Database::Alias.new(redis)
309
+ end
310
+
311
+ module Database
312
+ class Alias
313
+ attr_accessor :redis
314
+
315
+ def initialize(redis)
316
+ @redis = redis
317
+ end
318
+
319
+ def akey(id)
320
+ "alias:#{id}"
321
+ end
322
+
323
+ def akeys
324
+ redis.keys("alias:*")
325
+ end
326
+
327
+ def ids
328
+ akeys.map { |e| e.split(/:/).last }
329
+ end
330
+
331
+ def register(id,project,job)
332
+ if registered?(id)
333
+ raise ArgumentError, "Alias already exists"
334
+ else
335
+ save(id,project,job)
336
+ end
337
+ end
338
+
339
+ def forget(id)
340
+ if registered?(id)
341
+ delete(id)
342
+ else
343
+ raise ArgumentError, "Alias does not exist"
344
+ end
345
+ end
346
+
347
+ def save(id,project,job)
348
+ redis.hmset(akey(id), "project", project, "job", job)
349
+ end
350
+
351
+ def delete(id)
352
+ redis.del(akey(id))
353
+ end
354
+
355
+ def forward(id)
356
+ redis.hmget(akey(id), "project", "job")
357
+ end
358
+
359
+ def reverse(project,job)
360
+ ids.select{ |n| p, j = forward(n); p == project && j == job }.first
361
+ end
362
+
363
+ def all
364
+ list = []
365
+ ids.each do |id|
366
+ hash = {}
367
+ hash["id"] = id
368
+ hash["project"], hash["job"] = forward(id)
369
+ list.push(hash)
370
+ end
371
+ list
372
+ end
373
+
374
+ def registered?(id)
375
+ project, job = forward(id)
376
+ if project && job
377
+ true
378
+ else
379
+ false
380
+ end
381
+ end
382
+ end
383
+ end
384
+
385
+ module API
386
+ class Client
387
+ attr_accessor :url, :token
388
+
389
+ MAX_EXECUTIONS = 10
390
+
391
+ def self.ensure_array(ref)
392
+ return [ref] if ref.is_a?(Hash)
393
+ return ref if ref.is_a?(Array)
394
+ end
395
+
396
+ def initialize(url, token, http, log, debug=false)
397
+ @url = url
398
+ @token = token
399
+ @http = http
400
+ @log = log
401
+ @debug = debug
402
+ end
403
+
404
+ def get(path,options={})
405
+ uri = "#{@url}/#{path}"
406
+ options[:authtoken] = @token
407
+
408
+ http_response = @http.get(
409
+ uri,
410
+ options
411
+ )
412
+
413
+ # Trying to avoid nokogiri but not wanting to use ReXML directly,
414
+ # hence the xmlsimple gem. ForceArray has usually worked well for
415
+ # me, but this XML data seemed to cause it to be inconsistent. So
416
+ # the ensure_array method and a little extra code has worked.
417
+ hash = ::XmlSimple.xml_in(
418
+ http_response.body,
419
+ {
420
+ "ForceArray" => false,
421
+ "GroupTags" => {
422
+ "options" => "option"
423
+ }
424
+ }
425
+ )
426
+
427
+ if @debug
428
+ output = options.map{ |k,v| "#{k.to_s}=#{v}" }.join("&")
429
+ @log.debug "API request: GET #{uri}&#{output}"
430
+ @log.debug "API response: (HTTP #{http_response.status}) #{http_response.body}"
431
+ @log.debug "Hash: #{hash.inspect}"
432
+ end
433
+
434
+ hash
435
+ end
436
+
437
+ def info
438
+ get('/api/1/system/info')["success"][1]["message"]
439
+ end
440
+
441
+ def projects
442
+ @projects ||= Project.all(self).sort_by{|p| p.name}
443
+ end
444
+
445
+ def jobs
446
+ @jobs ||= Job.all(self).sort_by{|j| [j.project, j.name]}
447
+ end
448
+
449
+ def job(project,name)
450
+ jobs.select{|j| j.project == project && j.name == name}.first
451
+ end
452
+
453
+ def definition(project,name)
454
+ Definition.load(self,job(project,name).id)
455
+ end
456
+
457
+ def executions(max)
458
+ max ||= MAX_EXECUTIONS
459
+ @executions ||= Execution.all(self,max).sort_by{|i| i.id}.reverse[0,max.to_i].reverse
460
+ end
461
+
462
+ def running(max)
463
+ max ||= MAX_EXECUTIONS
464
+ @running ||= Running.all(self,max).sort_by{|i| i.id}.reverse[0,max].reverse
465
+ end
466
+
467
+ def run(project,name,options,user)
468
+ job = job(project,name)
469
+ Job.run(self,job.id,options,user)
470
+ end
471
+ end
472
+
473
+ class Project
474
+ attr_accessor :name, :description, :href
475
+
476
+ def self.all(client)
477
+ all = []
478
+ response = client.get("/api/1/projects")
479
+ if response["projects"]["count"].to_i > 0
480
+ Client.ensure_array(response["projects"]["project"]).each do |p|
481
+ all.push(Project.new(p))
482
+ end
483
+ end
484
+ all
485
+ end
486
+
487
+ def initialize(hash)
488
+ @name = hash["name"]
489
+ @description = hash["description"]
490
+ @href = hash["href"]
491
+ end
492
+ end
493
+
494
+ class Definition
495
+ attr_accessor :id, :name, :project, :description, :options
496
+
497
+ def self.load(client,id)
498
+ response = client.get("/api/1/job/#{id}")
499
+ if response["job"]
500
+ Definition.new(response["job"])
501
+ end
502
+ end
503
+
504
+ def initialize(hash)
505
+ @id = hash["id"]
506
+ @name = hash["name"]
507
+ @project = hash["context"]["project"]
508
+ @description = hash["description"]
509
+ if option_response = hash["context"]["options"]
510
+ @options ||= {}
511
+ Client.ensure_array(option_response).each do |o|
512
+ @options[o["name"]] = o
513
+ end
514
+ end
515
+ end
516
+
517
+ def pretty_print_options
518
+ text = []
519
+ @options.each do |name,data|
520
+ text.push(
521
+ " * #{name} " +
522
+ ( data["required"] ? "(REQUIRED) " : "" ) +
523
+ ( data["description"] ? "- #{data["description"]}" : "" )
524
+ )
525
+ end
526
+ text.join("\n")
527
+ end
528
+ end
529
+
530
+ class Job
531
+ attr_accessor :id, :name, :group, :project, :description,
532
+ :average_duration, :options
533
+
534
+ def self.all(client)
535
+ all = []
536
+ client.projects.each do |p|
537
+ response = client.get("/api/2/project/#{p.name}/jobs")
538
+ if response["jobs"]["count"].to_i > 0
539
+ Client.ensure_array(response["jobs"]["job"]).each do |j|
540
+ all.push(Job.new(j))
541
+ end
542
+ end
543
+ end
544
+ all
545
+ end
546
+
547
+ def self.run(client,id,options,user)
548
+ args = {}
549
+ args[:asUser] = user if user
550
+ if options
551
+ arg_string = []
552
+ options.each do |k,v|
553
+ arg_string.push("-#{k} #{v}")
554
+ end
555
+ args[:argString] = arg_string.join(" ")
556
+ end
557
+
558
+ api_response = client.get("/api/5/job/#{id}/run", args)
559
+
560
+ if api_response["success"]
561
+ Execution.new(api_response["executions"]["execution"])
562
+ elsif api_response["error"][0]
563
+ Execution.new(
564
+ "status" => api_response["error"][1]["code"],
565
+ "message" => api_response["error"][1]["message"]
566
+ )
567
+ end
568
+ end
569
+
570
+ def initialize(hash)
571
+ @id = hash["id"]
572
+ @name = hash["name"]
573
+ @group = hash["group"]
574
+ @project = hash["project"]
575
+ @description = hash["description"]
576
+ @average_duration = hash["average_duration"] if hash["average_duration"]
577
+ @options = Client.ensure_array(hash["options"]) if hash["options"]
578
+ end
579
+ end
580
+
581
+ class Execution
582
+ attr_accessor :id, :href, :status, :message, :project, :user, :start,
583
+ :end, :job, :description, :argstring, :successful_nodes,
584
+ :failed_nodes, :aborted_by
585
+
586
+ def self.all(client,max)
587
+ all = []
588
+ client.projects.each do |p|
589
+ response = client.get("/api/5/executions",
590
+ project: p.name, max: max)
591
+ if response["executions"]["count"].to_i > 0
592
+ list = Client.ensure_array(response["executions"]["execution"])
593
+ list.each do |e|
594
+ all.push(Execution.new(e))
595
+ end
596
+ end
597
+ end
598
+ all
599
+ end
600
+
601
+ def initialize(hash)
602
+ @id = hash["id"]
603
+ @href = hash["href"]
604
+ @status = hash["status"]
605
+ @message = hash["message"]
606
+ @project = hash["project"]
607
+ @user = hash["user"]
608
+ @start = hash["date-started"]["content"] if hash["date-started"]
609
+ @end = hash["date-ended"]["content"] if hash["date-ended"]
610
+ @job = Job.new(hash["job"]) if hash["job"]
611
+ @description = hash["description"]
612
+ @argstring = hash["argstring"]
613
+ @aborted_by = hash["aborted_by"]
614
+ @successful_nodes = Client.ensure_array(hash["successful_nodes"]["node"]) if hash["successful_nodes"]
615
+ @failed_nodes = Client.ensure_array(hash["failed_nodes"]["node"]) if hash["failed_nodes"]
616
+ end
617
+
618
+ def pretty_print
619
+ line = "#{@id} #{@status} #{@user} [#{@job.project}] #{@job.name} "
620
+ line += @job.options.map { |o| "#{o["name"]}:#{o["value"]}" }.join(", ") + " " if @job.options
621
+ line += "start:#{@start}" + ( @end ? " end:#{@end}" : "" )
622
+ end
623
+ end
624
+
625
+ class Running < Execution
626
+
627
+ def self.all(client,max)
628
+ all = []
629
+ client.projects.each do |p|
630
+ response = client.get("/api/5/executions/running",
631
+ project: p.name, max: max)
632
+ if response["executions"]["count"].to_i > 0
633
+ list = Client.ensure_array(response["executions"]["execution"])
634
+ list.each do |e|
635
+ all.push(Execution.new(e))
636
+ end
637
+ end
638
+ end
639
+ all
640
+ end
641
+
642
+ end
643
+ end
644
+ end
645
+
646
+ Lita.register_handler(Rundeck)
647
+ end
648
+ end