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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +160 -0
- data/Rakefile +6 -0
- data/lib/lita-rundeck.rb +7 -0
- data/lib/lita/handlers/rundeck.rb +648 -0
- data/lita-rundeck.gemspec +26 -0
- data/locales/en.yml +51 -0
- data/spec/files/definition.xml +24 -0
- data/spec/files/executions.xml +188 -0
- data/spec/files/executions_empty.xml +3 -0
- data/spec/files/info.xml +52 -0
- data/spec/files/jobs.xml +22 -0
- data/spec/files/jobs_empty.xml +3 -0
- data/spec/files/projects.xml +8 -0
- data/spec/files/projects_empty.xml +3 -0
- data/spec/files/run.xml +19 -0
- data/spec/files/run_conflict.xml +5 -0
- data/spec/files/run_options_invalid.xml +6 -0
- data/spec/files/run_unauthorized.xml +5 -0
- data/spec/files/running.xml +19 -0
- data/spec/files/running_empty.xml +3 -0
- data/spec/lita/handlers/rundeck_spec.rb +383 -0
- data/spec/spec_helper.rb +10 -0
- metadata +199 -0
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
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
|
+
[](https://travis-ci.org/harlanbarnes/lita-rundeck)
|
4
|
+
[](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
data/lib/lita-rundeck.rb
ADDED
@@ -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
|