smart_proxy_dynflow_core 0.2.2 → 0.3.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 +6 -21
- data/config/settings.yml.example +19 -3
- data/deploy/smart_proxy_dynflow_core.service +2 -3
- data/lib/smart_proxy_dynflow_core.rb +3 -0
- data/lib/smart_proxy_dynflow_core/api.rb +19 -3
- data/lib/smart_proxy_dynflow_core/bundler_helper.rb +0 -1
- data/lib/smart_proxy_dynflow_core/callback.rb +0 -1
- data/lib/smart_proxy_dynflow_core/core.rb +3 -1
- data/lib/smart_proxy_dynflow_core/helpers.rb +4 -3
- data/lib/smart_proxy_dynflow_core/launcher.rb +22 -15
- data/lib/smart_proxy_dynflow_core/log.rb +104 -44
- data/lib/smart_proxy_dynflow_core/logger_middleware.rb +31 -0
- data/lib/smart_proxy_dynflow_core/request_id_middleware.rb +18 -0
- data/lib/smart_proxy_dynflow_core/settings.rb +7 -25
- data/lib/smart_proxy_dynflow_core/task_launcher_registry.rb +0 -1
- data/lib/smart_proxy_dynflow_core/version.rb +1 -1
- data/smart_proxy_dynflow_core.gemspec +6 -5
- metadata +41 -27
- data/lib/smart_proxy_dynflow_core/webrick-patch.rb +0 -39
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 467f03a2c2c0ad4e3b168039abdb8e9a465a46b8ecf44aade57c5a09bbeafbfc
|
|
4
|
+
data.tar.gz: e966e14e4a95f42af61b5bcf47595ad6afa241271656b6724e5000b3e2cd354d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6a3b2b7b4dc313c1b6cc215c2de6cda4f49ad9f901bc7ab949b0ed4a16521e021b9c39a0fc371091e54172610787f968a2bb8577f68086ef15308bd805fc970
|
|
7
|
+
data.tar.gz: 7cbfc949016d5d824ebbb421093c8ef687c24d33c6699e7ef0c875145e035e8233317ff64c7757597bd0e76c9186566dec4e407397e32803b6d42ea26602fb6f
|
data/Gemfile
CHANGED
|
@@ -10,29 +10,14 @@ group :test do
|
|
|
10
10
|
gem 'smart_proxy', :git => "https://github.com/theforeman/smart-proxy", :branch => "develop"
|
|
11
11
|
gem 'smart_proxy_dynflow', :path => '.'
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
gem 'rubocop', '< 0.51.0'
|
|
17
|
-
else
|
|
18
|
-
gem 'public_suffix'
|
|
19
|
-
gem 'rubocop', '~> 0.52.1'
|
|
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 'rack', '>= 1.1'
|
|
34
|
-
gem 'sinatra'
|
|
35
|
-
end
|
|
18
|
+
gem 'logging-journald', '~> 2.0', :platforms => [:ruby]
|
|
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/config/settings.yml.example
CHANGED
|
@@ -39,11 +39,27 @@
|
|
|
39
39
|
# Specify versions like: '1.1', or '1.2'
|
|
40
40
|
#:tls_disabled_versions: []
|
|
41
41
|
|
|
42
|
-
# File to log to, leave empty for
|
|
43
|
-
|
|
42
|
+
# File to log to, leave empty for stdout or use STDOUT, STDERR, SYSLOG or JOURNAL
|
|
43
|
+
#:log_file: /var/log/foreman-proxy/smart_proxy_dynflow_core.log
|
|
44
44
|
|
|
45
45
|
# Log level, one of UNKNOWN, FATAL, ERROR, WARN, INFO, DEBUG
|
|
46
|
-
|
|
46
|
+
#:log_level: ERROR
|
|
47
|
+
|
|
48
|
+
# The maximum size of a log file before it's rolled (in MiB) or zero to disable file rolling
|
|
49
|
+
# and rely on logrotate or similar tool
|
|
50
|
+
#:file_rolling_size: 0
|
|
51
|
+
|
|
52
|
+
# The maximum age of a log file before it's rolled (in seconds). Also accepts 'daily', 'weekly', or 'monthly'.
|
|
53
|
+
#:file_rolling_age: weekly
|
|
54
|
+
|
|
55
|
+
# Number of log files to keep
|
|
56
|
+
#:file_rolling_keep: 6
|
|
57
|
+
|
|
58
|
+
# Logging pattern for file-based loging
|
|
59
|
+
#:file_logging_pattern: '%d %.8X{request} [%.1l] %m'
|
|
60
|
+
|
|
61
|
+
# Logging pattern for syslog or journal loging
|
|
62
|
+
#:system_logging_pattern: '%.8X{request} [%.1l] %m'
|
|
47
63
|
|
|
48
64
|
# Maximum age of execution plans to keep before having them cleaned
|
|
49
65
|
# by the execution plan cleaner (in seconds), defaults to 24 hours
|
|
@@ -4,10 +4,9 @@ Documentation=https://github.com/theforeman/smart_proxy_dynflow
|
|
|
4
4
|
After=network.target remote-fs.target nss-lookup.target
|
|
5
5
|
|
|
6
6
|
[Service]
|
|
7
|
-
Type=
|
|
7
|
+
Type=notify
|
|
8
8
|
User=foreman-proxy
|
|
9
|
-
|
|
10
|
-
ExecStart=/usr/bin/smart_proxy_dynflow_core -d -p /var/run/foreman-proxy/smart_proxy_dynflow_core.pid
|
|
9
|
+
ExecStart=/usr/bin/smart_proxy_dynflow_core --no-daemonize
|
|
11
10
|
EnvironmentFile=-/etc/sysconfig/smart_proxy_dynflow_core
|
|
12
11
|
|
|
13
12
|
[Install]
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
require 'dynflow'
|
|
2
|
+
require 'smart_proxy_dynflow_core/request_id_middleware'
|
|
3
|
+
require 'smart_proxy_dynflow_core/logger_middleware'
|
|
4
|
+
require 'smart_proxy_dynflow_core/middleware/keep_current_request_id'
|
|
2
5
|
require 'smart_proxy_dynflow_core/task_launcher_registry'
|
|
3
6
|
require 'foreman_tasks_core'
|
|
4
7
|
require 'smart_proxy_dynflow_core/log'
|
|
@@ -3,10 +3,25 @@ require 'multi_json'
|
|
|
3
3
|
|
|
4
4
|
module SmartProxyDynflowCore
|
|
5
5
|
class Api < ::Sinatra::Base
|
|
6
|
+
TASK_UPDATE_REGEXP_PATH = %r{/tasks/(\S+)/(update|done)}
|
|
6
7
|
helpers Helpers
|
|
7
8
|
|
|
9
|
+
configure do
|
|
10
|
+
if Settings.instance.standalone
|
|
11
|
+
::Sinatra::Base.set :logging, false
|
|
12
|
+
::Sinatra::Base.use ::SmartProxyDynflowCore::RequestIdMiddleware
|
|
13
|
+
::Sinatra::Base.use ::SmartProxyDynflowCore::LoggerMiddleware
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
8
17
|
before do
|
|
9
|
-
|
|
18
|
+
if match = request.path_info.match(TASK_UPDATE_REGEXP_PATH)
|
|
19
|
+
task_id = match[1]
|
|
20
|
+
action = match[2]
|
|
21
|
+
authorize_with_token(task_id: task_id, clear: action == 'done')
|
|
22
|
+
else
|
|
23
|
+
authorize_with_ssl_client
|
|
24
|
+
end
|
|
10
25
|
content_type :json
|
|
11
26
|
end
|
|
12
27
|
|
|
@@ -45,9 +60,10 @@ module SmartProxyDynflowCore
|
|
|
45
60
|
tasks_count(params['state']).to_json
|
|
46
61
|
end
|
|
47
62
|
|
|
48
|
-
post "/tasks/:task_id/done"
|
|
63
|
+
# capturing post "/tasks/:task_id/(update|done)"
|
|
64
|
+
post TASK_UPDATE_REGEXP_PATH do |task_id, _action|
|
|
49
65
|
data = MultiJson.load(request.body.read)
|
|
50
|
-
|
|
66
|
+
dispatch_external_event(task_id, data)
|
|
51
67
|
end
|
|
52
68
|
|
|
53
69
|
get "/tasks/operations" do
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
module SmartProxyDynflowCore
|
|
2
2
|
class BundlerHelper
|
|
3
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
4
3
|
def self.require_groups(*groups)
|
|
5
4
|
if File.exist?(File.expand_path('../../../Gemfile.in', __FILE__))
|
|
6
5
|
# If there is a Gemfile.in file, we will not use Bundler but BundlerExt
|
|
@@ -15,7 +15,9 @@ module SmartProxyDynflowCore
|
|
|
15
15
|
|
|
16
16
|
def create_world(&block)
|
|
17
17
|
config = default_world_config(&block)
|
|
18
|
-
::Dynflow::World.new(config)
|
|
18
|
+
world = ::Dynflow::World.new(config)
|
|
19
|
+
world.middleware.use ::Actions::Middleware::KeepCurrentRequestID
|
|
20
|
+
world
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def persistence_conn_string
|
|
@@ -4,13 +4,14 @@ module SmartProxyDynflowCore
|
|
|
4
4
|
SmartProxyDynflowCore::Core.world
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def authorize_with_token
|
|
7
|
+
def authorize_with_token(task_id:, clear: true)
|
|
8
8
|
if request.env.key? 'HTTP_AUTHORIZATION'
|
|
9
9
|
if defined?(::ForemanTasksCore)
|
|
10
10
|
auth = request.env['HTTP_AUTHORIZATION']
|
|
11
11
|
basic_prefix = /\ABasic /
|
|
12
12
|
if !auth.to_s.empty? && auth =~ basic_prefix &&
|
|
13
|
-
ForemanTasksCore::OtpManager.authenticate(auth.gsub(basic_prefix, '')
|
|
13
|
+
ForemanTasksCore::OtpManager.authenticate(auth.gsub(basic_prefix, ''),
|
|
14
|
+
expected_user: task_id, clear: clear)
|
|
14
15
|
Log.instance.debug('authorized with token')
|
|
15
16
|
return true
|
|
16
17
|
end
|
|
@@ -63,7 +64,7 @@ module SmartProxyDynflowCore
|
|
|
63
64
|
{ :count => tasks.count, :state => state }
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
def
|
|
67
|
+
def dispatch_external_event(task_id, params)
|
|
67
68
|
world.event(task_id,
|
|
68
69
|
params['step_id'].to_i,
|
|
69
70
|
::ForemanTasksCore::Runner::ExternalEvent.new(params))
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
require 'webrick/https'
|
|
2
2
|
require 'smart_proxy_dynflow_core/bundler_helper'
|
|
3
3
|
require 'smart_proxy_dynflow_core/settings'
|
|
4
|
-
require '
|
|
4
|
+
require 'sd_notify'
|
|
5
|
+
|
|
5
6
|
module SmartProxyDynflowCore
|
|
6
7
|
class Launcher
|
|
8
|
+
CIPHERS = ['ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384',
|
|
9
|
+
'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-SHA256',
|
|
10
|
+
'AES256-SHA256', 'AES128-SHA', 'AES256-SHA'].freeze
|
|
11
|
+
|
|
7
12
|
def self.launch!(options)
|
|
8
13
|
self.new.start options
|
|
9
14
|
end
|
|
10
15
|
|
|
11
16
|
def start(options)
|
|
12
|
-
load_settings!(options)
|
|
13
17
|
Settings.instance.standalone = true
|
|
18
|
+
load_settings!(options)
|
|
14
19
|
install_usr1_trap
|
|
15
20
|
Rack::Server.new(rack_settings).start do |_server|
|
|
16
21
|
SmartProxyDynflowCore::Core.ensure_initialized
|
|
22
|
+
::SdNotify.ready
|
|
17
23
|
end
|
|
24
|
+
Log.instance.info "Finished shutting down"
|
|
25
|
+
Logging.shutdown
|
|
18
26
|
end
|
|
19
27
|
|
|
20
28
|
def load_settings!(options = {})
|
|
@@ -47,7 +55,7 @@ module SmartProxyDynflowCore
|
|
|
47
55
|
|
|
48
56
|
def install_usr1_trap
|
|
49
57
|
trap(:USR1) do
|
|
50
|
-
Log.
|
|
58
|
+
Log.reopen
|
|
51
59
|
end
|
|
52
60
|
end
|
|
53
61
|
|
|
@@ -75,15 +83,14 @@ module SmartProxyDynflowCore
|
|
|
75
83
|
:app => app,
|
|
76
84
|
:Host => Settings.instance.listen,
|
|
77
85
|
:Port => Settings.instance.port,
|
|
78
|
-
:AccessLog => [
|
|
86
|
+
:AccessLog => [],
|
|
79
87
|
:Logger => Log.instance,
|
|
80
88
|
:daemonize => Settings.instance.daemonize,
|
|
81
|
-
:pid => Settings.instance.pid_file,
|
|
89
|
+
:pid => Settings.instance.daemonize && Settings.instance.pid_file,
|
|
82
90
|
:server => :webrick
|
|
83
91
|
}
|
|
84
92
|
end
|
|
85
93
|
|
|
86
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
87
94
|
def https_app
|
|
88
95
|
ssl_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
|
89
96
|
ssl_options |= OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE if defined?(OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE)
|
|
@@ -91,17 +98,16 @@ module SmartProxyDynflowCore
|
|
|
91
98
|
ssl_options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
|
|
92
99
|
ssl_options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
|
|
93
100
|
ssl_options |= OpenSSL::SSL::OP_NO_TLSv1 if defined?(OpenSSL::SSL::OP_NO_TLSv1)
|
|
101
|
+
ssl_options |= OpenSSL::SSL::OP_NO_TLSv1_1 if defined?(OpenSSL::SSL::OP_NO_TLSv1_1)
|
|
94
102
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
constant = OpenSSL::SSL.const_get("OP_NO_TLSv#{version.to_s.tr('.', '_')}") rescue nil
|
|
103
|
+
Settings.instance.tls_disabled_versions&.each do |version|
|
|
104
|
+
constant = OpenSSL::SSL.const_get("OP_NO_TLSv#{version.to_s.tr('.', '_')}") rescue nil
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
end
|
|
106
|
+
if constant
|
|
107
|
+
Log.instance.info "TLSv#{version} will be disabled."
|
|
108
|
+
ssl_options |= constant
|
|
109
|
+
else
|
|
110
|
+
Log.instance.warn "TLSv#{version} was not found."
|
|
105
111
|
end
|
|
106
112
|
end
|
|
107
113
|
|
|
@@ -111,6 +117,7 @@ module SmartProxyDynflowCore
|
|
|
111
117
|
:SSLPrivateKey => ssl_private_key,
|
|
112
118
|
:SSLCertificate => ssl_certificate,
|
|
113
119
|
:SSLCACertificateFile => Settings.instance.ssl_ca_file,
|
|
120
|
+
:SSLCiphers => CIPHERS - SmartProxyDynflowCore::Settings.instance.ssl_disabled_ciphers,
|
|
114
121
|
:SSLOptions => ssl_options
|
|
115
122
|
}
|
|
116
123
|
end
|
|
@@ -1,61 +1,126 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'logging'
|
|
2
2
|
|
|
3
3
|
module SmartProxyDynflowCore
|
|
4
|
-
class
|
|
5
|
-
|
|
4
|
+
class ReopenAppender < ::Logging::Appender
|
|
5
|
+
def initialize(name, logger, opts = {})
|
|
6
|
+
@reopen = false
|
|
7
|
+
@logger = logger
|
|
8
|
+
super(name, opts)
|
|
9
|
+
end
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@logger = self.new log_file
|
|
11
|
-
@logger.level = log_level
|
|
12
|
-
end
|
|
13
|
-
@logger
|
|
14
|
-
end
|
|
11
|
+
def set(status = true)
|
|
12
|
+
@reopen = status
|
|
13
|
+
end
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
def append(_event)
|
|
16
|
+
if @reopen
|
|
17
|
+
Logging.reopen
|
|
18
|
+
@reopen = false
|
|
18
19
|
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Log
|
|
24
|
+
BASE_LOG_SIZE = 1024 * 1024 # 1 MiB
|
|
25
|
+
LOGGER_NAME = 'dynflow-core'.freeze
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
require 'syslog/logger'
|
|
29
|
+
@syslog_available = true
|
|
30
|
+
rescue LoadError
|
|
31
|
+
@syslog_available = false
|
|
32
|
+
end
|
|
19
33
|
|
|
34
|
+
class << self
|
|
20
35
|
def reload!
|
|
36
|
+
Logging.logger[LOGGER_NAME].appenders.each(&:close)
|
|
37
|
+
Logging.logger[LOGGER_NAME].clear_appenders
|
|
21
38
|
@logger = nil
|
|
22
39
|
instance
|
|
23
40
|
end
|
|
24
41
|
|
|
25
|
-
def
|
|
26
|
-
if
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Logger::WARN
|
|
42
|
+
def reopen
|
|
43
|
+
return if @logger.nil? || @reopen.nil?
|
|
44
|
+
if Settings.instance.log_file !~ /^(STDOUT|SYSLOG|JOURNALD?)$/i
|
|
45
|
+
@reopen.set
|
|
30
46
|
end
|
|
31
47
|
end
|
|
32
48
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
def instance
|
|
50
|
+
return ::Proxy::LogBuffer::Decorator.instance unless Settings.instance.standalone
|
|
51
|
+
return @logger if @logger
|
|
52
|
+
layout = Logging::Layouts.pattern(pattern: Settings.instance.file_logging_pattern + "\n")
|
|
53
|
+
notime_layout = Logging::Layouts.pattern(pattern: Settings.instance.system_logging_pattern + "\n")
|
|
54
|
+
log_file = Settings.instance.log_file || ''
|
|
55
|
+
@logger = Logging.logger[LOGGER_NAME]
|
|
56
|
+
@reopen = ReopenAppender.new("Reopen dummy appender", @logger)
|
|
57
|
+
@logger.add_appenders(@reopen)
|
|
58
|
+
if !Settings.instance.loaded || log_file.casecmp('STDOUT').zero?
|
|
59
|
+
@logger.add_appenders(Logging.appenders.stdout(LOGGER_NAME, layout: layout))
|
|
60
|
+
elsif log_file.casecmp('SYSLOG').zero?
|
|
61
|
+
unless @syslog_available
|
|
62
|
+
puts "Syslog is not supported on this platform, use STDOUT or a file"
|
|
63
|
+
exit(1)
|
|
64
|
+
end
|
|
65
|
+
@logger.add_appenders(Logging.appenders.syslog(LOGGER_NAME, layout: notime_layout, facility: ::Syslog::Constants::LOG_LOCAL5))
|
|
66
|
+
elsif log_file.casecmp('JOURNAL').zero? || log_file.casecmp('JOURNALD').zero?
|
|
67
|
+
begin
|
|
68
|
+
@logger.add_appenders(Logging.appenders.journald(LOGGER_NAME, LOGGER_NAME: :proxy_logger, layout: notime_layout, facility: ::Syslog::Constants::LOG_LOCAL5))
|
|
69
|
+
rescue NoMethodError
|
|
70
|
+
@logger.add_appenders(Logging.appenders.stdout(LOGGER_NAME, layout: layout))
|
|
71
|
+
@logger.warn "Journald is not available on this platform. Falling back to STDOUT."
|
|
72
|
+
end
|
|
36
73
|
else
|
|
37
|
-
|
|
74
|
+
begin
|
|
75
|
+
keep = Settings.instance.file_rolling_keep
|
|
76
|
+
size = BASE_LOG_SIZE * Settings.instance.file_rolling_size
|
|
77
|
+
age = Settings.instance.file_rolling_age
|
|
78
|
+
if size.positive?
|
|
79
|
+
@logger.add_appenders(Logging.appenders.rolling_file(LOGGER_NAME, layout: layout, filename: log_file, keep: keep, size: size, age: age, roll_by: 'number'))
|
|
80
|
+
else
|
|
81
|
+
@logger.add_appenders(Logging.appenders.file(LOGGER_NAME, layout: layout, filename: log_file))
|
|
82
|
+
end
|
|
83
|
+
rescue ArgumentError => ae
|
|
84
|
+
@logger.add_appenders(Logging.appenders.stdout(LOGGER_NAME, layout: layout))
|
|
85
|
+
@logger.warn "Log file #{log_file} cannot be opened. Falling back to STDOUT: #{ae}"
|
|
86
|
+
end
|
|
38
87
|
end
|
|
88
|
+
@logger.level = ::Logging.level_num(Settings.instance.log_level)
|
|
89
|
+
@logger
|
|
39
90
|
end
|
|
40
|
-
end
|
|
41
91
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
end
|
|
92
|
+
def with_fields(fields = {})
|
|
93
|
+
::Logging.ndc.push(fields) do
|
|
94
|
+
yield
|
|
95
|
+
end
|
|
96
|
+
end
|
|
48
97
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
98
|
+
# Standard way for logging exceptions to get the most data in the log. By default
|
|
99
|
+
# it logs via warn level, this can be changed via options[:level]
|
|
100
|
+
def exception(context_message, exception, options = {})
|
|
101
|
+
level = options[:level] || :warn
|
|
102
|
+
unless ::Logging::LEVELS.keys.include?(level.to_s)
|
|
103
|
+
raise "Unexpected log level #{level}, expected one of #{::Logging::LEVELS.keys}"
|
|
104
|
+
end
|
|
105
|
+
# send class, message and stack as structured fields in addition to message string
|
|
106
|
+
backtrace = exception.backtrace ? exception.backtrace : []
|
|
107
|
+
extra_fields = {
|
|
108
|
+
exception_class: exception.class.name,
|
|
109
|
+
exception_message: exception.message,
|
|
110
|
+
exception_backtrace: backtrace
|
|
111
|
+
}
|
|
112
|
+
extra_fields[:foreman_code] = exception.code if exception.respond_to?(:code)
|
|
113
|
+
with_fields(extra_fields) do
|
|
114
|
+
@logger.public_send(level) do
|
|
115
|
+
([context_message, "#{exception.class}: #{exception.message}"] + backtrace).join("\n")
|
|
116
|
+
end
|
|
117
|
+
end
|
|
53
118
|
end
|
|
54
119
|
end
|
|
55
120
|
|
|
56
121
|
class ProxyStructuredFormater < ::Dynflow::LoggerAdapters::Formatters::Abstract
|
|
57
|
-
def
|
|
58
|
-
if
|
|
122
|
+
def format(message)
|
|
123
|
+
if message.is_a?(Exception)
|
|
59
124
|
subject = "#{message.message} (#{message.class})"
|
|
60
125
|
if @base.respond_to?(:exception)
|
|
61
126
|
@base.exception("Error details", message)
|
|
@@ -64,22 +129,17 @@ module SmartProxyDynflowCore
|
|
|
64
129
|
"#{subject}\n#{message.backtrace.join("\n")}"
|
|
65
130
|
end
|
|
66
131
|
else
|
|
67
|
-
message
|
|
132
|
+
@original_formatter.call(severity, datetime, prog_name, message)
|
|
68
133
|
end
|
|
69
134
|
end
|
|
70
|
-
|
|
71
|
-
def format(message)
|
|
72
|
-
call(nil, nil, nil, message)
|
|
73
|
-
end
|
|
74
135
|
end
|
|
75
136
|
|
|
76
137
|
class ProxyAdapter < ::Dynflow::LoggerAdapters::Simple
|
|
77
|
-
def initialize(logger, level = Logger::DEBUG,
|
|
138
|
+
def initialize(logger, level = Logger::DEBUG, _formatters = [])
|
|
78
139
|
@logger = logger
|
|
79
140
|
@logger.level = level
|
|
80
|
-
@
|
|
81
|
-
@
|
|
82
|
-
@dynflow_logger = apply_formatters(ProgNameWrapper.new(@logger, 'dynflow'), [ProxyStructuredFormater])
|
|
141
|
+
@action_logger = apply_formatters(ProgNameWrapper.new(@logger, ' action'), [])
|
|
142
|
+
@dynflow_logger = apply_formatters(ProgNameWrapper.new(@logger, 'dynflow'), [])
|
|
83
143
|
end
|
|
84
144
|
end
|
|
85
145
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module SmartProxyDynflowCore
|
|
2
|
+
class LoggerMiddleware
|
|
3
|
+
def initialize(app)
|
|
4
|
+
@logger = SmartProxyDynflowCore::Log.instance
|
|
5
|
+
@app = app
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call(env)
|
|
9
|
+
before = Time.now.to_f
|
|
10
|
+
status = 500
|
|
11
|
+
env['rack.logger'] = @logger
|
|
12
|
+
@logger.info { "Started #{env['REQUEST_METHOD']} #{env['PATH_INFO']} #{env['QUERY_STRING']}" }
|
|
13
|
+
@logger.debug { 'Headers: ' + env.select { |k, v| k.start_with? 'HTTP_' }.inspect }
|
|
14
|
+
if @logger.debug? && env['rack.input']
|
|
15
|
+
body = env['rack.input'].read
|
|
16
|
+
@logger.debug('Body: ' + body) unless body.empty?
|
|
17
|
+
env['rack.input'].rewind
|
|
18
|
+
end
|
|
19
|
+
status, = @app.call(env)
|
|
20
|
+
rescue Exception => e
|
|
21
|
+
Log.exception "Error processing request '#{::Logging.mdc['request']}", e
|
|
22
|
+
raise e
|
|
23
|
+
ensure
|
|
24
|
+
@logger.info do
|
|
25
|
+
after = Time.now.to_f
|
|
26
|
+
duration = (after - before) * 1000
|
|
27
|
+
"Finished #{env['REQUEST_METHOD']} #{env['PATH_INFO']} with #{status} (#{duration.round(2)} ms)"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module SmartProxyDynflowCore
|
|
2
|
+
class RequestIdMiddleware
|
|
3
|
+
def initialize(app)
|
|
4
|
+
@app = app
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def call(env)
|
|
8
|
+
::Logging.mdc['remote_ip'] = env['REMOTE_ADDR']
|
|
9
|
+
if env.has_key?('HTTP_X_REQUEST_ID')
|
|
10
|
+
::Logging.mdc['request'] = env['HTTP_X_REQUEST_ID']
|
|
11
|
+
else
|
|
12
|
+
::Logging.mdc['request'] = SecureRandom.uuid
|
|
13
|
+
end
|
|
14
|
+
status, header, body = @app.call(env)
|
|
15
|
+
[status, header, ::Rack::BodyProxy.new(body) { ::Logging.mdc.clear }]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
require 'ostruct'
|
|
2
2
|
|
|
3
|
-
# Implement hash-like access for 1.9.3 and older
|
|
4
|
-
if RUBY_VERSION.split('.').first.to_i < 2
|
|
5
|
-
class OpenStruct
|
|
6
|
-
def [](key)
|
|
7
|
-
self.send key
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def []=(key, value)
|
|
11
|
-
self.send "#{key}=", value
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def to_h
|
|
15
|
-
marshal_dump
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
3
|
module SmartProxyDynflowCore
|
|
21
4
|
class Settings < OpenStruct
|
|
22
5
|
DEFAULT_SETTINGS = {
|
|
@@ -41,7 +24,12 @@ module SmartProxyDynflowCore
|
|
|
41
24
|
:pid_file => '/var/run/foreman-proxy/smart_proxy_dynflow_core.pid',
|
|
42
25
|
:daemonize => false,
|
|
43
26
|
:execution_plan_cleaner_age => 60 * 60 * 24,
|
|
44
|
-
:loaded => false
|
|
27
|
+
:loaded => false,
|
|
28
|
+
:file_logging_pattern => '%d %.8X{request} [%.1l] %m',
|
|
29
|
+
:system_logging_pattern => '%.8X{request} [%.1l] %m',
|
|
30
|
+
:file_rolling_keep => 6,
|
|
31
|
+
:file_rolling_size => 0,
|
|
32
|
+
:file_rolling_age => 'weekly'
|
|
45
33
|
}.freeze
|
|
46
34
|
|
|
47
35
|
PROXY_SETTINGS = %i[ssl_ca_file ssl_certificate ssl_private_key foreman_url
|
|
@@ -73,13 +61,7 @@ module SmartProxyDynflowCore
|
|
|
73
61
|
end
|
|
74
62
|
|
|
75
63
|
def self.load_from_proxy(plugin)
|
|
76
|
-
|
|
77
|
-
plugin
|
|
78
|
-
else
|
|
79
|
-
# DEPRECATION: Remove this branch when dropping support for smart-proxy < 1.16
|
|
80
|
-
plugin[:class]
|
|
81
|
-
end
|
|
82
|
-
settings = plugin_class.settings.to_h
|
|
64
|
+
settings = plugin.settings.to_h
|
|
83
65
|
PROXY_SETTINGS.each do |key|
|
|
84
66
|
SETTINGS[key] = Proxy::SETTINGS[key]
|
|
85
67
|
end
|
|
@@ -2,7 +2,6 @@ lib = File.expand_path('../lib', __FILE__)
|
|
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
require 'smart_proxy_dynflow_core/version'
|
|
4
4
|
|
|
5
|
-
# rubocop:disable Metrics/BlockLength
|
|
6
5
|
Gem::Specification.new do |gem|
|
|
7
6
|
gem.name = "smart_proxy_dynflow_core"
|
|
8
7
|
gem.version = SmartProxyDynflowCore::VERSION
|
|
@@ -22,20 +21,22 @@ Gem::Specification.new do |gem|
|
|
|
22
21
|
gem.require_paths = ["lib"]
|
|
23
22
|
gem.license = 'GPL-3.0'
|
|
24
23
|
|
|
25
|
-
gem.
|
|
24
|
+
gem.required_ruby_version = '~> 2.5'
|
|
25
|
+
|
|
26
|
+
gem.add_development_dependency "bundler", ">= 1.7"
|
|
26
27
|
gem.add_development_dependency('minitest')
|
|
27
28
|
gem.add_development_dependency('mocha', '~> 1')
|
|
28
29
|
gem.add_development_dependency('rack-test', '~> 0')
|
|
29
30
|
gem.add_development_dependency "rake", "~> 10.0"
|
|
30
|
-
gem.add_development_dependency('rubocop', '~> 0.52.1')
|
|
31
31
|
gem.add_development_dependency('webmock', '~> 1')
|
|
32
32
|
|
|
33
33
|
gem.add_runtime_dependency('dynflow', "~> 1.1")
|
|
34
|
-
gem.add_runtime_dependency('foreman-tasks-core', '>= 0.
|
|
34
|
+
gem.add_runtime_dependency('foreman-tasks-core', '>= 0.3.3')
|
|
35
|
+
gem.add_runtime_dependency('logging')
|
|
35
36
|
gem.add_runtime_dependency('rack')
|
|
36
37
|
gem.add_runtime_dependency('rest-client')
|
|
38
|
+
gem.add_runtime_dependency('sd_notify', '~> 0.1')
|
|
37
39
|
gem.add_runtime_dependency('sequel')
|
|
38
40
|
gem.add_runtime_dependency('sinatra')
|
|
39
41
|
gem.add_runtime_dependency('sqlite3')
|
|
40
42
|
end
|
|
41
|
-
# rubocop:enable Metrics/BlockLength
|
metadata
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smart_proxy_dynflow_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.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: 1980-01-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '1.7'
|
|
20
20
|
type: :development
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '1.7'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
@@ -81,61 +81,61 @@ dependencies:
|
|
|
81
81
|
- !ruby/object:Gem::Version
|
|
82
82
|
version: '10.0'
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
84
|
+
name: webmock
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
87
|
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version:
|
|
89
|
+
version: '1'
|
|
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:
|
|
96
|
+
version: '1'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
|
-
name:
|
|
98
|
+
name: dynflow
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
100
100
|
requirements:
|
|
101
101
|
- - "~>"
|
|
102
102
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '1'
|
|
104
|
-
type: :
|
|
103
|
+
version: '1.1'
|
|
104
|
+
type: :runtime
|
|
105
105
|
prerelease: false
|
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
107
107
|
requirements:
|
|
108
108
|
- - "~>"
|
|
109
109
|
- !ruby/object:Gem::Version
|
|
110
|
-
version: '1'
|
|
110
|
+
version: '1.1'
|
|
111
111
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
112
|
+
name: foreman-tasks-core
|
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
|
114
114
|
requirements:
|
|
115
|
-
- - "
|
|
115
|
+
- - ">="
|
|
116
116
|
- !ruby/object:Gem::Version
|
|
117
|
-
version:
|
|
117
|
+
version: 0.3.3
|
|
118
118
|
type: :runtime
|
|
119
119
|
prerelease: false
|
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
121
|
requirements:
|
|
122
|
-
- - "
|
|
122
|
+
- - ">="
|
|
123
123
|
- !ruby/object:Gem::Version
|
|
124
|
-
version:
|
|
124
|
+
version: 0.3.3
|
|
125
125
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
126
|
+
name: logging
|
|
127
127
|
requirement: !ruby/object:Gem::Requirement
|
|
128
128
|
requirements:
|
|
129
129
|
- - ">="
|
|
130
130
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: 0
|
|
131
|
+
version: '0'
|
|
132
132
|
type: :runtime
|
|
133
133
|
prerelease: false
|
|
134
134
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
135
|
requirements:
|
|
136
136
|
- - ">="
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: 0
|
|
138
|
+
version: '0'
|
|
139
139
|
- !ruby/object:Gem::Dependency
|
|
140
140
|
name: rack
|
|
141
141
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -164,6 +164,20 @@ dependencies:
|
|
|
164
164
|
- - ">="
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
166
|
version: '0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: sd_notify
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '0.1'
|
|
174
|
+
type: :runtime
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '0.1'
|
|
167
181
|
- !ruby/object:Gem::Dependency
|
|
168
182
|
name: sequel
|
|
169
183
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -228,34 +242,34 @@ files:
|
|
|
228
242
|
- lib/smart_proxy_dynflow_core/helpers.rb
|
|
229
243
|
- lib/smart_proxy_dynflow_core/launcher.rb
|
|
230
244
|
- lib/smart_proxy_dynflow_core/log.rb
|
|
245
|
+
- lib/smart_proxy_dynflow_core/logger_middleware.rb
|
|
246
|
+
- lib/smart_proxy_dynflow_core/request_id_middleware.rb
|
|
231
247
|
- lib/smart_proxy_dynflow_core/settings.rb
|
|
232
248
|
- lib/smart_proxy_dynflow_core/task_launcher_registry.rb
|
|
233
249
|
- lib/smart_proxy_dynflow_core/testing.rb
|
|
234
250
|
- lib/smart_proxy_dynflow_core/version.rb
|
|
235
|
-
- lib/smart_proxy_dynflow_core/webrick-patch.rb
|
|
236
251
|
- smart_proxy_dynflow_core.gemspec
|
|
237
252
|
homepage: https://github.com/theforeman/smart_proxy_dynflow
|
|
238
253
|
licenses:
|
|
239
254
|
- GPL-3.0
|
|
240
255
|
metadata: {}
|
|
241
|
-
post_install_message:
|
|
256
|
+
post_install_message:
|
|
242
257
|
rdoc_options: []
|
|
243
258
|
require_paths:
|
|
244
259
|
- lib
|
|
245
260
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
246
261
|
requirements:
|
|
247
|
-
- - "
|
|
262
|
+
- - "~>"
|
|
248
263
|
- !ruby/object:Gem::Version
|
|
249
|
-
version: '
|
|
264
|
+
version: '2.5'
|
|
250
265
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
266
|
requirements:
|
|
252
267
|
- - ">="
|
|
253
268
|
- !ruby/object:Gem::Version
|
|
254
269
|
version: '0'
|
|
255
270
|
requirements: []
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
signing_key:
|
|
271
|
+
rubygems_version: 3.1.2
|
|
272
|
+
signing_key:
|
|
259
273
|
specification_version: 4
|
|
260
274
|
summary: Dynflow runtime for Foreman smart proxy
|
|
261
275
|
test_files: []
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
require 'webrick/https'
|
|
2
|
-
|
|
3
|
-
CIPHERS = ['ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384',
|
|
4
|
-
'ECDHE-RSA-AES128-CBC-SHA', 'ECDHE-RSA-AES256-CBC-SHA',
|
|
5
|
-
'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'AES128-SHA256',
|
|
6
|
-
'AES256-SHA256', 'AES128-SHA', 'AES256-SHA'].freeze
|
|
7
|
-
|
|
8
|
-
module WEBrick
|
|
9
|
-
class GenericServer
|
|
10
|
-
# rubocop:disable Metrics/AbcSize
|
|
11
|
-
def setup_ssl_context(config) # :nodoc:
|
|
12
|
-
unless config[:SSLCertificate]
|
|
13
|
-
cn = config[:SSLCertName]
|
|
14
|
-
comment = config[:SSLCertComment]
|
|
15
|
-
cert, key = Utils.create_self_signed_cert(1024, cn, comment)
|
|
16
|
-
config[:SSLCertificate] = cert
|
|
17
|
-
config[:SSLPrivateKey] = key
|
|
18
|
-
end
|
|
19
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
|
20
|
-
ctx.set_params
|
|
21
|
-
ctx.ciphers = (CIPHERS - SmartProxyDynflowCore::Settings.instance.ssl_disabled_ciphers).join(':')
|
|
22
|
-
ctx.key = config[:SSLPrivateKey]
|
|
23
|
-
ctx.cert = config[:SSLCertificate]
|
|
24
|
-
ctx.client_ca = config[:SSLClientCA]
|
|
25
|
-
ctx.extra_chain_cert = config[:SSLExtraChainCert]
|
|
26
|
-
ctx.ca_file = config[:SSLCACertificateFile]
|
|
27
|
-
ctx.ca_path = config[:SSLCACertificatePath]
|
|
28
|
-
ctx.cert_store = config[:SSLCertificateStore]
|
|
29
|
-
ctx.tmp_dh_callback = config[:SSLTmpDhCallback]
|
|
30
|
-
ctx.verify_mode = config[:SSLVerifyClient]
|
|
31
|
-
ctx.verify_depth = config[:SSLVerifyDepth]
|
|
32
|
-
ctx.verify_callback = config[:SSLVerifyCallback]
|
|
33
|
-
ctx.timeout = config[:SSLTimeout]
|
|
34
|
-
ctx.options |= config[:SSLOptions] unless config[:SSLOptions].nil?
|
|
35
|
-
ctx
|
|
36
|
-
end
|
|
37
|
-
# rubocop:enable Metrics/AbcSize
|
|
38
|
-
end
|
|
39
|
-
end
|