sp-job 0.1.17
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 +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
|