sp-job 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ Dir.glob('lib/tasks/*.rake').each { |r| load r }
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.17
@@ -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__)
@@ -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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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'
@@ -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