opsask 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.yardoc/checksums +4 -0
  4. data/.yardoc/object_types +0 -0
  5. data/.yardoc/objects/root.dat +0 -0
  6. data/.yardoc/proxy_types +0 -0
  7. data/Gemfile +15 -0
  8. data/Gemfile.lock +75 -0
  9. data/LICENSE +13 -0
  10. data/Rakefile +30 -0
  11. data/Readme.md +3 -0
  12. data/VERSION +1 -0
  13. data/bin/opsask +4 -0
  14. data/lib/opsask/app.rb +540 -0
  15. data/lib/opsask/main.rb +54 -0
  16. data/lib/opsask/metadata.rb +29 -0
  17. data/lib/opsask.rb +3 -0
  18. data/opsask.gemspec +29 -0
  19. data/public/css/foundation.css +5068 -0
  20. data/public/css/foundation.min.css +1 -0
  21. data/public/css/normalize.css +410 -0
  22. data/public/css/style.css +152 -0
  23. data/public/favicon.ico +0 -0
  24. data/public/fonts/DINMittelschriftStd.woff +0 -0
  25. data/public/img/.gitkeep +1 -0
  26. data/public/js/foundation/foundation.abide.js +256 -0
  27. data/public/js/foundation/foundation.accordion.js +49 -0
  28. data/public/js/foundation/foundation.alert.js +37 -0
  29. data/public/js/foundation/foundation.clearing.js +485 -0
  30. data/public/js/foundation/foundation.dropdown.js +208 -0
  31. data/public/js/foundation/foundation.equalizer.js +64 -0
  32. data/public/js/foundation/foundation.interchange.js +326 -0
  33. data/public/js/foundation/foundation.joyride.js +848 -0
  34. data/public/js/foundation/foundation.js +587 -0
  35. data/public/js/foundation/foundation.magellan.js +171 -0
  36. data/public/js/foundation/foundation.offcanvas.js +39 -0
  37. data/public/js/foundation/foundation.orbit.js +464 -0
  38. data/public/js/foundation/foundation.reveal.js +399 -0
  39. data/public/js/foundation/foundation.tab.js +58 -0
  40. data/public/js/foundation/foundation.tooltip.js +215 -0
  41. data/public/js/foundation/foundation.topbar.js +387 -0
  42. data/public/js/foundation/jquery.cookie.js +8 -0
  43. data/public/js/foundation.min.js +10 -0
  44. data/public/js/opsask.js +19 -0
  45. data/public/js/vendor/fastclick.js +9 -0
  46. data/public/js/vendor/jquery.cookie.js +8 -0
  47. data/public/js/vendor/jquery.js +26 -0
  48. data/public/js/vendor/modernizr.js +8 -0
  49. data/public/js/vendor/placeholder.js +2 -0
  50. data/tasks/generate-email-summary.rb +73 -0
  51. data/tasks/send-email-summary.sh +13 -0
  52. data/views/_components.erb +6 -0
  53. data/views/_count.erb +11 -0
  54. data/views/_flash.erb +3 -0
  55. data/views/_form.erb +37 -0
  56. data/views/_queue.erb +16 -0
  57. data/views/agile.erb +6 -0
  58. data/views/glance.erb +19 -0
  59. data/views/index.erb +27 -0
  60. data/views/layout.erb +43 -0
  61. data/views/stragglers.erb +6 -0
  62. data/views/untracked.erb +6 -0
  63. metadata +217 -0
