smart_proxy_dynflow 0.2.1 → 0.4.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 +5 -5
- data/Gemfile +7 -22
- data/lib/smart_proxy_dynflow.rb +21 -1
- data/lib/smart_proxy_dynflow/api.rb +61 -38
- data/lib/smart_proxy_dynflow/callback.rb +69 -24
- data/lib/smart_proxy_dynflow/core.rb +121 -0
- data/lib/smart_proxy_dynflow/helpers.rb +51 -5
- data/lib/smart_proxy_dynflow/http_config.ru +5 -1
- data/lib/smart_proxy_dynflow/log.rb +52 -0
- data/lib/smart_proxy_dynflow/middleware/keep_current_request_id.rb +59 -0
- data/lib/smart_proxy_dynflow/plugin.rb +7 -16
- data/lib/smart_proxy_dynflow/settings.rb +9 -0
- data/lib/smart_proxy_dynflow/task_launcher_registry.rb +31 -0
- data/lib/smart_proxy_dynflow/testing.rb +24 -0
- data/lib/smart_proxy_dynflow/version.rb +1 -1
- data/settings.d/dynflow.yml.example +7 -1
- metadata +45 -27
- data/lib/smart_proxy_dynflow/http_config_with_executor.ru +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b3c381a5358391ea92bacf8c59c3a279b49a50a49369bd962c0ff94e7c4d459e
|
4
|
+
data.tar.gz: 3d83db86f0ce2a76b5d1e1f4ca5afd57eb82a5b327ba1ad2f561c77217e4ae0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da9ce875255e93fcc491fc438eb6cbe3fb0b803b6524539a71575f47b29e0a65bcffb1378a6bacc3eee85e4c5194a3d0e3a9f77faff3fe5a82211f4242f671ea
|
7
|
+
data.tar.gz: 55b4f92fdf94ca2b0cde9282f02be12d167674bd358729bd26a766d320a45de0dcc2f8535d94dc31b164963691e076b6b3740c60f385b4f9ca18b2d06c8abf82
|
data/Gemfile
CHANGED
@@ -7,32 +7,17 @@ group :development do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
group :test do
|
10
|
-
gem 'smart_proxy_dynflow', :path => '.'
|
11
10
|
gem 'smart_proxy', :git => "https://github.com/theforeman/smart-proxy", :branch => "develop"
|
11
|
+
gem 'smart_proxy_dynflow', :path => '.'
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
gem 'rainbow', '< 3'
|
17
|
-
else
|
18
|
-
gem 'rubocop', '~> 0.52.1'
|
19
|
-
gem 'public_suffix'
|
20
|
-
end
|
21
|
-
|
22
|
-
if RUBY_VERSION < '2.2'
|
23
|
-
gem 'rack-test', '< 0.8'
|
24
|
-
else
|
25
|
-
gem 'rack-test'
|
26
|
-
end
|
13
|
+
gem 'public_suffix'
|
14
|
+
gem 'rack-test'
|
15
|
+
gem 'rubocop', '~> 0.52.1'
|
27
16
|
end
|
28
17
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
else
|
33
|
-
gem 'sinatra'
|
34
|
-
gem 'rack', '>= 1.1'
|
35
|
-
end
|
18
|
+
gem 'logging-journald', '~> 2.0', :platforms => [:ruby], :require => false
|
19
|
+
gem 'rack', '>= 1.1'
|
20
|
+
gem 'sinatra'
|
36
21
|
|
37
22
|
# load bundler.d
|
38
23
|
Dir["#{File.dirname(__FILE__)}/bundler.d/*.rb"].each do |bundle|
|
data/lib/smart_proxy_dynflow.rb
CHANGED
@@ -1,5 +1,25 @@
|
|
1
|
+
require 'dynflow'
|
2
|
+
|
3
|
+
require 'smart_proxy_dynflow/task_launcher_registry'
|
4
|
+
require 'smart_proxy_dynflow/middleware/keep_current_request_id'
|
5
|
+
|
6
|
+
require 'foreman_tasks_core'
|
7
|
+
|
8
|
+
require 'smart_proxy_dynflow/log'
|
9
|
+
require 'smart_proxy_dynflow/settings'
|
10
|
+
require 'smart_proxy_dynflow/core'
|
11
|
+
require 'smart_proxy_dynflow/callback'
|
12
|
+
|
1
13
|
require 'smart_proxy_dynflow/version'
|
2
14
|
require 'smart_proxy_dynflow/plugin'
|
3
|
-
require 'smart_proxy_dynflow/callback'
|
4
15
|
require 'smart_proxy_dynflow/helpers'
|
5
16
|
require 'smart_proxy_dynflow/api'
|
17
|
+
|
18
|
+
module Proxy
|
19
|
+
class Dynflow
|
20
|
+
Core.after_initialize do |dynflow_core|
|
21
|
+
ForemanTasksCore.dynflow_setup(dynflow_core.world)
|
22
|
+
end
|
23
|
+
Core.register_silencer_matchers ForemanTasksCore.silent_dead_letter_matchers
|
24
|
+
end
|
25
|
+
end
|
@@ -9,57 +9,80 @@ module Proxy
|
|
9
9
|
helpers ::Proxy::Log
|
10
10
|
helpers ::Proxy::Dynflow::Helpers
|
11
11
|
|
12
|
+
include ::Sinatra::Authorization::Helpers
|
13
|
+
|
14
|
+
TASK_UPDATE_REGEXP_PATH = %r{/tasks/(\S+)/(update|done)}
|
15
|
+
|
12
16
|
before do
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
if match = request.path_info.match(TASK_UPDATE_REGEXP_PATH)
|
18
|
+
task_id = match[1]
|
19
|
+
action = match[2]
|
20
|
+
authorize_with_token(task_id: task_id, clear: action == 'done')
|
17
21
|
else
|
18
|
-
|
19
|
-
do_authorize_with_trusted_hosts
|
22
|
+
do_authorize_any
|
20
23
|
end
|
24
|
+
content_type :json
|
21
25
|
end
|
22
26
|
|
27
|
+
post "/tasks/status" do
|
28
|
+
params = MultiJson.load(request.body.read)
|
29
|
+
ids = params.fetch('task_ids', [])
|
30
|
+
result = world.persistence
|
31
|
+
.find_execution_plans(:filters => { :uuid => ids }).reduce({}) do |acc, plan|
|
32
|
+
acc.update(plan.id => { 'state' => plan.state, 'result' => plan.result })
|
33
|
+
end
|
34
|
+
MultiJson.dump(result)
|
35
|
+
end
|
23
36
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
if trusted_hosts
|
31
|
-
if [ 'yes', 'on', 1 ].include? request.env['HTTPS'].to_s
|
32
|
-
fqdn = https_cert_cn
|
33
|
-
source = 'SSL_CLIENT_CERT'
|
34
|
-
else
|
35
|
-
fqdn = remote_fqdn(Proxy::SETTINGS.forward_verify)
|
36
|
-
source = 'REMOTE_ADDR'
|
37
|
-
end
|
38
|
-
fqdn = fqdn.downcase
|
39
|
-
logger.debug "verifying remote client #{fqdn} (based on #{source}) against trusted_hosts #{trusted_hosts}"
|
37
|
+
post "/tasks/launch/?" do
|
38
|
+
params = MultiJson.load(request.body.read)
|
39
|
+
launcher = launcher_class(params).new(world, callback_host(params, request), params.fetch('options', {}))
|
40
|
+
launcher.launch!(params['input'])
|
41
|
+
launcher.results.to_json
|
42
|
+
end
|
40
43
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
post "/tasks/?" do
|
45
|
+
params = MultiJson.load(request.body.read)
|
46
|
+
trigger_task(::Dynflow::Utils.constantize(params['action_name']),
|
47
|
+
params['action_input'].merge(:callback_host => callback_host(params, request))).to_json
|
45
48
|
end
|
46
49
|
|
47
|
-
|
48
|
-
|
49
|
-
if request.env['SSL_CLIENT_CERT'].to_s.empty?
|
50
|
-
log_halt 403, "No client SSL certificate supplied"
|
51
|
-
end
|
52
|
-
else
|
53
|
-
logger.debug('require_ssl_client_verification: skipping, non-HTTPS request')
|
54
|
-
end
|
50
|
+
post "/tasks/:task_id/cancel" do |task_id|
|
51
|
+
cancel_task(task_id).to_json
|
55
52
|
end
|
56
53
|
|
57
|
-
|
58
|
-
|
54
|
+
get "/tasks/:task_id/status" do |task_id|
|
55
|
+
task_status(task_id).to_json
|
59
56
|
end
|
60
57
|
|
61
|
-
get "
|
62
|
-
|
58
|
+
get "/tasks/count" do
|
59
|
+
tasks_count(params['state']).to_json
|
60
|
+
end
|
61
|
+
|
62
|
+
# capturing post "/tasks/:task_id/(update|done)"
|
63
|
+
post TASK_UPDATE_REGEXP_PATH do |task_id, _action|
|
64
|
+
data = MultiJson.load(request.body.read)
|
65
|
+
dispatch_external_event(task_id, data)
|
66
|
+
end
|
67
|
+
|
68
|
+
get "/tasks/operations" do
|
69
|
+
TaskLauncherRegistry.operations.to_json
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def callback_host(params, request)
|
75
|
+
params.fetch('action_input', {})['proxy_url'] ||
|
76
|
+
request.env.values_at('HTTP_X_FORWARDED_FOR', 'HTTP_HOST').compact.first
|
77
|
+
end
|
78
|
+
|
79
|
+
def launcher_class(params)
|
80
|
+
operation = params.fetch('operation')
|
81
|
+
if TaskLauncherRegistry.key?(operation)
|
82
|
+
TaskLauncherRegistry.fetch(operation)
|
83
|
+
else
|
84
|
+
halt 404, MultiJson.dump(:error => "Unknown operation '#{operation}' requested.")
|
85
|
+
end
|
63
86
|
end
|
64
87
|
end
|
65
88
|
end
|
@@ -1,32 +1,77 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
class
|
7
|
-
def
|
8
|
-
|
1
|
+
require 'rest-client'
|
2
|
+
|
3
|
+
class Proxy::Dynflow
|
4
|
+
module Callback
|
5
|
+
class Request
|
6
|
+
class << self
|
7
|
+
def send_to_foreman_tasks(callback_info, data)
|
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
|
9
32
|
end
|
33
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
10
34
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
when 'GET'
|
16
|
-
request_factory.create_get path, request.env['rack.request.query_hash']
|
17
|
-
when 'POST'
|
18
|
-
request_factory.create_post path, request.body.read
|
19
|
-
end
|
20
|
-
req['X-Forwarded-For'] = request.env['HTTP_HOST']
|
21
|
-
req['AUTHORIZATION'] = request.env['HTTP_AUTHORIZATION']
|
22
|
-
response = send_request req
|
23
|
-
Proxy::LogBuffer::Decorator.instance.debug "Proxy request status #{response.code} - #{response}"
|
24
|
-
response
|
35
|
+
private
|
36
|
+
|
37
|
+
def prepare_payload(callback, data)
|
38
|
+
{ :callback => callback, :data => data }.to_json
|
25
39
|
end
|
40
|
+
end
|
26
41
|
|
27
|
-
|
28
|
-
|
42
|
+
def callback(payload)
|
43
|
+
response = callback_resource.post(payload, :content_type => :json)
|
44
|
+
if response.code.to_s != "200"
|
45
|
+
raise "Failed performing callback to Foreman server: #{response.code} #{response.body}"
|
29
46
|
end
|
47
|
+
response
|
48
|
+
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
|
+
end
|
57
|
+
|
58
|
+
class Action < ::Dynflow::Action
|
59
|
+
def plan(callback, data)
|
60
|
+
plan_self(:callback => callback, :data => data)
|
61
|
+
end
|
62
|
+
|
63
|
+
def run
|
64
|
+
Callback::Request.send_to_foreman_tasks(input[:callback], input[:data])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module PlanHelper
|
69
|
+
def plan_with_callback(input)
|
70
|
+
input = input.dup
|
71
|
+
callback = input.delete('callback')
|
72
|
+
|
73
|
+
planned_action = plan_self(input)
|
74
|
+
plan_action(Callback::Action, callback, planned_action.output) if callback
|
30
75
|
end
|
31
76
|
end
|
32
77
|
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
class Proxy::Dynflow
|
2
|
+
class Core
|
3
|
+
attr_accessor :world, :accepted_cert_serial
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@world = create_world
|
7
|
+
cert_file = Proxy::SETTINGS.foreman_ssl_cert || Proxy::SETTINGS.ssl_certificate
|
8
|
+
if cert_file
|
9
|
+
client_cert = File.read(cert_file)
|
10
|
+
# we trust only requests using the same certificate as we are
|
11
|
+
# (in other words the local proxy only)
|
12
|
+
@accepted_cert_serial = OpenSSL::X509::Certificate.new(client_cert).serial
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_world(&block)
|
17
|
+
config = default_world_config(&block)
|
18
|
+
world = ::Dynflow::World.new(config)
|
19
|
+
world.middleware.use ::Actions::Middleware::KeepCurrentRequestID
|
20
|
+
world
|
21
|
+
end
|
22
|
+
|
23
|
+
def persistence_conn_string
|
24
|
+
return ENV['DYNFLOW_DB_CONN_STRING'] if ENV.key? 'DYNFLOW_DB_CONN_STRING'
|
25
|
+
db_conn_string = 'sqlite:/'
|
26
|
+
|
27
|
+
db_file = Settings.instance.database
|
28
|
+
if db_file.nil? || db_file.empty?
|
29
|
+
Log.instance.warn "Could not open DB for dynflow at '#{db_file}', " \
|
30
|
+
"will keep data in memory. Restart will drop all dynflow data."
|
31
|
+
else
|
32
|
+
db_conn_string += "/#{db_file}"
|
33
|
+
end
|
34
|
+
|
35
|
+
db_conn_string
|
36
|
+
end
|
37
|
+
|
38
|
+
def persistence_adapter
|
39
|
+
::Dynflow::PersistenceAdapters::Sequel.new persistence_conn_string
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_world_config
|
43
|
+
::Dynflow::Config.new.tap do |config|
|
44
|
+
config.auto_rescue = true
|
45
|
+
config.logger_adapter = logger_adapter
|
46
|
+
config.persistence_adapter = persistence_adapter
|
47
|
+
config.execution_plan_cleaner = execution_plan_cleaner
|
48
|
+
# TODO: There has to be a better way
|
49
|
+
matchers = config.silent_dead_letter_matchers.call.concat(self.class.silencer_matchers)
|
50
|
+
config.silent_dead_letter_matchers = matchers
|
51
|
+
yield config if block_given?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def logger_adapter
|
56
|
+
Log::ProxyAdapter.new(Proxy::LogBuffer::Decorator.instance, Log.instance.level)
|
57
|
+
end
|
58
|
+
|
59
|
+
def execution_plan_cleaner
|
60
|
+
proc do |world|
|
61
|
+
age = Settings.instance.execution_plan_cleaner_age
|
62
|
+
options = { :poll_interval => age, :max_age => age }
|
63
|
+
::Dynflow::Actors::ExecutionPlanCleaner.new(world, options)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class << self
|
68
|
+
attr_reader :instance
|
69
|
+
|
70
|
+
def ensure_initialized
|
71
|
+
return @instance if @instance
|
72
|
+
@instance = Core.new
|
73
|
+
after_initialize_blocks.each { |block| block.call(@instance) }
|
74
|
+
@instance
|
75
|
+
end
|
76
|
+
|
77
|
+
def silencer_matchers
|
78
|
+
@matchers ||= []
|
79
|
+
end
|
80
|
+
|
81
|
+
def register_silencer_matchers(matchers)
|
82
|
+
silencer_matchers.concat matchers
|
83
|
+
end
|
84
|
+
|
85
|
+
def web_console
|
86
|
+
require 'dynflow/web'
|
87
|
+
dynflow_console = ::Dynflow::Web.setup do
|
88
|
+
# we can't use the proxy's after_activation hook, as
|
89
|
+
# it happens before the Daemon forks the process (including
|
90
|
+
# closing opened file descriptors)
|
91
|
+
# TODO: extend smart proxy to enable hooks that happen after
|
92
|
+
# the forking
|
93
|
+
helpers Helpers
|
94
|
+
include ::Sinatra::Authorization::Helpers
|
95
|
+
|
96
|
+
before do
|
97
|
+
do_authorize_with_ssl_client if Settings.instance.console_auth
|
98
|
+
end
|
99
|
+
|
100
|
+
Core.ensure_initialized
|
101
|
+
set :world, Core.world
|
102
|
+
end
|
103
|
+
dynflow_console
|
104
|
+
end
|
105
|
+
|
106
|
+
def world
|
107
|
+
instance.world
|
108
|
+
end
|
109
|
+
|
110
|
+
def after_initialize(&block)
|
111
|
+
after_initialize_blocks << block
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def after_initialize_blocks
|
117
|
+
@after_initialize_blocks ||= []
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -1,11 +1,57 @@
|
|
1
1
|
module Proxy
|
2
2
|
class Dynflow
|
3
3
|
module Helpers
|
4
|
-
def
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
def world
|
5
|
+
Proxy::Dynflow::Core.world
|
6
|
+
end
|
7
|
+
|
8
|
+
def authorize_with_token(task_id:, clear: true)
|
9
|
+
if request.env.key? 'HTTP_AUTHORIZATION'
|
10
|
+
if defined?(::ForemanTasksCore)
|
11
|
+
auth = request.env['HTTP_AUTHORIZATION']
|
12
|
+
basic_prefix = /\ABasic /
|
13
|
+
if !auth.to_s.empty? && auth =~ basic_prefix &&
|
14
|
+
ForemanTasksCore::OtpManager.authenticate(auth.gsub(basic_prefix, ''),
|
15
|
+
expected_user: task_id, clear: clear)
|
16
|
+
Log.instance.debug('authorized with token')
|
17
|
+
return true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
halt 403, MultiJson.dump(:error => 'Invalid username or password supplied')
|
21
|
+
end
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def trigger_task(*args)
|
26
|
+
triggered = world.trigger(*args)
|
27
|
+
{ :task_id => triggered.id }
|
28
|
+
end
|
29
|
+
|
30
|
+
def cancel_task(task_id)
|
31
|
+
execution_plan = world.persistence.load_execution_plan(task_id)
|
32
|
+
cancel_events = execution_plan.cancel
|
33
|
+
{ :task_id => task_id, :canceled_steps_count => cancel_events.size }
|
34
|
+
end
|
35
|
+
|
36
|
+
def task_status(task_id)
|
37
|
+
ep = world.persistence.load_execution_plan(task_id)
|
38
|
+
ep.to_hash.merge(:actions => ep.actions.map(&:to_hash))
|
39
|
+
rescue KeyError => _e
|
40
|
+
status 404
|
41
|
+
{}
|
42
|
+
end
|
43
|
+
|
44
|
+
def tasks_count(state)
|
45
|
+
state ||= 'all'
|
46
|
+
filter = state != 'all' ? { :filters => { :state => [state] } } : {}
|
47
|
+
tasks = world.persistence.find_execution_plans(filter)
|
48
|
+
{ :count => tasks.count, :state => state }
|
49
|
+
end
|
50
|
+
|
51
|
+
def dispatch_external_event(task_id, params)
|
52
|
+
world.event(task_id,
|
53
|
+
params['step_id'].to_i,
|
54
|
+
::ForemanTasksCore::Runner::ExternalEvent.new(params))
|
9
55
|
end
|
10
56
|
end
|
11
57
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'logging'
|
2
|
+
|
3
|
+
class Proxy::Dynflow
|
4
|
+
class Log
|
5
|
+
LOGGER_NAME = 'dynflow-core'.freeze
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'syslog/logger'
|
9
|
+
@syslog_available = true
|
10
|
+
rescue LoadError
|
11
|
+
@syslog_available = false
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def reload!
|
16
|
+
Logging.logger[LOGGER_NAME].appenders.each(&:close)
|
17
|
+
Logging.logger[LOGGER_NAME].clear_appenders
|
18
|
+
@logger = nil
|
19
|
+
instance
|
20
|
+
end
|
21
|
+
|
22
|
+
def instance
|
23
|
+
::Proxy::LogBuffer::Decorator.instance
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ProxyStructuredFormater < ::Dynflow::LoggerAdapters::Formatters::Abstract
|
28
|
+
def format(message)
|
29
|
+
if message.is_a?(Exception)
|
30
|
+
subject = "#{message.message} (#{message.class})"
|
31
|
+
if @base.respond_to?(:exception)
|
32
|
+
@base.exception("Error details", message)
|
33
|
+
subject
|
34
|
+
else
|
35
|
+
"#{subject}\n#{message.backtrace.join("\n")}"
|
36
|
+
end
|
37
|
+
else
|
38
|
+
@original_formatter.call(severity, datetime, prog_name, message)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class ProxyAdapter < ::Dynflow::LoggerAdapters::Simple
|
44
|
+
def initialize(logger, level = Logger::DEBUG, _formatters = [])
|
45
|
+
@logger = logger
|
46
|
+
@logger.level = level
|
47
|
+
@action_logger = apply_formatters(ProgNameWrapper.new(@logger, ' action'), [])
|
48
|
+
@dynflow_logger = apply_formatters(ProgNameWrapper.new(@logger, 'dynflow'), [])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Actions
|
2
|
+
module Middleware
|
3
|
+
class KeepCurrentRequestID < Dynflow::Middleware
|
4
|
+
def delay(*args)
|
5
|
+
pass(*args).tap { store_current_request_id }
|
6
|
+
end
|
7
|
+
|
8
|
+
def plan(*args)
|
9
|
+
with_current_request_id do
|
10
|
+
pass(*args).tap { store_current_request_id }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(*args)
|
15
|
+
restore_current_request_id { pass(*args) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def finalize
|
19
|
+
restore_current_request_id { pass }
|
20
|
+
end
|
21
|
+
|
22
|
+
# Run all execution plan lifecycle hooks as the original request_id
|
23
|
+
def hook(*args)
|
24
|
+
restore_current_request_id { pass(*args) }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def with_current_request_id
|
30
|
+
if action.input[:current_request_id].nil?
|
31
|
+
yield
|
32
|
+
else
|
33
|
+
restore_current_request_id { yield }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def store_current_request_id
|
38
|
+
action.input[:current_request_id] = ::Logging.mdc['request']
|
39
|
+
end
|
40
|
+
|
41
|
+
def restore_current_request_id
|
42
|
+
unless (restored_id = action.input[:current_request_id]).nil?
|
43
|
+
old_id = ::Logging.mdc['request']
|
44
|
+
if !old_id.nil? && old_id != restored_id
|
45
|
+
action.action_logger.warn('Changing request id %{request_id} to saved id %{saved_id}' % { :saved_id => restored_id, :request_id => old_id })
|
46
|
+
end
|
47
|
+
::Logging.mdc['request'] = restored_id
|
48
|
+
end
|
49
|
+
yield
|
50
|
+
ensure
|
51
|
+
# Reset to original request id only when not nil
|
52
|
+
# Otherwise, keep the id until it's cleaned in Dynflow's run_user_code block
|
53
|
+
# so that it will stay valid for the rest of the processing of the current step
|
54
|
+
# (even outside of the middleware lifecycle)
|
55
|
+
::Logging.mdc['request'] = old_id unless old_id.nil?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -4,27 +4,18 @@ require 'proxy/plugin'
|
|
4
4
|
|
5
5
|
class Proxy::Dynflow
|
6
6
|
class Plugin < Proxy::Plugin
|
7
|
-
rackup_path =
|
8
|
-
|
9
|
-
|
10
|
-
rescue LoadError
|
11
|
-
'http_config.ru'
|
12
|
-
end
|
13
|
-
http_rackup_path File.expand_path(rackup_path, File.expand_path("../", __FILE__))
|
14
|
-
https_rackup_path File.expand_path(rackup_path, File.expand_path("../", __FILE__))
|
7
|
+
rackup_path = File.expand_path('http_config.ru', __dir__)
|
8
|
+
http_rackup_path rackup_path
|
9
|
+
https_rackup_path rackup_path
|
15
10
|
|
16
11
|
settings_file "dynflow.yml"
|
17
|
-
requires :foreman_proxy, ">= 1.
|
18
|
-
default_settings :
|
12
|
+
requires :foreman_proxy, ">= 1.16.0"
|
13
|
+
default_settings :console_auth => true,
|
14
|
+
:execution_plan_cleaner_age => 60 * 60 * 24
|
19
15
|
plugin :dynflow, Proxy::Dynflow::VERSION
|
20
16
|
|
21
17
|
after_activation do
|
22
|
-
|
23
|
-
require 'smart_proxy_dynflow_core'
|
24
|
-
rescue LoadError => e
|
25
|
-
# Dynflow core is not available in the proxy, will be handled
|
26
|
-
# by standalone Dynflow core
|
27
|
-
end
|
18
|
+
Proxy::Dynflow::Core.ensure_initialized
|
28
19
|
end
|
29
20
|
end
|
30
21
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Proxy::Dynflow
|
2
|
+
class TaskLauncherRegistry
|
3
|
+
class << self
|
4
|
+
def register(name, launcher)
|
5
|
+
registry[name] = launcher
|
6
|
+
end
|
7
|
+
|
8
|
+
def fetch(name, default = nil)
|
9
|
+
if default.nil?
|
10
|
+
registry.fetch(name)
|
11
|
+
else
|
12
|
+
registry.fetch(name, default)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def key?(name)
|
17
|
+
registry.key?(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def operations
|
21
|
+
registry.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def registry
|
27
|
+
@registry ||= {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'dynflow/testing'
|
2
|
+
|
3
|
+
unless defined? DYNFLOW_TESTING_LOG_LEVEL
|
4
|
+
DYNFLOW_TESTING_LOG_LEVEL = 4
|
5
|
+
end
|
6
|
+
|
7
|
+
class Proxy::Dynflow
|
8
|
+
# Helper for usage in other dependent plugins that need Dynflow
|
9
|
+
# related things, such as testing instance of world etc.
|
10
|
+
module Testing
|
11
|
+
class << self
|
12
|
+
def create_world(&block)
|
13
|
+
Core.ensure_initialized
|
14
|
+
Core.instance.create_world do |config|
|
15
|
+
config.exit_on_terminate = false
|
16
|
+
config.auto_terminate = false
|
17
|
+
config.logger_adapter = ::Dynflow::LoggerAdapters::Simple.new $stderr, DYNFLOW_TESTING_LOG_LEVEL
|
18
|
+
config.execution_plan_cleaner = nil
|
19
|
+
yield(config) if block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,4 +1,10 @@
|
|
1
1
|
---
|
2
2
|
:enabled: true
|
3
3
|
:database: /var/lib/foreman-proxy/dynflow/dynflow.sqlite
|
4
|
-
|
4
|
+
|
5
|
+
# Require a valid cert to access Dynflow console
|
6
|
+
# :console_auth: true
|
7
|
+
|
8
|
+
# Maximum age of execution plans to keep before having them cleaned
|
9
|
+
# by the execution plan cleaner (in seconds), defaults to 24 hours
|
10
|
+
# :execution_plan_cleaner_age: 86400
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_proxy_dynflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Nečas
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: logging
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
type: :
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '1.7'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '1.7'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,33 +67,47 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rack-test
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: rake
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
89
|
+
version: '10.0'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1'
|
97
111
|
description: " Use the Dynflow inside Foreman smart proxy\n"
|
98
112
|
email:
|
99
113
|
- inecas@redhat.com
|
@@ -107,35 +121,39 @@ files:
|
|
107
121
|
- lib/smart_proxy_dynflow.rb
|
108
122
|
- lib/smart_proxy_dynflow/api.rb
|
109
123
|
- lib/smart_proxy_dynflow/callback.rb
|
124
|
+
- lib/smart_proxy_dynflow/core.rb
|
110
125
|
- lib/smart_proxy_dynflow/helpers.rb
|
111
126
|
- lib/smart_proxy_dynflow/http_config.ru
|
112
|
-
- lib/smart_proxy_dynflow/
|
127
|
+
- lib/smart_proxy_dynflow/log.rb
|
128
|
+
- lib/smart_proxy_dynflow/middleware/keep_current_request_id.rb
|
113
129
|
- lib/smart_proxy_dynflow/plugin.rb
|
114
130
|
- lib/smart_proxy_dynflow/proxy_adapter.rb
|
131
|
+
- lib/smart_proxy_dynflow/settings.rb
|
132
|
+
- lib/smart_proxy_dynflow/task_launcher_registry.rb
|
133
|
+
- lib/smart_proxy_dynflow/testing.rb
|
115
134
|
- lib/smart_proxy_dynflow/version.rb
|
116
135
|
- settings.d/dynflow.yml.example
|
117
136
|
homepage: https://github.com/theforeman/smart_proxy_dynflow
|
118
137
|
licenses:
|
119
138
|
- GPL-3.0
|
120
139
|
metadata: {}
|
121
|
-
post_install_message:
|
140
|
+
post_install_message:
|
122
141
|
rdoc_options: []
|
123
142
|
require_paths:
|
124
143
|
- lib
|
125
144
|
required_ruby_version: !ruby/object:Gem::Requirement
|
126
145
|
requirements:
|
127
|
-
- - "
|
146
|
+
- - "~>"
|
128
147
|
- !ruby/object:Gem::Version
|
129
|
-
version: '
|
148
|
+
version: '2.5'
|
130
149
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
150
|
requirements:
|
132
151
|
- - ">="
|
133
152
|
- !ruby/object:Gem::Version
|
134
153
|
version: '0'
|
135
154
|
requirements: []
|
136
|
-
|
137
|
-
|
138
|
-
signing_key:
|
155
|
+
rubygems_version: 3.1.2
|
156
|
+
signing_key:
|
139
157
|
specification_version: 4
|
140
158
|
summary: Dynflow runtime for Foreman smart proxy
|
141
159
|
test_files: []
|