sp-job 0.1.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE +661 -0
- data/README.md +36 -0
- data/Rakefile +8 -0
- data/VERSION +1 -0
- data/bin/console +14 -0
- data/bin/queue-job +72 -0
- data/bin/setup +8 -0
- data/lib/sp-job.rb +45 -0
- data/lib/sp/job.rb +24 -0
- data/lib/sp/job/back_burner.rb +205 -0
- data/lib/sp/job/broker.rb +372 -0
- data/lib/sp/job/broker_http_client.rb +342 -0
- data/lib/sp/job/broker_oauth2_client.rb +378 -0
- data/lib/sp/job/common.rb +295 -0
- data/lib/sp/job/engine.rb +34 -0
- data/lib/sp/job/jsonapi_error.rb +94 -0
- data/lib/sp/job/pg_connection.rb +179 -0
- data/lib/sp/job/uploaded_image_converter.rb +91 -0
- data/lib/sp/job/version.rb +24 -0
- data/lib/sp/job/worker.rb +46 -0
- data/lib/tasks/configure.rake +452 -0
- data/sp-job.gemspec +50 -0
- metadata +323 -0
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Sp::Job
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sp/job`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'sp-job'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install sp-job
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sp-job.
|
36
|
+
|
data/Rakefile
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.17
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "sp/job"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/queue-job
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
|
4
|
+
#
|
5
|
+
# This file is part of sp-job.
|
6
|
+
#
|
7
|
+
# sp-job is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Affero General Public License as published by
|
9
|
+
# the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# sp-job is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Affero General Public License
|
18
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
# encoding: utf-8
|
21
|
+
#
|
22
|
+
require 'bundler/setup'
|
23
|
+
require 'sp/job'
|
24
|
+
require 'sp/job'
|
25
|
+
require 'redis'
|
26
|
+
require 'backburner'
|
27
|
+
require 'optparse'
|
28
|
+
require 'sp/job/common'
|
29
|
+
require 'syslog/logger'
|
30
|
+
extend SP::Job::Common
|
31
|
+
|
32
|
+
begin
|
33
|
+
$args = {}
|
34
|
+
$config = {}
|
35
|
+
$args[:redis] = '127.0.0.1:6379'
|
36
|
+
$args[:beanstalkd] = '127.0.0.1:11300'
|
37
|
+
$args[:validity] = 7200
|
38
|
+
$args[:ttr] = 3600
|
39
|
+
|
40
|
+
#
|
41
|
+
# Parse command line arguments
|
42
|
+
#
|
43
|
+
$option_parser = OptionParser.new do |opts|
|
44
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} ARGS"
|
45
|
+
opts.on('-r', '--redis=HOST:PORT' , "Hostname and port of the redis server (default: '#{$args[:redis]}')") { |v| $args[:redis] = v }
|
46
|
+
opts.on('-b', '--beanstalkd=HOST:PORT', "Hostname and port of the beanstalkd server (default: '#{$args[:beanstalkd]}')") { |v| $args[:beanstalkd] = v }
|
47
|
+
opts.on('-V', '--validity=SECS' , "job validty in seconds") { |v| $args[:validity] = v }
|
48
|
+
opts.on('-t', '--tube=TUBE' , "beanstalkd tube name") { |v| $args[:tube] = v }
|
49
|
+
opts.on('-i', '--sid=SERVICEID' , "service id on redis") { |v| $config[:service_id] = v }
|
50
|
+
opts.on('-v', '--ttr=SECS' , "job ttr time to run in seconds") { |v| $args[:ttr] = v }
|
51
|
+
opts.on('-l', '--log=LOGFILE' , "path to log file (default: '#{$args[:log_file]}')") { |v| $args[:log_file] = File.expand_path(v) }
|
52
|
+
opts.on('-d', '--debug' , "developer mode: log to stdout and print job") { $args[:debug] = true }
|
53
|
+
end
|
54
|
+
$option_parser.parse!
|
55
|
+
|
56
|
+
raise "Tube must be specified with --tube!!!" if $args[:tube].nil?
|
57
|
+
raise "Service id must be specified with --sid!!!" if $config[:service_id].nil?
|
58
|
+
|
59
|
+
$redis = Redis.new(:host => $args[:redis].split(':')[0], :port => $args[:redis].split(':')[1], :db => 0)
|
60
|
+
$beaneater = Beaneater.new $args[:beanstalkd]
|
61
|
+
job = { }
|
62
|
+
ARGV.each do |arg|
|
63
|
+
key, value = arg.to_s.split('=')
|
64
|
+
job[key.to_sym] = value
|
65
|
+
end
|
66
|
+
submit_job(job: job, tube: $args[:tube], ttr: $args[:ttr], validity: $args[:validity])
|
67
|
+
rescue => e
|
68
|
+
STDERR.puts e
|
69
|
+
STDERR.puts e.backtrace
|
70
|
+
sys_log = Syslog::Logger.new $PROGRAM_NAME
|
71
|
+
sys_log.error "#{e} #{e.backtrace}"
|
72
|
+
end
|
data/bin/setup
ADDED
data/lib/sp-job.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011-2016 Cloudware S.A. All rights reserved.
|
3
|
+
#
|
4
|
+
# This file is part of sp-job.
|
5
|
+
#
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
|
20
|
+
#require 'byebug'
|
21
|
+
require 'awesome_print'
|
22
|
+
require 'rollbar'
|
23
|
+
require 'redis'
|
24
|
+
require 'backburner'
|
25
|
+
require 'json'
|
26
|
+
require 'fileutils'
|
27
|
+
require 'concurrent'
|
28
|
+
require 'optparse'
|
29
|
+
require 'os'
|
30
|
+
require 'pg'
|
31
|
+
require 'sp-duh'
|
32
|
+
require 'oauth2'
|
33
|
+
require 'oauth2-client'
|
34
|
+
require 'curb'
|
35
|
+
require 'rails'
|
36
|
+
require 'erb'
|
37
|
+
require 'ostruct'
|
38
|
+
require 'json'
|
39
|
+
require 'mail'
|
40
|
+
|
41
|
+
require 'sp/job'
|
42
|
+
require 'sp/job/engine'
|
43
|
+
require 'sp/job/version'
|
44
|
+
require 'sp/job/worker'
|
45
|
+
require 'sp/job/broker_oauth2_client'
|
data/lib/sp/job.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
|
3
|
+
#
|
4
|
+
# This file is part of sp-job.
|
5
|
+
#
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
|
20
|
+
module SP
|
21
|
+
module Job
|
22
|
+
MODULE_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2011-2017 Cloudware S.A. All rights reserved.
|
3
|
+
#
|
4
|
+
# This file is part of sp-job.
|
5
|
+
#
|
6
|
+
# sp-job is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU Affero General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# sp-job is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU Affero General Public License
|
17
|
+
# along with sp-job. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
#
|
19
|
+
# encoding: utf-8
|
20
|
+
#
|
21
|
+
require 'sp/job/pg_connection'
|
22
|
+
require 'roadie'
|
23
|
+
|
24
|
+
#
|
25
|
+
# Initialize global data needed for configuration
|
26
|
+
#
|
27
|
+
$prefix = OS.mac? ? '/usr/local' : '/'
|
28
|
+
$rollbar = false
|
29
|
+
$bury = false
|
30
|
+
$min_progress = 3 # TODO to config??
|
31
|
+
$args = {
|
32
|
+
stdout: false,
|
33
|
+
log_level: 'info',
|
34
|
+
program_name: File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)),
|
35
|
+
config_file: File.join($prefix, 'etc', File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME)), 'conf.json'),
|
36
|
+
log_file: File.join($prefix, 'var', 'log', 'jobs', "#{File.basename($PROGRAM_NAME, File.extname($PROGRAM_NAME))}.log")
|
37
|
+
}
|
38
|
+
|
39
|
+
#
|
40
|
+
# Parse command line arguments
|
41
|
+
#
|
42
|
+
$option_parser = OptionParser.new do |opts|
|
43
|
+
opts.banner = "Usage: #{$PROGRAM_NAME} ARGS"
|
44
|
+
opts.on('-c', '--config=CONFIG.JSON', "path to json configuration file (default: '#{$args[:config_file]}')") { |v| $args[:config_file] = File.expand_path(v) }
|
45
|
+
opts.on('-l', '--log=LOGFILE' , "path to log file (default: '#{$args[:log_file]}')") { |v| $args[:log_file] = File.expand_path(v) }
|
46
|
+
opts.on('-d', '--debug' , "developer mode: log to stdout and print job") { $args[:debug] = true }
|
47
|
+
opts.on('-v', '--log_level=LEVEL' , "Log level DEBUG, INFO, WARN, ERROR, FATAL") { |v| $args[:log_level] = v }
|
48
|
+
end
|
49
|
+
$option_parser.parse!
|
50
|
+
|
51
|
+
#
|
52
|
+
# Read configuration
|
53
|
+
#
|
54
|
+
$config = JSON.parse(File.read(File.expand_path($args[:config_file])), symbolize_names: true)
|
55
|
+
|
56
|
+
#
|
57
|
+
# Configure rollbar
|
58
|
+
#
|
59
|
+
unless $config[:rollbar].nil?
|
60
|
+
$rollbar = true
|
61
|
+
Rollbar.configure do |config|
|
62
|
+
config.access_token = $config[:rollbar][:token] if $config[:rollbar][:token]
|
63
|
+
config.environment = $config[:rollbar][:environment] if $config[:rollbar] && $config[:rollbar][:environment]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Configure backburner queue
|
69
|
+
#
|
70
|
+
Backburner.configure do |config|
|
71
|
+
|
72
|
+
config.beanstalk_url = "beanstalk://#{$config[:beanstalkd][:host]}:#{$config[:beanstalkd][:port]}"
|
73
|
+
config.on_error = lambda { |e|
|
74
|
+
if $exception_reported == false
|
75
|
+
$exception_reported == true
|
76
|
+
update_progress(status: 'error', message: e)
|
77
|
+
end
|
78
|
+
if $rollbar
|
79
|
+
Rollbar.error(e)
|
80
|
+
end
|
81
|
+
catch_fatal_exceptions(e)
|
82
|
+
}
|
83
|
+
#config.priority_labels = { :custom => 50, :useless => 1000 }
|
84
|
+
#config.max_job_retries = 0 # default 0 retries
|
85
|
+
#config.retry_delay = 5 # default 5 seconds
|
86
|
+
#config.default_priority = 65536
|
87
|
+
config.retry_delay_proc = lambda { |min_retry_delay, num_retries| min_retry_delay + (num_retries ** 3) }
|
88
|
+
config.respond_timeout = 120
|
89
|
+
config.default_worker = SP::Job::Worker
|
90
|
+
config.logger = $args[:debug] ? Logger.new(STDOUT) : Logger.new($args[:log_file])
|
91
|
+
config.logger.formatter = proc do |severity, datetime, progname, msg|
|
92
|
+
date_format = datetime.strftime("%Y-%m-%d %H:%M:%S")
|
93
|
+
"[#{date_format}] #{severity}: #{msg}\n"
|
94
|
+
end
|
95
|
+
if $args[:log_level].nil?
|
96
|
+
config.logger.level = Logger::INFO
|
97
|
+
else
|
98
|
+
case $args[:log_level].upcase
|
99
|
+
when 'DEBUG'
|
100
|
+
config.logger.level = Logger::DEBUG
|
101
|
+
when 'INFO'
|
102
|
+
config.logger.level = Logger::INFO
|
103
|
+
when 'WARN'
|
104
|
+
config.logger.level = Logger::WARN
|
105
|
+
when 'ERROR'
|
106
|
+
config.logger.level = Logger::ERROR
|
107
|
+
when 'FATAL'
|
108
|
+
config.logger.level = Logger::FATAL
|
109
|
+
else
|
110
|
+
config.logger.level = Logger::INFO
|
111
|
+
end
|
112
|
+
end
|
113
|
+
config.logger.datetime_format = "%Y-%m-%d %H:%M:%S"
|
114
|
+
config.primary_queue = $args[:program_name]
|
115
|
+
config.reserve_timeout = nil
|
116
|
+
config.job_parser_proc = lambda { |body|
|
117
|
+
rv = Hash.new
|
118
|
+
rv[:args] = [JSON.parse(body, :symbolize_names => true)]
|
119
|
+
rv[:class] = rv[:args][0][:tube] || $args[:program_name]
|
120
|
+
rv
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
if $config[:mail]
|
125
|
+
Mail.defaults do
|
126
|
+
delivery_method :smtp, {
|
127
|
+
:address => $config[:mail][:smtp][:address],
|
128
|
+
:port => $config[:mail][:smtp][:port].to_i,
|
129
|
+
:domain => $config[:mail][:smtp][:domain],
|
130
|
+
:user_name => $config[:mail][:smtp][:user_name],
|
131
|
+
:password => $config[:mail][:smtp][:password],
|
132
|
+
:authentication => $config[:mail][:smtp][:authentication],
|
133
|
+
:enable_starttls_auto => $config[:mail][:smtp][:enable_starttls_auto]
|
134
|
+
}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Monkey patches to keep the tube name as plain vannila job name
|
140
|
+
#
|
141
|
+
module Backburner
|
142
|
+
module Helpers
|
143
|
+
|
144
|
+
def expand_tube_name (tube)
|
145
|
+
tube
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
module Logger
|
151
|
+
def log_job_begin(name, args)
|
152
|
+
log_info "Work job #{name}"
|
153
|
+
@job_started_at = Time.now
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Job
|
158
|
+
# Processes a job and handles any failure, deleting the job once complete
|
159
|
+
#
|
160
|
+
# @example
|
161
|
+
# @task.process
|
162
|
+
#
|
163
|
+
def process
|
164
|
+
# Invoke before hook and stop if false
|
165
|
+
res = @hooks.invoke_hook_events(job_class, :before_perform, *args)
|
166
|
+
unless res
|
167
|
+
task.delete
|
168
|
+
return false
|
169
|
+
end
|
170
|
+
# Execute the job
|
171
|
+
@hooks.around_hook_events(job_class, :around_perform, *args) do
|
172
|
+
# We subtract one to ensure we timeout before beanstalkd does, except if:
|
173
|
+
# a) ttr == 0, to support never timing out
|
174
|
+
# b) ttr == 1, so that we don't accidentally set it to never time out
|
175
|
+
# NB: A ttr of 1 will likely result in race conditions between
|
176
|
+
# Backburner and beanstalkd and should probably be avoided
|
177
|
+
timeout_job_after(task.ttr > 1 ? task.ttr - 1 : task.ttr) { job_class.perform(*args) }
|
178
|
+
end
|
179
|
+
task.delete
|
180
|
+
# Invoke after perform hook
|
181
|
+
@hooks.invoke_hook_events(job_class, :after_perform, *args)
|
182
|
+
rescue => e
|
183
|
+
@hooks.invoke_hook_events(job_class, :on_failure, e, *args)
|
184
|
+
raise e
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Mix-in the mix-in in the script so that we can use the Common module functions
|
190
|
+
require 'sp/job/common'
|
191
|
+
extend SP::Job::Common
|
192
|
+
|
193
|
+
#
|
194
|
+
# Now create the global data needed by the mix-in methods
|
195
|
+
#
|
196
|
+
$connected = false
|
197
|
+
$job_status = {}
|
198
|
+
$validity = 2
|
199
|
+
$redis = Redis.new(:host => $config[:redis][:host], :port => $config[:redis][:port], :db => 0)
|
200
|
+
$beaneater = Beaneater.new "#{$config[:beanstalkd][:host]}:#{$config[:beanstalkd][:port]}"
|
201
|
+
$check_db_life_span = false
|
202
|
+
$status_dirty = false
|
203
|
+
if $config[:postgres] && $config[:postgres][:conn_str]
|
204
|
+
$pg = ::SP::Job::PGConnection.new(owner: 'back_burner', config: $config[:postgres])
|
205
|
+
end
|
@@ -0,0 +1,372 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# encoding: utf-8
|
4
|
+
#
|
5
|
+
# Copyright (c) 2017 Cloudware S.A. Allrights reserved
|
6
|
+
#
|
7
|
+
# Helper to obtain tokens to access toconline API's.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'sp/job/jsonapi_error'
|
11
|
+
|
12
|
+
module SP
|
13
|
+
module Job
|
14
|
+
|
15
|
+
class Broker
|
16
|
+
|
17
|
+
#
|
18
|
+
# Helper class that defined an 'i18n' message.
|
19
|
+
#
|
20
|
+
class I18N
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
@key
|
25
|
+
@args
|
26
|
+
|
27
|
+
public
|
28
|
+
|
29
|
+
attr_accessor :key
|
30
|
+
attr_accessor :args
|
31
|
+
|
32
|
+
def initialize (key:, args:)
|
33
|
+
@key = key
|
34
|
+
@args = args
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
#
|
41
|
+
# Helper class that defined an 'Broker' error.
|
42
|
+
#
|
43
|
+
class Error < ::SP::Job::JSONAPIError
|
44
|
+
|
45
|
+
def initialize (i18n:, code:, internal:)
|
46
|
+
super(code: code, internal: internal)
|
47
|
+
@i18n = i18n
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Helper class that defined an 'Not Implemented' error.
|
54
|
+
#
|
55
|
+
class NotImplementedError < Error
|
56
|
+
|
57
|
+
def initialize (i18n:, internal:)
|
58
|
+
super(i18n: i18n, code: 501, internal: internal)
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Helper class that defined an 'Bard Request' error.
|
65
|
+
#
|
66
|
+
class BadRequest < Error
|
67
|
+
|
68
|
+
def initialize (i18n:, internal:)
|
69
|
+
super(i18n: i18n, code: 400, internal: internal)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Helper class that defined an 'Internal Error' error.
|
76
|
+
#
|
77
|
+
class InternalError < Error
|
78
|
+
|
79
|
+
def initialize (i18n:, internal:)
|
80
|
+
super(i18n: i18n, code: 500, internal: internal)
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Helper class that defined an 'Unauthorized' error.
|
87
|
+
#
|
88
|
+
class Unauthorized < Error
|
89
|
+
|
90
|
+
def initialize (i18n:, internal:)
|
91
|
+
super(i18n: i18n, code: 401, internal: internal)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
#
|
98
|
+
#
|
99
|
+
class OAuth2
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
@service_id = nil
|
104
|
+
@client = nil
|
105
|
+
@redis = nil
|
106
|
+
|
107
|
+
public
|
108
|
+
|
109
|
+
def initialize (service_id:, config:, redis: nil)
|
110
|
+
@service_id = service_id
|
111
|
+
@client = ::SP::Job::BrokerOAuth2Client.new(
|
112
|
+
protocol: config[:protocol],
|
113
|
+
host: config[:host],
|
114
|
+
port: config[:port],
|
115
|
+
client_id: config[:client_id],
|
116
|
+
client_secret: config[:client_secret],
|
117
|
+
redirect_uri: config[:redirect_uri],
|
118
|
+
scope: config[:scope],
|
119
|
+
options: {}
|
120
|
+
)
|
121
|
+
@redis = redis
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Obtain an 'access' and a 'refresh' token.
|
126
|
+
#
|
127
|
+
# @param scope
|
128
|
+
#
|
129
|
+
def authorize (scope: nil, fields: nil)
|
130
|
+
# obtain an 'authorization code'
|
131
|
+
ac_response = @client.get_authorization_code(
|
132
|
+
a_redirect_uri = nil,
|
133
|
+
a_scope = scope
|
134
|
+
)
|
135
|
+
# got a valid 'authorization code'?
|
136
|
+
if ac_response[:oauth2].has_key?(:code)
|
137
|
+
# got fields?
|
138
|
+
if nil != fields
|
139
|
+
# prepare redis arguments: field value, [field value, ...]
|
140
|
+
array = []
|
141
|
+
fields.each do |k,v|
|
142
|
+
array << k.to_s
|
143
|
+
array << v
|
144
|
+
end
|
145
|
+
$redis.hmset("#{@service_id}:oauth:authorization_code:#{ac_response[:oauth2][:code]}",
|
146
|
+
array,
|
147
|
+
'patched_by', 'toconline-session'
|
148
|
+
)
|
149
|
+
end
|
150
|
+
# exchange it for a 'access' and a 'refresh' token
|
151
|
+
at_response = @client.exchange_auth_code_for_token(
|
152
|
+
ac_response[:oauth2][:code]
|
153
|
+
)
|
154
|
+
# return 'oauth2' at object
|
155
|
+
return at_response
|
156
|
+
else
|
157
|
+
# return 'oauth2' ac object
|
158
|
+
return ac_response
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Refresh an access token.
|
164
|
+
#
|
165
|
+
# @param scope
|
166
|
+
# @param old
|
167
|
+
#
|
168
|
+
def refresh (scope: nil, old: nil)
|
169
|
+
at_response = @client.refresh_access_token(
|
170
|
+
a_refresh_token = old[:refresh_token],
|
171
|
+
a_scope = scope
|
172
|
+
)
|
173
|
+
if true == at_response[:oauth2].has_key?(:error)
|
174
|
+
return at_response
|
175
|
+
end
|
176
|
+
# no error, delete old tokens
|
177
|
+
if nil == old
|
178
|
+
# return oauth response
|
179
|
+
return at_response
|
180
|
+
end
|
181
|
+
# old tokens provided: remove them from redis
|
182
|
+
if nil == @redis || nil == @service_id
|
183
|
+
raise InternalError.new(i18n: nil, internal: nil)
|
184
|
+
end
|
185
|
+
# delete old tokens from redis
|
186
|
+
@redis.multi do |multi|
|
187
|
+
if nil != old[:access_token]
|
188
|
+
multi.del("#{@service_id}:oauth:access_token:#{old[:access_token]}")
|
189
|
+
end
|
190
|
+
if nil != old[:refresh_token]
|
191
|
+
multi.del("#{@service_id}:oauth:refresh_token:#{old[:refresh_token]}")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
# return oauth response
|
195
|
+
return at_response
|
196
|
+
end
|
197
|
+
|
198
|
+
#
|
199
|
+
# Patch a pair of tokens, by generating new ones
|
200
|
+
#
|
201
|
+
# @param access_token
|
202
|
+
# @param refresh_token
|
203
|
+
# @param fields
|
204
|
+
#
|
205
|
+
def patch (access_token:, refresh_token:, fields:)
|
206
|
+
if nil == @redis || nil == @service_id
|
207
|
+
raise InternalError.new(i18n: nil, internal: nil)
|
208
|
+
end
|
209
|
+
# generate new pair, based on provided refresh_token
|
210
|
+
at_response = @client.refresh_access_token(
|
211
|
+
a_refresh_token = refresh_token,
|
212
|
+
a_scope = nil # keep current scope
|
213
|
+
)
|
214
|
+
if at_response[:oauth2].has_key?(:error)
|
215
|
+
return at_response
|
216
|
+
end
|
217
|
+
# prepare redis arguments: field value, [field value, ...]
|
218
|
+
array = []
|
219
|
+
fields.each do |k,v|
|
220
|
+
array << k.to_s
|
221
|
+
array << v
|
222
|
+
end
|
223
|
+
# patch new tokens
|
224
|
+
@redis.multi do |multi|
|
225
|
+
multi.hmset("#{@service_id}:oauth:refresh_token:#{at_response[:oauth2][:refresh_token]}",
|
226
|
+
array,
|
227
|
+
'patched_by', 'toconline-session'
|
228
|
+
)
|
229
|
+
multi.hmset("#{@service_id}:oauth:access_token:#{at_response[:oauth2][:access_token]}",
|
230
|
+
array,
|
231
|
+
'patched_by', 'toconline-session'
|
232
|
+
)
|
233
|
+
end
|
234
|
+
# delete old tokens from redis
|
235
|
+
@redis.multi do |multi|
|
236
|
+
multi.del("#{@service_id}:oauth:access_token:#{access_token}")
|
237
|
+
multi.del("#{@service_id}:oauth:refresh_token:#{refresh_token}")
|
238
|
+
end
|
239
|
+
# return oauth response
|
240
|
+
return at_response
|
241
|
+
end
|
242
|
+
|
243
|
+
#
|
244
|
+
# Remove a pair of tokens from redis.
|
245
|
+
#
|
246
|
+
# @param access
|
247
|
+
# @param refresh
|
248
|
+
#
|
249
|
+
def dispose (access:, refresh:)
|
250
|
+
if nil == @redis || nil == @service_id
|
251
|
+
raise InternalError.new(i18n: nil, internal: nil)
|
252
|
+
end
|
253
|
+
|
254
|
+
if refresh.nil?
|
255
|
+
refresh = @redis.hget("#{@service_id}:oauth:access_token:#{access}",'refresh_token')
|
256
|
+
end
|
257
|
+
|
258
|
+
# delete tokens from redis
|
259
|
+
@redis.multi do |multi|
|
260
|
+
multi.del("#{@service_id}:oauth:access_token:#{access}")
|
261
|
+
multi.del("#{@service_id}:oauth:refresh_token:#{refresh}")
|
262
|
+
end
|
263
|
+
#
|
264
|
+
nil
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
268
|
+
|
269
|
+
#
|
270
|
+
#
|
271
|
+
#
|
272
|
+
class Job
|
273
|
+
|
274
|
+
#
|
275
|
+
#
|
276
|
+
#
|
277
|
+
attr_accessor :oauth2
|
278
|
+
attr_accessor :output
|
279
|
+
|
280
|
+
#
|
281
|
+
#
|
282
|
+
#
|
283
|
+
def initialize (config:)
|
284
|
+
if nil != config && nil != config[:oauth2]
|
285
|
+
@oauth2 = OAuth2.new(service_id: config[:service_id], config: config[:oauth2], redis: config[:redis])
|
286
|
+
else
|
287
|
+
@oauth2 = nil
|
288
|
+
end
|
289
|
+
@output = {
|
290
|
+
:action => "response",
|
291
|
+
:content_type => "application/json",
|
292
|
+
:status_code => 400,
|
293
|
+
:response => nil
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
#
|
298
|
+
# Finalize the job response.
|
299
|
+
#
|
300
|
+
# @param response
|
301
|
+
# @param content_type
|
302
|
+
# @param status_code
|
303
|
+
#
|
304
|
+
# @return
|
305
|
+
#
|
306
|
+
def finalized (response:, content_type: 'application/json', status_code: 200)
|
307
|
+
@output[:response] = response
|
308
|
+
@output[:content_type] = content_type
|
309
|
+
@output[:status_code] = status_code
|
310
|
+
@output
|
311
|
+
end
|
312
|
+
|
313
|
+
#
|
314
|
+
# Perform an OAuth2 request, catch errors
|
315
|
+
# and convertem them to a common result hash.
|
316
|
+
#
|
317
|
+
# @param callback
|
318
|
+
#
|
319
|
+
def call(*callback)
|
320
|
+
begin
|
321
|
+
@output = yield
|
322
|
+
rescue ::SP::Job::BrokerOAuth2Client::InvalidEmailOrPassword => invalid_password
|
323
|
+
@output[:status_code] = 403
|
324
|
+
@output[:content_type], @output[:response] = Error.new(i18n: nil, code: @output[:status_code],
|
325
|
+
internal: invalid_password.as_hash[:oauth2]
|
326
|
+
).content_type_and_body()
|
327
|
+
rescue ::SP::Job::BrokerOAuth2Client::AccessDenied => acccess_denied
|
328
|
+
@output[:status_code] = 403
|
329
|
+
@output[:content_type], @output[:response] = Error.new(i18n: nil, code: @output[:status_code],
|
330
|
+
internal: acccess_denied.as_hash[:oauth2]
|
331
|
+
).content_type_and_body()
|
332
|
+
rescue ::SP::Job::BrokerOAuth2Client::UnauthorizedUser => unauthorized_user
|
333
|
+
@output[:status_code] = 401
|
334
|
+
@output[:content_type], @output[:response] = Error.new(i18n: nil, code: @output[:status_code],
|
335
|
+
internal: unauthorized_user.as_hash[:oauth2]
|
336
|
+
).content_type_and_body()
|
337
|
+
rescue ::SP::Job::BrokerOAuth2Client::InternalError => internal_error
|
338
|
+
@output[:status_code] = 500
|
339
|
+
@output[:content_type], @output[:response] = Error.new(i18n: nil, code: @output[:status_code],
|
340
|
+
internal: internal_error.as_hash[:oauth2]
|
341
|
+
).content_type_and_body()
|
342
|
+
rescue ::SP::Job::BrokerOAuth2Client::Error => error
|
343
|
+
@output[:status_code] = 500
|
344
|
+
@output[:content_type], @output[:response] = Error.new(i18n: nil, code: @output[:status_code],
|
345
|
+
internal: error.as_hash[:oauth2]
|
346
|
+
).content_type_and_body()
|
347
|
+
rescue NotImplementedError => broker_not_implemented
|
348
|
+
@output[:status_code] = broker_not_implemented.code
|
349
|
+
@output[:content_type], @output[:response] = b_not_implemented.content_type_and_body()
|
350
|
+
rescue BadRequest => broker_bad_request
|
351
|
+
@output[:status_code] = broker_bad_request.code
|
352
|
+
@output[:content_type], @output[:response] = broker_bad_request.content_type_and_body()
|
353
|
+
rescue InternalError => broker_internal_error
|
354
|
+
@output[:status_code] = broker_internal_error.code
|
355
|
+
@output[:content_type], @output[:response] = broker_internal_error.content_type_and_body()
|
356
|
+
rescue Error => broker_error
|
357
|
+
@output[:status_code] = broker_error.code
|
358
|
+
@output[:content_type], @output[:response] = broker_error.content_type_and_body()
|
359
|
+
rescue Exception => e
|
360
|
+
internal_error = InternalError.new(i18n: nil, internal: nil)
|
361
|
+
@output[:status_code] = internal_error.code
|
362
|
+
@output[:content_type], @output[:response] = internal_error.content_type_and_body()
|
363
|
+
end
|
364
|
+
@output
|
365
|
+
end
|
366
|
+
|
367
|
+
end
|
368
|
+
|
369
|
+
end # end class 'Broker'
|
370
|
+
|
371
|
+
end # module Job
|
372
|
+
end# module SP
|