data/lib/opsask/app.rb ADDED
@@ -0,0 +1,540 @@
1
+ require 'curb'
2
+ require 'sinatra/base'
3
+ require 'sinatra/partial'
4
+ require 'rack-flash'
5
+ require 'json'
6
+ require 'jira'
7
+
8
+ require_relative 'metadata'
9
+
10
+
11
+ module OpsAsk
12
+ class App < Sinatra::Base
13
+ set :root, OpsAsk::ROOT
14
+
15
+ # Add flash support
16
+ enable :sessions
17
+ use Rack::Flash
18
+
19
+ # Add partials support
20
+ register Sinatra::Partial
21
+ set :partial_template_engine, :erb
22
+ enable :partial_underscores
23
+
24
+ # Serve up our form
25
+ get '/' do
26
+ erb :index, locals: {
27
+ jiras_for_today: issues_for(today),
28
+ jiras_for_tomorrow: issues_for(tomorrow),
29
+ untracked_jiras: untracked_issues,
30
+ stragglers: straggling_issues
31
+ }
32
+ end
33
+
34
+ get '/glance' do
35
+ erb :glance, locals: {
36
+ jiras_for_today: issues_for(today),
37
+ jiras_for_tomorrow: issues_for(tomorrow),
38
+ untracked_jiras: untracked_issues,
39
+ stragglers: straggling_issues
40
+ }
41
+ end
42
+
43
+ get '/untracked' do
44
+ erb :untracked, locals: {
45
+ untracked_jiras: untracked_issues,
46
+ stragglers: straggling_issues
47
+ }
48
+ end
49
+
50
+ get '/stragglers' do
51
+ erb :stragglers, locals: {
52
+ untracked_jiras: untracked_issues,
53
+ stragglers: straggling_issues
54
+ }
55
+ end
56
+
57
+ get '/sprint/:sprint_num' do
58
+ erb :agile, locals: {
59
+ asks: asks_in_sprint(params[:sprint_num]),
60
+ sprint: get_sprint(params[:sprint_num]),
61
+ untracked_jiras: untracked_issues,
62
+ stragglers: straggling_issues
63
+ }
64
+ end
65
+
66
+ get '/agile' do
67
+ erb :agile, locals: {
68
+ asks: asks_in_current_sprint,
69
+ sprint: current_sprint([]),
70
+ untracked_jiras: untracked_issues,
71
+ stragglers: straggling_issues
72
+ }
73
+ end
74
+
75
+ # I think everyone should do this
76
+ get '/version' do
77
+ content_type :txt
78
+ "opsask #{settings.config[:app_version]}"
79
+ end
80
+
81
+ # Try to create a JIRA
82
+ post '/' do
83
+ duedate = validate_room_for_new_jiras
84
+ component, summary, description, assign_to_me, epic, ops_only = validate_jira_params
85
+ jira = create_jira duedate, component, summary, description, assign_to_me, epic, ops_only
86
+ if jira.nil? or !jira.has_key?('key')
87
+ flash[:error] = [ %Q| Failure!
88
+ JIRA had an issue processing your request. Try again?
89
+ | ]
90
+ else
91
+ flash[:notice] = [ %Q| Success!
92
+ <a href="#{settings.config[:jira_url]}/browse/#{jira['key']}">#{jira['key']}</a>
93
+ has been created on your behalf.
94
+ | ]
95
+ end
96
+ redirect '/'
97
+ end
98
+
99
+ # Public assets
100
+ %w[ css img js fonts ].each do |asset|
101
+ get "/#{asset}/:file" do
102
+ send_file "public/#{asset}/#{params[:file]}", :disposition => 'inline'
103
+ end
104
+ end
105
+
106
+ get '/favicon.ico' do
107
+ send_file 'public/favicon.ico', :disposition => 'inline'
108
+ end
109
+
110
+
111
+
112
+ # This section gets called before every request. Here, we set up the
113
+ # OAuth consumer details including the consumer key, private key,
114
+ # site uri, and the request token, access token, and authorize paths
115
+ before do
116
+ options = {
117
+ :site => settings.config[:jira_url],
118
+ :context_path => '',
119
+ :signature_method => 'RSA-SHA1',
120
+ :request_token_path => "#{settings.config[:jira_url]}/plugins/servlet/oauth/request-token",
121
+ :authorize_url => "#{settings.config[:jira_url]}/plugins/servlet/oauth/authorize",
122
+ :access_token_path => "#{settings.config[:jira_url]}/plugins/servlet/oauth/access-token",
123
+ :private_key_file => settings.config[:jira_private_key],
124
+ :rest_base_path => "#{settings.config[:jira_url]}/rest/api/latest",
125
+ :consumer_key => settings.config[:jira_consumer_key]
126
+ }
127
+
128
+ @jira_client = JIRA::Client.new(options)
129
+ # @jira_client.consumer.http.set_debug_output($stderr)
130
+
131
+ # Add AccessToken if authorised previously.
132
+ if session[:jira_auth]
133
+ @jira_client.set_access_token(
134
+ session[:jira_auth][:access_token],
135
+ session[:jira_auth][:access_key]
136
+ )
137
+
138
+ if @project.nil?
139
+ @project = @jira_client.Project.find(settings.config[:project_key])
140
+ end
141
+ end
142
+
143
+ # Keep a pointer to myself
144
+ begin
145
+ response = @jira_client.get(
146
+ @jira_client.options[:rest_base_path] + '/myself?expand=groups'
147
+ )
148
+ @myself = JSON::parse response.body
149
+ @me = @myself['name']
150
+ rescue JIRA::OauthClient::UninitializedAccessTokenError
151
+ end
152
+ end
153
+
154
+ # Retrieves the @access_token then stores it inside a session cookie. In a real app,
155
+ # you'll want to persist the token in a datastore associated with the user.
156
+ get '/callback/' do
157
+ request_token = @jira_client.set_request_token(
158
+ session[:request_token], session[:request_secret]
159
+ )
160
+ access_token = @jira_client.init_access_token(
161
+ :oauth_verifier => params[:oauth_verifier]
162
+ )
163
+
164
+ session[:jira_auth] = {
165
+ :access_token => access_token.token,
166
+ :access_key => access_token.secret
167
+ }
168
+
169
+ session.delete(:request_token)
170
+ session.delete(:request_secret)
171
+
172
+ redirect '/'
173
+ end
174
+
175
+ # Initialize the JIRA session
176
+ get '/login' do
177
+ request_token = @jira_client.request_token
178
+ session[:request_token] = request_token.token
179
+ session[:request_secret] = request_token.secret
180
+ redirect request_token.authorize_url
181
+ end
182
+
183
+ # Expire the JIRA session
184
+ get '/logout' do
185
+ session.delete(:jira_auth)
186
+ redirect '/'
187
+ end
188
+
189
+
190
+
191
+ private
192
+ def logged_in?
193
+ !!session[:jira_auth]
194
+ end
195
+
196
+ def ops?
197
+ return false unless logged_in?
198
+ @myself['groups']['items'].each do |i|
199
+ return true if i['name'] == settings.config[:ops_group]
200
+ end
201
+ return false
202
+ end
203
+
204
+ def one_day
205
+ 1 * 24 * 60 * 60 # Day * Hour * Minute * Second = Seconds / Day
206
+ end
207
+
208
+ def now
209
+ Time.now # + 3 * one_day # DEBUG
210
+ end
211
+
212
+ def todays_date offset=0
213
+ date = now + offset
214
+ date += one_day if date.saturday?
215
+ date += one_day if date.sunday?
216
+ return date
217
+ end
218
+
219
+ def asks_in_current_sprint
220
+ # TODO
221
+ end
222
+
223
+ def asks_in_sprint num
224
+ return [] unless logged_in?
225
+ issues = []
226
+ @jira_client.Issue.jql("project = #{settings.config[:project_name]} AND labels in (OpsAsk) and labels in (Sprint#{num})").each do |i|
227
+ issues << i.attrs
228
+ end
229
+ return issues
230
+ end
231
+
232
+ def sprints
233
+ url = "#{settings.config[:jira_url]}/rest/greenhopper/1.0/sprintquery/#{settings.config[:agile_board]}"
234
+ curl_request = Curl::Easy.http_get(url) do |curl|
235
+ curl.headers['Accept'] = 'application/json'
236
+ curl.headers['Content-Type'] = 'application/json'
237
+ curl.http_auth_types = :basic
238
+ curl.username = settings.config[:jira_user]
239
+ curl.password = settings.config[:jira_pass]
240
+ curl.verbose = true
241
+ end
242
+
243
+ raw_response = curl_request.body_str
244
+ begin
245
+ data = JSON::parse(raw_response)
246
+ return data['sprints']
247
+ rescue
248
+ $stderr.puts "Failed to parse response from JIRA: #{raw_response}"
249
+ end
250
+ return nil
251
+ end
252
+
253
+ def get_sprint num
254
+ sprint = sprints.select { |s| s['name'] == "Sprint #{num}" }
255
+ sprint_id = sprint.first['id']
256
+ url = "#{settings.config[:jira_url]}/rest/greenhopper/1.0/rapid/charts/sprintreport?rapidViewId=#{settings.config[:agile_board]}&sprintId=#{sprint_id}"
257
+ curl_request = Curl::Easy.http_get(url) do |curl|
258
+ curl.headers['Accept'] = 'application/json'
259
+ curl.headers['Content-Type'] = 'application/json'
260
+ curl.http_auth_types = :basic
261
+ curl.username = settings.config[:jira_user]
262
+ curl.password = settings.config[:jira_pass]
263
+ curl.verbose = true
264
+ end
265
+
266
+ raw_response = curl_request.body_str
267
+ begin
268
+ data = JSON::parse(raw_response)
269
+ contents = data.delete('contents')
270
+ data = data.delete('sprint')
271
+ return data.merge(contents)
272
+ rescue
273
+ $stderr.puts "Failed to parse response from JIRA: #{raw_response}"
274
+ end
275
+ return {}
276
+ end
277
+
278
+ def current_sprint_name sprint=current_sprint
279
+ sprint.nil? ? nil : sprint['name'].gsub(/\s+/, '')
280
+ end
281
+
282
+ def current_sprint keys=[ 'sprintsData', 'sprints', 0 ]
283
+ url = "#{settings.config[:jira_url]}/rest/greenhopper/1.0/xboard/work/allData.json?rapidViewId=#{settings.config[:agile_board]}"
284
+ curl_request = Curl::Easy.http_get(url) do |curl|
285
+ curl.headers['Accept'] = 'application/json'
286
+ curl.headers['Content-Type'] = 'application/json'
287
+ curl.http_auth_types = :basic
288
+ curl.username = settings.config[:jira_user]
289
+ curl.password = settings.config[:jira_pass]
290
+ curl.verbose = true
291
+ end
292
+
293
+ raw_response = curl_request.body_str
294
+ begin
295
+ data = JSON::parse(raw_response)
296
+ keys.each { |k| data = data[k] }
297
+ return data
298
+ rescue
299
+ $stderr.puts "Failed to parse response from JIRA: #{raw_response}"
300
+ end
301
+ return nil
302
+ end
303
+
304
+ def today offset=0
305
+ todays_date(offset).strftime '%Y-%m-%d'
306
+ end
307
+
308
+ def tomorrow
309
+ today(one_day)
310
+ end
311
+
312
+ def name_for_today offset=0
313
+ todays_date(offset).strftime '%A %^b %-d'
314
+ end
315
+
316
+ def name_for_tomorrow
317
+ name_for_today(one_day)
318
+ end
319
+
320
+ def name_for_coming_week
321
+ todays_date.strftime 'Week of %^b %-d'
322
+ end
323
+
324
+ def jiras_for date
325
+ return [] unless logged_in?
326
+ unless ops?
327
+ return @jira_client.Issue.jql("due = #{date} AND project = #{settings.config[:project_name]} AND type != Change AND labels not in (OpsOnly)")
328
+ end
329
+ return @jira_client.Issue.jql("due = #{date} AND project = #{settings.config[:project_name]} AND type != Change")
330
+ end
331
+
332
+ def jira_count_for date
333
+ jiras_for(date).length
334
+ end
335
+
336
+ def jira_count_for_today ; jira_count_for(today) end
337
+
338
+ def jira_count_for_tomorrow ; jira_count_for(tomorrow) end
339
+
340
+ def raw_classes_for jira
341
+ classes = [ jira.fields['resolution'].nil? ? 'open' : 'closed' ]
342
+ classes << jira.fields['assignee']['name'].downcase.gsub(/\W+/, '')
343
+ end
344
+
345
+ def classes_for jira
346
+ raw_classes_for(jira).join(' ')
347
+ end
348
+
349
+ def sorting_key_for jira
350
+ rcs = raw_classes_for(jira)
351
+ idx = 1
352
+ idx = 2 if rcs.include? 'denimcores'
353
+ idx = 0 if rcs.include? 'closed'
354
+ return "#{idx}-#{jira.key}"
355
+ end
356
+
357
+ def issues_for date
358
+ jiras_for(date).sort_by do |jira|
359
+ sorting_key_for(jira)
360
+ end.reverse
361
+ end
362
+
363
+ def its_the_weekend?
364
+ now.saturday? || now.sunday?
365
+ end
366
+
367
+ def room_for_new_jiras_for? date
368
+ return true if ops?
369
+ jira_count_for(date) < settings.config[:queue_size]
370
+ end
371
+
372
+ def date_for_new_jiras
373
+ if now.hour < settings.config[:cutoff_hour] || its_the_weekend?
374
+ return today if room_for_new_jiras_for? today
375
+ return tomorrow if room_for_new_jiras_for? tomorrow
376
+ else
377
+ return tomorrow if room_for_new_jiras_for? tomorrow
378
+ end
379
+ return nil
380
+ end
381
+
382
+ def room_for_new_jiras?
383
+ return true if ops?
384
+ !date_for_new_jiras.nil?
385
+ end
386
+
387
+ def validate_room_for_new_jiras
388
+ duedate = date_for_new_jiras
389
+ return duedate unless duedate.nil?
390
+ flash[:error] = [ "Sorry, there's is no room for new JIRAs" ]
391
+ redirect '/'
392
+ end
393
+
394
+ def validate_jira_params
395
+ flash[:error] = []
396
+ flash[:error] << 'Summary is required' if params['jira-summary'].empty?
397
+ redirect '/' unless flash[:error].empty?
398
+ return [
399
+ params['jira-component'],
400
+ params['jira-summary'],
401
+ params['jira-description'],
402
+ !!params['jira-assign_to_me'],
403
+ params['jira-epic'],
404
+ !!params['jira-ops_only']
405
+ ]
406
+ end
407
+
408
+ def create_jira duedate, component, summary, description, assign_to_me, epic, ops_only
409
+ epic = 'INF-3091' if epic.nil? # OpsAsk default epic
410
+ assignee = assign_to_me ? @me : settings.config[:assignee]
411
+ components = []
412
+ components = [ { name: component } ] unless component
413
+ labels = [ 'OpsAsk', current_sprint_name ].compact
414
+ labels << 'OpsOnly' if ops_only
415
+ data = {
416
+ fields: {
417
+ project: { key: settings.config[:project_key] },
418
+ issuetype: { name: settings.config[:issue_type] },
419
+ versions: [ { name: settings.config[:version] } ],
420
+ duedate: duedate,
421
+ summary: summary,
422
+ description: description,
423
+ components: components,
424
+ assignee: { name: assignee },
425
+ reporter: { name: @me },
426
+ labels: labels,
427
+ customfield_10002: 1, # Story Points = 1
428
+ # customfield_10350: epic,
429
+ customfield_10040: { id: '-1' } # Release Priority = None
430
+ }
431
+ }
432
+
433
+ url = "#{settings.config[:jira_url]}/rest/api/latest/issue"
434
+ curl_request = Curl::Easy.http_post(url, data.to_json) do |curl|
435
+ curl.headers['Accept'] = 'application/json'
436
+ curl.headers['Content-Type'] = 'application/json'
437
+ curl.http_auth_types = :basic
438
+ curl.username = settings.config[:jira_user]
439
+ curl.password = settings.config[:jira_pass]
440
+ curl.verbose = true
441
+ end
442
+
443
+ raw_response = curl_request.body_str
444
+ begin
445
+ response = JSON::parse raw_response
446
+ rescue
447
+ $stderr.puts "Failed to parse response from JIRA: #{raw_response}"
448
+ return nil
449
+ end
450
+ return response
451
+ end
452
+
453
+ def components
454
+ return @project.components.map(&:name).select { |c| c =~ /^Ops/ }
455
+ end
456
+
457
+ def untracked_issues
458
+ return [] unless logged_in?
459
+ constraints = [
460
+ "project = #{settings.config[:project_name]}",
461
+ "due < #{today}",
462
+ "resolution = unresolved",
463
+ "assignee = denimcores"
464
+ ].join(' AND ')
465
+ @jira_client.Issue.jql(constraints).sort_by do |jira|
466
+ sorting_key_for(jira)
467
+ end.reverse
468
+ end
469
+
470
+ def straggling_issues
471
+ return [] unless logged_in?
472
+ constraints = [
473
+ "project = #{settings.config[:project_name]}",
474
+ "due < #{today}",
475
+ "labels in (OpsAsk)",
476
+ "resolution = unresolved",
477
+ "assignee != denimcores"
478
+ ].join(' AND ')
479
+ @jira_client.Issue.jql(constraints).sort_by do |jira|
480
+ sorting_key_for(jira)
481
+ end.reverse
482
+ end
483
+
484
+ def epics
485
+ data = {
486
+ jql: "type = Epic AND project = #{settings.config[:project_name]}",
487
+ startAt: 0,
488
+ maxResults: 1000
489
+ }
490
+
491
+ url = "#{settings.config[:jira_url]}/rest/api/latest/search"
492
+ curl_request = Curl::Easy.http_post(url, data.to_json) do |curl|
493
+ curl.headers['Accept'] = 'application/json'
494
+ curl.headers['Content-Type'] = 'application/json'
495
+ curl.http_auth_types = :basic
496
+ curl.username = settings.config[:jira_user]
497
+ curl.password = settings.config[:jira_pass]
498
+ curl.verbose = true
499
+ end
500
+
501
+ raw_response = curl_request.body_str
502
+ begin
503
+ response = JSON::parse raw_response
504
+ rescue
505
+ $stderr.puts "Failed to parse response from JIRA: #{raw_response}"
506
+ return nil
507
+ end
508
+ return response['issues'].map do |epic|
509
+ {
510
+ 'key' => epic['key'],
511
+ 'name' => epic['fields']['customfield_10351'] || epic['fields']['summary']
512
+ }
513
+ end
514
+ end
515
+
516
+ def epic key
517
+ url = "#{settings.config[:jira_url]}/rest/api/latest/issue/#{key}"
518
+ curl_request = Curl::Easy.http_get(url) do |curl|
519
+ curl.headers['Accept'] = 'application/json'
520
+ curl.headers['Content-Type'] = 'application/json'
521
+ curl.http_auth_types = :basic
522
+ curl.username = settings.config[:jira_user]
523
+ curl.password = settings.config[:jira_pass]
524
+ curl.verbose = true
525
+ end
526
+
527
+ raw_response = curl_request.body_str
528
+ begin
529
+ response = JSON::parse raw_response
530
+ rescue
531
+ $stderr.puts "Failed to parse response from JIRA: #{raw_response}"
532
+ return nil
533
+ end
534
+ return {
535
+ 'key' => response['key'],
536
+ 'name' => response['fields']['customfield_10351'] || response['fields']['summary']
537
+ }
538
+ end
539
+ end
540
+ end
@@ -0,0 +1,54 @@
1
+ require 'thor'
2
+ require 'sinatra/base'
3
+
4
+ require_relative 'app'
5
+ require_relative 'metadata'
6
+
7
+
8
+ module OpsAsk
9
+ class Main < Thor
10
+ desc 'art', 'Show the application art'
11
+ def art
12
+ max_line_len = OpsAsk::ART.lines.sort_by { |l| l.length }.last.length
13
+ description = "OpsAsk #{OpsAsk::VERSION} / #{OpsAsk::SUMMARY} / #{OpsAsk::AUTHOR} (#{OpsAsk::EMAIL})"
14
+ puts
15
+ puts OpsAsk::ART
16
+ puts description.center(max_line_len)
17
+ puts
18
+ end
19
+
20
+ desc 'version', 'Show the application version'
21
+ def version
22
+ puts OpsAsk::VERSION
23
+ end
24
+
25
+ desc 'server', 'Start application web server'
26
+ option :port, default: 3000, aliases: :p
27
+ option :config, default: nil, aliases: :c
28
+ def server
29
+ config = {
30
+ ops_group: 'change-network-operations',
31
+ agile_board: '169', # Operations
32
+ assignee: 'denimcores',
33
+ jira_user: nil,
34
+ jira_pass: nil,
35
+ jira_url: 'http://jira.bluejeansnet.com',
36
+ queue_size: 10,
37
+ cutoff_hour: 18, # 6pm
38
+ project_key: 'INF',
39
+ project_name: 'Infrastructure',
40
+ issue_type: 'Task',
41
+ version: 'Un-targeted',
42
+ jira_private_key: 'opsask.pem',
43
+ jira_consumer_key: 'opsask-test',
44
+ app_version: OpsAsk::VERSION
45
+ }
46
+
47
+ if options[:config]
48
+ config.merge! JSON::parse(File.read(options[:config]), symbolize_names: true)
49
+ end
50
+
51
+ App.run! port: options[:port], config: config
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ module OpsAsk
2
+ # General information about the project
3
+ SUMMARY = %q.Ask Ops for stuff.
4
+ AUTHOR = 'Sean Clemmer'
5
+ EMAIL = 'sczizzo@gmail.com'
6
+ LICENSE = 'ISC'
7
+ HOMEPAGE = 'http://git.ops.bluejeans.com/inf/opsask'
8
+
9
+ # Project root
10
+ ROOT = File.join File.dirname(__FILE__), '..', '..'
11
+
12
+ # Pull the project version out of the VERSION file
13
+ VERSION = File.read(File.join(ROOT, 'VERSION')).strip
14
+
15
+ ART = <<-'EOART'
16
+ _ _ _ _ _ _
17
+ /\ \ /\ \ / /\ / /\ / /\ /\_\
18
+ / \ \ / \ \ / / \ / / \ / / \ / / / _
19
+ / /\ \ \ / /\ \ \ / / /\ \__ / / /\ \ / / /\ \__ / / / /\_\
20
+ / / /\ \ \ / / /\ \_\ / / /\ \___\ / / /\ \ \ / / /\ \___\ / / /__/ / /
21
+ / / / \ \_\ / / /_/ / / \ \ \ \/___// / / \ \ \ \ \ \ \/___// /\_____/ /
22
+ / / / / / // / /__\/ / \ \ \ / / /___/ /\ \ \ \ \ / /\_______/
23
+ / / / / / // / /_____/_ \ \ \ / / /_____/ /\ \ _ \ \ \ / / /\ \ \
24
+ / / /___/ / // / / /_/\__/ / / / /_________/\ \ \ /_/\__/ / / / / / \ \ \
25
+ / / /____\/ // / / \ \/___/ / / / /_ __\ \_\\ \/___/ / / / / \ \ \
26
+ \/_________/ \/_/ \_____\/ \_\___\ /____/_/ \_____\/ \/_/ \_\_\
27
+
28
+ EOART
29
+ end
data/lib/opsask.rb ADDED
@@ -0,0 +1,3 @@
1
+ require_relative 'opsask/app'
2
+ require_relative 'opsask/main'
3
+ require_relative 'opsask/metadata'
data/opsask.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path(File.join('..', 'lib'), __FILE__)
3
+ require 'opsask/metadata'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'opsask'
7
+ s.version = OpsAsk::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.author = OpsAsk::AUTHOR
10
+ s.email = OpsAsk::EMAIL
11
+ s.summary = OpsAsk::SUMMARY
12
+ s.description = OpsAsk::SUMMARY + '.'
13
+ s.homepage = OpsAsk::HOMEPAGE
14
+ s.license = OpsAsk::LICENSE
15
+
16
+ s.add_runtime_dependency 'thor', '~> 0'
17
+ s.add_runtime_dependency 'thin', '~> 1'
18
+ s.add_runtime_dependency 'curb', '~> 0.8'
19
+ s.add_runtime_dependency 'rack', '~> 1'
20
+ s.add_runtime_dependency 'sinatra', '~> 1.4'
21
+ s.add_runtime_dependency 'jira-ruby', '~> 0.1'
22
+ s.add_runtime_dependency 'rack-flash3', '~> 1'
23
+ s.add_runtime_dependency 'sinatra-partial', '~> 0.4'
24
+
25
+ s.files = `git ls-files`.split("\n")
26
+ s.test_files = `git ls-files -- test/*`.split("\n")
27
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File::basename(f) }
28
+ s.require_paths = %w[ lib ]
29
+ end