smart_proxy_dynflow 0.4.0 → 0.6.0
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 +4 -4
- data/Gemfile +4 -7
- data/{bundler.plugins.d → bundler.d}/dynflow.rb +0 -0
- data/lib/smart_proxy_dynflow/action/batch.rb +21 -0
- data/lib/smart_proxy_dynflow/action/batch_callback.rb +20 -0
- data/lib/smart_proxy_dynflow/action/batch_runner.rb +14 -0
- data/lib/smart_proxy_dynflow/action/output_collector.rb +8 -0
- data/lib/smart_proxy_dynflow/action/runner.rb +81 -0
- data/lib/smart_proxy_dynflow/action/shareable.rb +25 -0
- data/lib/smart_proxy_dynflow/action/single_runner_batch.rb +39 -0
- data/lib/smart_proxy_dynflow/action.rb +12 -0
- data/lib/smart_proxy_dynflow/api.rb +1 -1
- data/lib/smart_proxy_dynflow/callback.rb +8 -44
- data/lib/smart_proxy_dynflow/continuous_output.rb +50 -0
- data/lib/smart_proxy_dynflow/core.rb +2 -2
- data/lib/smart_proxy_dynflow/helpers.rb +11 -6
- data/lib/smart_proxy_dynflow/log.rb +1 -1
- data/lib/smart_proxy_dynflow/otp_manager.rb +36 -0
- data/lib/smart_proxy_dynflow/plugin.rb +9 -1
- data/lib/smart_proxy_dynflow/proxy_adapter.rb +1 -1
- data/lib/smart_proxy_dynflow/runner/base.rb +98 -0
- data/lib/smart_proxy_dynflow/runner/command.rb +40 -0
- data/lib/smart_proxy_dynflow/runner/command_runner.rb +11 -0
- data/lib/smart_proxy_dynflow/runner/dispatcher.rb +191 -0
- data/lib/smart_proxy_dynflow/runner/parent.rb +57 -0
- data/lib/smart_proxy_dynflow/runner/update.rb +28 -0
- data/lib/smart_proxy_dynflow/runner.rb +10 -0
- data/lib/smart_proxy_dynflow/settings.rb +1 -1
- data/lib/smart_proxy_dynflow/settings_loader.rb +53 -0
- data/lib/smart_proxy_dynflow/task_launcher/abstract.rb +44 -0
- data/lib/smart_proxy_dynflow/task_launcher/batch.rb +37 -0
- data/lib/smart_proxy_dynflow/task_launcher/group.rb +48 -0
- data/lib/smart_proxy_dynflow/task_launcher/single.rb +17 -0
- data/lib/smart_proxy_dynflow/task_launcher.rb +9 -0
- data/lib/smart_proxy_dynflow/task_launcher_registry.rb +1 -1
- data/lib/smart_proxy_dynflow/testing.rb +1 -1
- data/lib/smart_proxy_dynflow/ticker.rb +47 -0
- data/lib/smart_proxy_dynflow/version.rb +2 -2
- data/lib/smart_proxy_dynflow.rb +2 -7
- metadata +57 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b904806b1f4b0084f948d91f800d255ecf7bca92b1ed6d78fee49761a29affc9
|
4
|
+
data.tar.gz: 1eb7b63daaac6fbc3bdcb24d2938a93d2bd5728bcdb934f8384f41709a072cbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 887d7788a14a93d693c3ec2a7ddc110fc5fd6fe62619da946f6e0d68f2ec8542b69670769d0cf9e7eafccee3af04b9bf89fef3565346d69bfa1eb4f71492b119
|
7
|
+
data.tar.gz: e1c2f822a02805f2136ba363d13028699dea85e05ed41ba988853c5aadf3358d5f59f65006e5404321c087aea7a96699582ba8dd1fbc8f1a67a5e09943509d2d
|
data/Gemfile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
gemspec :name => '
|
3
|
+
gemspec :name => 'smart_proxy_dynflow'
|
4
4
|
|
5
5
|
group :development do
|
6
6
|
gem 'pry'
|
@@ -8,10 +8,12 @@ end
|
|
8
8
|
|
9
9
|
group :test do
|
10
10
|
gem 'smart_proxy', :git => "https://github.com/theforeman/smart-proxy", :branch => "develop"
|
11
|
-
gem 'smart_proxy_dynflow', :path => '.'
|
12
11
|
|
12
|
+
gem 'minitest'
|
13
|
+
gem 'mocha'
|
13
14
|
gem 'public_suffix'
|
14
15
|
gem 'rack-test'
|
16
|
+
gem 'rake'
|
15
17
|
gem 'rubocop', '~> 0.52.1'
|
16
18
|
end
|
17
19
|
|
@@ -19,11 +21,6 @@ gem 'logging-journald', '~> 2.0', :platforms => [:ruby], :require => false
|
|
19
21
|
gem 'rack', '>= 1.1'
|
20
22
|
gem 'sinatra'
|
21
23
|
|
22
|
-
# load bundler.d
|
23
|
-
Dir["#{File.dirname(__FILE__)}/bundler.d/*.rb"].each do |bundle|
|
24
|
-
self.instance_eval(Bundler.read_file(bundle))
|
25
|
-
end
|
26
|
-
|
27
24
|
# load local gemfile
|
28
25
|
local_gemfile = File.join(File.dirname(__FILE__), 'Gemfile.local.rb')
|
29
26
|
self.instance_eval(Bundler.read_file(local_gemfile)) if File.exist?(local_gemfile)
|
File without changes
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Proxy::Dynflow::Action
|
2
|
+
class Batch < ::Dynflow::Action
|
3
|
+
include Dynflow::Action::WithSubPlans
|
4
|
+
include Dynflow::Action::WithPollingSubPlans
|
5
|
+
|
6
|
+
# { task_id => { :action_class => Klass, :input => input } }
|
7
|
+
def plan(launcher, input_hash)
|
8
|
+
launcher.launch_children(self, input_hash)
|
9
|
+
plan_self
|
10
|
+
end
|
11
|
+
|
12
|
+
def initiate
|
13
|
+
ping suspended_action
|
14
|
+
wait_for_sub_plans sub_plans
|
15
|
+
end
|
16
|
+
|
17
|
+
def rescue_strategy
|
18
|
+
Dynflow::Action::Rescue::Fail
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Proxy::Dynflow::Action
|
2
|
+
class BatchCallback < ::Dynflow::Action
|
3
|
+
def plan(input_hash, results)
|
4
|
+
plan_self :targets => input_hash, :results => results
|
5
|
+
end
|
6
|
+
|
7
|
+
def run
|
8
|
+
payload = format_payload(input['targets'], input['results'])
|
9
|
+
Proxy::Dynflow::Callback::Request.new.callback({ :callbacks => payload }.to_json)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def format_payload(input_hash, results)
|
15
|
+
input_hash.map do |task_id, callback|
|
16
|
+
{ :callback => callback, :data => results[task_id] }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'smart_proxy_dynflow/action/runner'
|
2
|
+
|
3
|
+
module Proxy::Dynflow::Action
|
4
|
+
class BatchRunner < ::Proxy::Dynflow::Action::Runner
|
5
|
+
def plan(launcher, input)
|
6
|
+
plan_self :targets => launcher.runner_input(input), :operation => launcher.operation
|
7
|
+
end
|
8
|
+
|
9
|
+
def initiate_runner
|
10
|
+
launcher = Proxy::Dynflow::TaskLauncherRegistry.fetch(input[:operation])
|
11
|
+
launcher.runner_class.new(input[:targets], suspended_action: suspended_action)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'smart_proxy_dynflow/action/shareable'
|
2
|
+
module Proxy::Dynflow
|
3
|
+
module Action
|
4
|
+
class Runner < Shareable
|
5
|
+
include ::Dynflow::Action::Cancellable
|
6
|
+
|
7
|
+
def run(event = nil)
|
8
|
+
case event
|
9
|
+
when nil
|
10
|
+
init_run
|
11
|
+
when Proxy::Dynflow::Runner::Update
|
12
|
+
process_update(event)
|
13
|
+
when Proxy::Dynflow::Runner::ExternalEvent
|
14
|
+
process_external_event(event)
|
15
|
+
when ::Dynflow::Action::Cancellable::Cancel
|
16
|
+
kill_run
|
17
|
+
else
|
18
|
+
raise "Unexpected event #{event.inspect}"
|
19
|
+
end
|
20
|
+
rescue => e
|
21
|
+
action_logger.error(e)
|
22
|
+
process_update(Proxy::Dynflow::Runner::Update.encode_exception('Proxy error', e))
|
23
|
+
end
|
24
|
+
|
25
|
+
def finalize
|
26
|
+
# To mark the task as a whole as failed
|
27
|
+
error! 'Script execution failed' if on_proxy? && failed_run?
|
28
|
+
end
|
29
|
+
|
30
|
+
def rescue_strategy_for_self
|
31
|
+
::Dynflow::Action::Rescue::Fail
|
32
|
+
end
|
33
|
+
|
34
|
+
def initiate_runner
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
|
38
|
+
def init_run
|
39
|
+
output[:result] = []
|
40
|
+
output[:runner_id] = runner_dispatcher.start(suspended_action, initiate_runner)
|
41
|
+
suspend
|
42
|
+
end
|
43
|
+
|
44
|
+
def runner_dispatcher
|
45
|
+
Proxy::Dynflow::Runner::Dispatcher.instance
|
46
|
+
end
|
47
|
+
|
48
|
+
def kill_run
|
49
|
+
runner_dispatcher.kill(output[:runner_id])
|
50
|
+
suspend
|
51
|
+
end
|
52
|
+
|
53
|
+
def finish_run(update)
|
54
|
+
output[:exit_status] = update.exit_status
|
55
|
+
output[:result] = output_result
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_external_event(event)
|
59
|
+
runner_dispatcher.external_event(output[:runner_id], event)
|
60
|
+
suspend
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_update(update)
|
64
|
+
output_chunk(update.continuous_output.raw_outputs) unless update.continuous_output.raw_outputs.empty?
|
65
|
+
if update.exit_status
|
66
|
+
finish_run(update)
|
67
|
+
else
|
68
|
+
suspend
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def failed_run?
|
73
|
+
output[:exit_status] != 0
|
74
|
+
end
|
75
|
+
|
76
|
+
def output_result
|
77
|
+
stored_output_chunks.map { |c| c[:chunk] }.reduce(&:concat)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Proxy::Dynflow::Action
|
2
|
+
class Shareable < ::Dynflow::Action
|
3
|
+
def plan(input)
|
4
|
+
input = input.dup
|
5
|
+
callback = input.delete('callback')
|
6
|
+
if callback
|
7
|
+
input[:task_id] = callback['task_id']
|
8
|
+
else
|
9
|
+
input[:task_id] ||= SecureRandom.uuid
|
10
|
+
end
|
11
|
+
|
12
|
+
planned_action = plan_self(input)
|
13
|
+
# code only applicable, when run with SmartProxyDynflowCore in place
|
14
|
+
if on_proxy? && callback
|
15
|
+
plan_action(Proxy::Dynflow::Callback::Action, callback, planned_action.output)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def on_proxy?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Proxy::Dynflow::Action
|
2
|
+
class SingleRunnerBatch < Batch
|
3
|
+
def plan(launcher, input_hash)
|
4
|
+
launcher.launch_children(self, input_hash)
|
5
|
+
sequence do
|
6
|
+
results = plan_self
|
7
|
+
plan_action BatchCallback, launcher.prepare_batch(input_hash), results.output[:results]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(event = nil)
|
12
|
+
super unless event == Dynflow::Action::Skip
|
13
|
+
end
|
14
|
+
|
15
|
+
def initiate
|
16
|
+
ping suspended_action
|
17
|
+
wait_for_sub_plans sub_plans
|
18
|
+
end
|
19
|
+
|
20
|
+
def check_for_errors!(optional = true)
|
21
|
+
super unless optional
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_finish
|
25
|
+
output[:results] = sub_plans.map(&:entry_action).reduce({}) do |acc, cur|
|
26
|
+
acc.merge(cur.execution_plan_id => cur.output)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def finalize
|
31
|
+
output.delete(:results)
|
32
|
+
check_for_errors!
|
33
|
+
end
|
34
|
+
|
35
|
+
def rescue_strategy_for_self
|
36
|
+
Dynflow::Action::Rescue::Skip
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Proxy::Dynflow
|
2
|
+
module Action
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'smart_proxy_dynflow/action/batch'
|
7
|
+
require 'smart_proxy_dynflow/action/batch_callback'
|
8
|
+
require 'smart_proxy_dynflow/action/batch_runner'
|
9
|
+
require 'smart_proxy_dynflow/action/output_collector'
|
10
|
+
require 'smart_proxy_dynflow/action/shareable'
|
11
|
+
require 'smart_proxy_dynflow/action/runner'
|
12
|
+
require 'smart_proxy_dynflow/action/single_runner_batch'
|
@@ -1,58 +1,22 @@
|
|
1
1
|
require 'rest-client'
|
2
2
|
|
3
|
-
|
3
|
+
module Proxy::Dynflow
|
4
4
|
module Callback
|
5
|
-
class Request
|
6
|
-
|
7
|
-
|
8
|
-
self.new.callback(prepare_payload(callback_info, data))
|
9
|
-
end
|
10
|
-
|
11
|
-
def ssl_options
|
12
|
-
return @ssl_options if defined? @ssl_options
|
13
|
-
@ssl_options = {}
|
14
|
-
settings = Proxy::SETTINGS
|
15
|
-
return @ssl_options unless URI.parse(settings.foreman_url).scheme == 'https'
|
16
|
-
|
17
|
-
@ssl_options[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
|
18
|
-
|
19
|
-
private_key_file = settings.foreman_ssl_key || settings.ssl_private_key
|
20
|
-
if private_key_file
|
21
|
-
private_key = File.read(private_key_file)
|
22
|
-
@ssl_options[:ssl_client_key] = OpenSSL::PKey::RSA.new(private_key)
|
23
|
-
end
|
24
|
-
certificate_file = settings.foreman_ssl_cert || settings.ssl_certificate
|
25
|
-
if certificate_file
|
26
|
-
certificate = File.read(certificate_file)
|
27
|
-
@ssl_options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(certificate)
|
28
|
-
end
|
29
|
-
ca_file = settings.foreman_ssl_ca || settings.ssl_ca_file
|
30
|
-
@ssl_options[:ssl_ca_file] = ca_file if ca_file
|
31
|
-
@ssl_options
|
32
|
-
end
|
33
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def prepare_payload(callback, data)
|
38
|
-
{ :callback => callback, :data => data }.to_json
|
39
|
-
end
|
5
|
+
class Request < ::Proxy::HttpRequest::ForemanRequest
|
6
|
+
def self.send_to_foreman_tasks(callback_info, data)
|
7
|
+
self.new.callback({ :callback => callback_info, :data => data }.to_json)
|
40
8
|
end
|
41
9
|
|
42
10
|
def callback(payload)
|
43
|
-
|
11
|
+
request = request_factory.create_post '/foreman_tasks/api/tasks/callback',
|
12
|
+
payload
|
13
|
+
response = send_request(request)
|
14
|
+
|
44
15
|
if response.code.to_s != "200"
|
45
16
|
raise "Failed performing callback to Foreman server: #{response.code} #{response.body}"
|
46
17
|
end
|
47
18
|
response
|
48
19
|
end
|
49
|
-
|
50
|
-
private
|
51
|
-
|
52
|
-
def callback_resource
|
53
|
-
@resource ||= RestClient::Resource.new(Proxy::SETTINGS.foreman_url + '/foreman_tasks/api/tasks/callback',
|
54
|
-
self.class.ssl_options)
|
55
|
-
end
|
56
20
|
end
|
57
21
|
|
58
22
|
class Action < ::Dynflow::Action
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Proxy::Dynflow
|
2
|
+
class ContinuousOutput
|
3
|
+
attr_accessor :raw_outputs
|
4
|
+
|
5
|
+
def initialize(raw_outputs = [])
|
6
|
+
@raw_outputs = []
|
7
|
+
raw_outputs.each { |raw_output| add_raw_output(raw_output) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_raw_output(raw_output)
|
11
|
+
missing_args = %w[output_type output timestamp] - raw_output.keys
|
12
|
+
unless missing_args.empty?
|
13
|
+
raise ArgumentError, "Missing args for raw output: #{missing_args.inspect}"
|
14
|
+
end
|
15
|
+
@raw_outputs << raw_output
|
16
|
+
end
|
17
|
+
|
18
|
+
def empty?
|
19
|
+
@raw_outputs.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def last_timestamp
|
23
|
+
return if @raw_outputs.empty?
|
24
|
+
@raw_outputs.last.fetch('timestamp')
|
25
|
+
end
|
26
|
+
|
27
|
+
def sort!
|
28
|
+
@raw_outputs.sort_by! { |record| record['timestamp'].to_f }
|
29
|
+
end
|
30
|
+
|
31
|
+
def humanize
|
32
|
+
sort!
|
33
|
+
raw_outputs.map { |output| output['output'] }.join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_exception(context, exception, timestamp = Time.now.getlocal)
|
37
|
+
add_output(context + ": #{exception.class} - #{exception.message}", 'debug', timestamp)
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_output(*args)
|
41
|
+
add_raw_output(self.class.format_output(*args))
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.format_output(message, type = 'debug', timestamp = Time.now.getlocal)
|
45
|
+
{ 'output_type' => type,
|
46
|
+
'output' => message,
|
47
|
+
'timestamp' => timestamp.to_f }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
module Proxy::Dynflow
|
2
2
|
class Core
|
3
3
|
attr_accessor :world, :accepted_cert_serial
|
4
4
|
|
@@ -75,7 +75,7 @@ class Proxy::Dynflow
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def silencer_matchers
|
78
|
-
@matchers ||= []
|
78
|
+
@matchers ||= [::Dynflow::DeadLetterSilencer::Matcher.new(Ticker)]
|
79
79
|
end
|
80
80
|
|
81
81
|
def register_silencer_matchers(matchers)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Proxy
|
2
|
-
|
2
|
+
module Dynflow
|
3
3
|
module Helpers
|
4
4
|
def world
|
5
5
|
Proxy::Dynflow::Core.world
|
@@ -7,12 +7,12 @@ module Proxy
|
|
7
7
|
|
8
8
|
def authorize_with_token(task_id:, clear: true)
|
9
9
|
if request.env.key? 'HTTP_AUTHORIZATION'
|
10
|
-
if defined?(::
|
10
|
+
if defined?(::Proxy::Dynflow)
|
11
11
|
auth = request.env['HTTP_AUTHORIZATION']
|
12
12
|
basic_prefix = /\ABasic /
|
13
13
|
if !auth.to_s.empty? && auth =~ basic_prefix &&
|
14
|
-
|
15
|
-
|
14
|
+
Proxy::Dynflow::OtpManager.authenticate(auth.gsub(basic_prefix, ''),
|
15
|
+
expected_user: task_id, clear: clear)
|
16
16
|
Log.instance.debug('authorized with token')
|
17
17
|
return true
|
18
18
|
end
|
@@ -35,7 +35,12 @@ module Proxy
|
|
35
35
|
|
36
36
|
def task_status(task_id)
|
37
37
|
ep = world.persistence.load_execution_plan(task_id)
|
38
|
-
|
38
|
+
actions = ep.actions.map do |action|
|
39
|
+
hash = action.to_hash
|
40
|
+
hash[:output][:result] = action.output_result if action.is_a?(Proxy::Dynflow::Action::Runner)
|
41
|
+
hash
|
42
|
+
end
|
43
|
+
ep.to_hash.merge(:actions => actions)
|
39
44
|
rescue KeyError => _e
|
40
45
|
status 404
|
41
46
|
{}
|
@@ -51,7 +56,7 @@ module Proxy
|
|
51
56
|
def dispatch_external_event(task_id, params)
|
52
57
|
world.event(task_id,
|
53
58
|
params['step_id'].to_i,
|
54
|
-
::
|
59
|
+
::Proxy::Dynflow::Runner::ExternalEvent.new(params))
|
55
60
|
end
|
56
61
|
end
|
57
62
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Proxy::Dynflow
|
5
|
+
class OtpManager
|
6
|
+
class << self
|
7
|
+
def generate_otp(username)
|
8
|
+
otp = SecureRandom.hex
|
9
|
+
passwords[username] = otp.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def drop_otp(username, password)
|
13
|
+
passwords.delete(username) if passwords[username] == password
|
14
|
+
end
|
15
|
+
|
16
|
+
def passwords
|
17
|
+
@password ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def authenticate(hash, expected_user: nil, clear: true)
|
21
|
+
plain = Base64.decode64(hash)
|
22
|
+
username, otp = plain.split(':', 2)
|
23
|
+
if expected_user
|
24
|
+
return false unless expected_user == username
|
25
|
+
end
|
26
|
+
password_matches = passwords[username] == otp
|
27
|
+
passwords.delete(username) if clear && password_matches
|
28
|
+
password_matches
|
29
|
+
end
|
30
|
+
|
31
|
+
def tokenize(username, password)
|
32
|
+
Base64.strict_encode64("#{username}:#{password}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -2,7 +2,7 @@ require 'proxy/log'
|
|
2
2
|
require 'proxy/pluggable'
|
3
3
|
require 'proxy/plugin'
|
4
4
|
|
5
|
-
|
5
|
+
module Proxy::Dynflow
|
6
6
|
class Plugin < Proxy::Plugin
|
7
7
|
rackup_path = File.expand_path('http_config.ru', __dir__)
|
8
8
|
http_rackup_path rackup_path
|
@@ -15,6 +15,14 @@ class Proxy::Dynflow
|
|
15
15
|
plugin :dynflow, Proxy::Dynflow::VERSION
|
16
16
|
|
17
17
|
after_activation do
|
18
|
+
require 'smart_proxy_dynflow/settings_loader'
|
19
|
+
require 'smart_proxy_dynflow/otp_manager'
|
20
|
+
require 'smart_proxy_dynflow/action'
|
21
|
+
require 'smart_proxy_dynflow/task_launcher'
|
22
|
+
|
23
|
+
Proxy::Dynflow::TaskLauncherRegistry.register('single',
|
24
|
+
Proxy::Dynflow::TaskLauncher::Single)
|
25
|
+
|
18
26
|
Proxy::Dynflow::Core.ensure_initialized
|
19
27
|
end
|
20
28
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Proxy::Dynflow
|
2
|
+
module Runner
|
3
|
+
# Runner is an object that is able to initiate some action and
|
4
|
+
# provide update data on refresh call.
|
5
|
+
class Base
|
6
|
+
attr_reader :id
|
7
|
+
attr_writer :logger
|
8
|
+
|
9
|
+
def initialize(*_args, suspended_action: nil)
|
10
|
+
@suspended_action = suspended_action
|
11
|
+
@id = SecureRandom.uuid
|
12
|
+
initialize_continuous_outputs
|
13
|
+
end
|
14
|
+
|
15
|
+
def logger
|
16
|
+
@logger ||= Logger.new(STDERR)
|
17
|
+
end
|
18
|
+
|
19
|
+
def run_refresh
|
20
|
+
logger.debug('refreshing runner')
|
21
|
+
refresh
|
22
|
+
generate_updates
|
23
|
+
end
|
24
|
+
|
25
|
+
# by default, external event just causes the refresh to be triggered: this allows the descendants
|
26
|
+
# of the Base to add custom logic to process the external events.
|
27
|
+
# Similarly as `run_refresh`, it's expected to return updates to be dispatched.
|
28
|
+
def external_event(_event)
|
29
|
+
run_refresh
|
30
|
+
end
|
31
|
+
|
32
|
+
def start
|
33
|
+
raise NotImplementedError
|
34
|
+
end
|
35
|
+
|
36
|
+
def refresh
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
def kill
|
41
|
+
# Override when you can kill the runner in the middle
|
42
|
+
end
|
43
|
+
|
44
|
+
def close
|
45
|
+
# if cleanup is needed
|
46
|
+
end
|
47
|
+
|
48
|
+
def timeout
|
49
|
+
# Override when timeouts and regular kills should be handled differently
|
50
|
+
publish_data('Timeout for execution passed, trying to stop the job', 'debug')
|
51
|
+
kill
|
52
|
+
end
|
53
|
+
|
54
|
+
def timeout_interval
|
55
|
+
# A number of seconds after which the runner should receive a #timeout
|
56
|
+
# or nil for no timeout
|
57
|
+
end
|
58
|
+
|
59
|
+
def publish_data(data, type)
|
60
|
+
@continuous_output.add_output(data, type)
|
61
|
+
end
|
62
|
+
|
63
|
+
def publish_exception(context, exception, fatal = true)
|
64
|
+
logger.error("#{context} - #{exception.class} #{exception.message}:\n" + \
|
65
|
+
exception.backtrace.join("\n"))
|
66
|
+
dispatch_exception context, exception
|
67
|
+
publish_exit_status('EXCEPTION') if fatal
|
68
|
+
end
|
69
|
+
|
70
|
+
def publish_exit_status(status)
|
71
|
+
@exit_status = status
|
72
|
+
end
|
73
|
+
|
74
|
+
def dispatch_exception(context, exception)
|
75
|
+
@continuous_output.add_exception(context, exception)
|
76
|
+
end
|
77
|
+
|
78
|
+
def generate_updates
|
79
|
+
return no_update if @continuous_output.empty? && @exit_status.nil?
|
80
|
+
new_data = @continuous_output
|
81
|
+
@continuous_output = Proxy::Dynflow::ContinuousOutput.new
|
82
|
+
new_update(new_data, @exit_status)
|
83
|
+
end
|
84
|
+
|
85
|
+
def no_update
|
86
|
+
{}
|
87
|
+
end
|
88
|
+
|
89
|
+
def new_update(data, exit_status)
|
90
|
+
{ @suspended_action => Runner::Update.new(data, exit_status) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize_continuous_outputs
|
94
|
+
@continuous_output = ::Proxy::Dynflow::ContinuousOutput.new
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Proxy::Dynflow
|
2
|
+
module Runner
|
3
|
+
module Command
|
4
|
+
def initialize_command(*command)
|
5
|
+
@command_out, @command_in, @command_pid = PTY.spawn(*command)
|
6
|
+
rescue Errno::ENOENT => e
|
7
|
+
publish_exception("Error running command '#{command.join(' ')}'", e)
|
8
|
+
end
|
9
|
+
|
10
|
+
def refresh
|
11
|
+
return if @command_out.nil?
|
12
|
+
ready_outputs, * = IO.select([@command_out], nil, nil, 0.1)
|
13
|
+
if ready_outputs
|
14
|
+
if @command_out.nread.positive?
|
15
|
+
lines = @command_out.read_nonblock(@command_out.nread)
|
16
|
+
else
|
17
|
+
close_io
|
18
|
+
Process.wait(@command_pid)
|
19
|
+
publish_exit_status($CHILD_STATUS.exitstatus)
|
20
|
+
end
|
21
|
+
publish_data(lines, 'stdout') if lines && !lines.empty?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def close
|
26
|
+
close_io
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def close_io
|
32
|
+
@command_out.close if @command_out && !@command_out.closed?
|
33
|
+
@command_out = nil
|
34
|
+
|
35
|
+
@command_in.close if @command_in && !@command_in.closed?
|
36
|
+
@command_in = nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|