smart_proxy_dynflow_core 0.3.0 → 0.4.1
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 +1 -6
- data/lib/smart_proxy_dynflow_core.rb +9 -15
- data/lib/smart_proxy_dynflow_core/task_launcher_registry.rb +1 -29
- data/lib/smart_proxy_dynflow_core/version.rb +1 -1
- data/smart_proxy_dynflow_core.gemspec +3 -19
- metadata +6 -204
- data/bin/smart_proxy_dynflow_core +0 -32
- data/config/settings.yml.example +0 -66
- data/deploy/smart_proxy_dynflow_core.init +0 -92
- data/deploy/smart_proxy_dynflow_core.service +0 -13
- data/lib/smart_proxy_dynflow_core/api.rb +0 -89
- data/lib/smart_proxy_dynflow_core/bundler_helper.rb +0 -30
- data/lib/smart_proxy_dynflow_core/callback.rb +0 -85
- data/lib/smart_proxy_dynflow_core/core.rb +0 -124
- data/lib/smart_proxy_dynflow_core/helpers.rb +0 -73
- data/lib/smart_proxy_dynflow_core/launcher.rb +0 -166
- data/lib/smart_proxy_dynflow_core/log.rb +0 -146
- data/lib/smart_proxy_dynflow_core/logger_middleware.rb +0 -31
- data/lib/smart_proxy_dynflow_core/request_id_middleware.rb +0 -18
- data/lib/smart_proxy_dynflow_core/settings.rb +0 -86
- data/lib/smart_proxy_dynflow_core/testing.rb +0 -28
@@ -1,166 +0,0 @@
|
|
1
|
-
require 'webrick/https'
|
2
|
-
require 'smart_proxy_dynflow_core/bundler_helper'
|
3
|
-
require 'smart_proxy_dynflow_core/settings'
|
4
|
-
require 'sd_notify'
|
5
|
-
|
6
|
-
module SmartProxyDynflowCore
|
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
|
-
|
12
|
-
def self.launch!(options)
|
13
|
-
self.new.start options
|
14
|
-
end
|
15
|
-
|
16
|
-
def start(options)
|
17
|
-
Settings.instance.standalone = true
|
18
|
-
load_settings!(options)
|
19
|
-
install_usr1_trap
|
20
|
-
Rack::Server.new(rack_settings).start do |_server|
|
21
|
-
SmartProxyDynflowCore::Core.ensure_initialized
|
22
|
-
::SdNotify.ready
|
23
|
-
end
|
24
|
-
Log.instance.info "Finished shutting down"
|
25
|
-
Logging.shutdown
|
26
|
-
end
|
27
|
-
|
28
|
-
def load_settings!(options = {})
|
29
|
-
config_dir, one_config = options.values_at(:config_dir, :one_config)
|
30
|
-
possible_config_dirs = [
|
31
|
-
'/etc/smart_proxy_dynflow_core',
|
32
|
-
File.expand_path('~/.config/smart_proxy_dynflow_core'),
|
33
|
-
File.join(File.dirname(__FILE__), '..', '..', 'config'),
|
34
|
-
]
|
35
|
-
possible_config_dirs << config_dir if config_dir
|
36
|
-
BundlerHelper.require_groups(:default)
|
37
|
-
possible_config_dirs.reverse! if one_config
|
38
|
-
possible_config_dirs.select { |config_dir| File.directory? config_dir }.each do |config_dir|
|
39
|
-
break if load_config_dir(config_dir) && one_config
|
40
|
-
end
|
41
|
-
Settings.instance.daemonize = options[:daemonize] if options.key?(:daemonize)
|
42
|
-
Settings.instance.pid_file = options[:pid_file] if options.key?(:pid_file)
|
43
|
-
Settings.loaded!
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.route_mapping(rack_builder)
|
47
|
-
rack_builder.map '/console' do
|
48
|
-
run Core.web_console
|
49
|
-
end
|
50
|
-
|
51
|
-
rack_builder.map '/' do
|
52
|
-
run Api
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def install_usr1_trap
|
57
|
-
trap(:USR1) do
|
58
|
-
Log.reopen
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
private
|
63
|
-
|
64
|
-
def rack_settings
|
65
|
-
settings = if https_enabled?
|
66
|
-
Log.instance.debug "Using HTTPS"
|
67
|
-
https_app
|
68
|
-
else
|
69
|
-
Log.instance.debug "Using HTTP"
|
70
|
-
{}
|
71
|
-
end
|
72
|
-
settings.merge(base_settings)
|
73
|
-
end
|
74
|
-
|
75
|
-
def app
|
76
|
-
Rack::Builder.new do
|
77
|
-
SmartProxyDynflowCore::Launcher.route_mapping(self)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def base_settings
|
82
|
-
{
|
83
|
-
:app => app,
|
84
|
-
:Host => Settings.instance.listen,
|
85
|
-
:Port => Settings.instance.port,
|
86
|
-
:AccessLog => [],
|
87
|
-
:Logger => Log.instance,
|
88
|
-
:daemonize => Settings.instance.daemonize,
|
89
|
-
:pid => Settings.instance.daemonize && Settings.instance.pid_file,
|
90
|
-
:server => :webrick
|
91
|
-
}
|
92
|
-
end
|
93
|
-
|
94
|
-
def https_app
|
95
|
-
ssl_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
96
|
-
ssl_options |= OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE if defined?(OpenSSL::SSL::OP_CIPHER_SERVER_PREFERENCE)
|
97
|
-
# This is required to disable SSLv3 on Ruby 1.8.7
|
98
|
-
ssl_options |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
|
99
|
-
ssl_options |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
|
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)
|
102
|
-
|
103
|
-
Settings.instance.tls_disabled_versions&.each do |version|
|
104
|
-
constant = OpenSSL::SSL.const_get("OP_NO_TLSv#{version.to_s.tr('.', '_')}") rescue nil
|
105
|
-
|
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."
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
{
|
115
|
-
:SSLEnable => true,
|
116
|
-
:SSLVerifyClient => OpenSSL::SSL::VERIFY_PEER,
|
117
|
-
:SSLPrivateKey => ssl_private_key,
|
118
|
-
:SSLCertificate => ssl_certificate,
|
119
|
-
:SSLCACertificateFile => Settings.instance.ssl_ca_file,
|
120
|
-
:SSLCiphers => CIPHERS - SmartProxyDynflowCore::Settings.instance.ssl_disabled_ciphers,
|
121
|
-
:SSLOptions => ssl_options
|
122
|
-
}
|
123
|
-
end
|
124
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
125
|
-
|
126
|
-
def https_enabled?
|
127
|
-
Settings.instance.use_https
|
128
|
-
end
|
129
|
-
|
130
|
-
def ssl_private_key
|
131
|
-
OpenSSL::PKey::RSA.new(File.read(Settings.instance.ssl_private_key))
|
132
|
-
rescue Exception => e
|
133
|
-
Log.instance.fatal "Unable to load private SSL key. Are the values "\
|
134
|
-
"correct in settings.yml and do permissions allow reading?: #{e}"
|
135
|
-
raise e
|
136
|
-
end
|
137
|
-
|
138
|
-
def ssl_certificate
|
139
|
-
OpenSSL::X509::Certificate.new(File.read(Settings.instance.ssl_certificate))
|
140
|
-
rescue Exception => e
|
141
|
-
Log.instance.fatal "Unable to load SSL certificate. Are the values " \
|
142
|
-
"correct in settings.yml and do permissions allow reading?: #{e}"
|
143
|
-
raise e
|
144
|
-
end
|
145
|
-
|
146
|
-
def load_config_dir(dir)
|
147
|
-
settings_yml = File.join(dir, 'settings.yml')
|
148
|
-
if File.exist? settings_yml
|
149
|
-
Log.instance.debug "Loading settings from #{dir}"
|
150
|
-
Settings.load_global_settings settings_yml
|
151
|
-
Dir[File.join(dir, 'settings.d', '*.yml')].each { |path| Settings.load_plugin_settings(path) }
|
152
|
-
true
|
153
|
-
end
|
154
|
-
ForemanTasksCore::SettingsLoader.settings_registry.each_key do |settings_keys|
|
155
|
-
settings = settings_keys.inject({}) do |h, settings_key|
|
156
|
-
if SETTINGS.plugins.key?(settings_key.to_s)
|
157
|
-
h.merge(SETTINGS.plugins[settings_key.to_s].to_h)
|
158
|
-
else
|
159
|
-
h
|
160
|
-
end
|
161
|
-
end
|
162
|
-
ForemanTasksCore::SettingsLoader.setup_settings(settings_keys.first, settings)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
@@ -1,146 +0,0 @@
|
|
1
|
-
require 'logging'
|
2
|
-
|
3
|
-
module SmartProxyDynflowCore
|
4
|
-
class ReopenAppender < ::Logging::Appender
|
5
|
-
def initialize(name, logger, opts = {})
|
6
|
-
@reopen = false
|
7
|
-
@logger = logger
|
8
|
-
super(name, opts)
|
9
|
-
end
|
10
|
-
|
11
|
-
def set(status = true)
|
12
|
-
@reopen = status
|
13
|
-
end
|
14
|
-
|
15
|
-
def append(_event)
|
16
|
-
if @reopen
|
17
|
-
Logging.reopen
|
18
|
-
@reopen = false
|
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
|
33
|
-
|
34
|
-
class << self
|
35
|
-
def reload!
|
36
|
-
Logging.logger[LOGGER_NAME].appenders.each(&:close)
|
37
|
-
Logging.logger[LOGGER_NAME].clear_appenders
|
38
|
-
@logger = nil
|
39
|
-
instance
|
40
|
-
end
|
41
|
-
|
42
|
-
def reopen
|
43
|
-
return if @logger.nil? || @reopen.nil?
|
44
|
-
if Settings.instance.log_file !~ /^(STDOUT|SYSLOG|JOURNALD?)$/i
|
45
|
-
@reopen.set
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
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
|
73
|
-
else
|
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
|
87
|
-
end
|
88
|
-
@logger.level = ::Logging.level_num(Settings.instance.log_level)
|
89
|
-
@logger
|
90
|
-
end
|
91
|
-
|
92
|
-
def with_fields(fields = {})
|
93
|
-
::Logging.ndc.push(fields) do
|
94
|
-
yield
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
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
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
class ProxyStructuredFormater < ::Dynflow::LoggerAdapters::Formatters::Abstract
|
122
|
-
def format(message)
|
123
|
-
if message.is_a?(Exception)
|
124
|
-
subject = "#{message.message} (#{message.class})"
|
125
|
-
if @base.respond_to?(:exception)
|
126
|
-
@base.exception("Error details", message)
|
127
|
-
subject
|
128
|
-
else
|
129
|
-
"#{subject}\n#{message.backtrace.join("\n")}"
|
130
|
-
end
|
131
|
-
else
|
132
|
-
@original_formatter.call(severity, datetime, prog_name, message)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
class ProxyAdapter < ::Dynflow::LoggerAdapters::Simple
|
138
|
-
def initialize(logger, level = Logger::DEBUG, _formatters = [])
|
139
|
-
@logger = logger
|
140
|
-
@logger.level = level
|
141
|
-
@action_logger = apply_formatters(ProgNameWrapper.new(@logger, ' action'), [])
|
142
|
-
@dynflow_logger = apply_formatters(ProgNameWrapper.new(@logger, 'dynflow'), [])
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
@@ -1,31 +0,0 @@
|
|
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
|
@@ -1,18 +0,0 @@
|
|
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,86 +0,0 @@
|
|
1
|
-
require 'ostruct'
|
2
|
-
|
3
|
-
module SmartProxyDynflowCore
|
4
|
-
class Settings < OpenStruct
|
5
|
-
DEFAULT_SETTINGS = {
|
6
|
-
:database => '/var/lib/foreman-proxy/dynflow/dynflow.sqlite',
|
7
|
-
:foreman_url => 'https://127.0.0.1:3000',
|
8
|
-
:console_auth => true,
|
9
|
-
:listen => '127.0.0.1',
|
10
|
-
:port => '8008',
|
11
|
-
:use_https => false,
|
12
|
-
:ssl_ca_file => nil,
|
13
|
-
:ssl_private_key => nil,
|
14
|
-
:ssl_certificate => nil,
|
15
|
-
:ssl_disabled_ciphers => [],
|
16
|
-
:tls_disabled_versions => [],
|
17
|
-
:foreman_ssl_ca => nil,
|
18
|
-
:foreman_ssl_key => nil,
|
19
|
-
:foreman_ssl_cert => nil,
|
20
|
-
:standalone => false,
|
21
|
-
:log_file => '/var/log/foreman-proxy/smart_proxy_dynflow_core.log',
|
22
|
-
:log_level => :ERROR,
|
23
|
-
:plugins => {},
|
24
|
-
:pid_file => '/var/run/foreman-proxy/smart_proxy_dynflow_core.pid',
|
25
|
-
:daemonize => false,
|
26
|
-
:execution_plan_cleaner_age => 60 * 60 * 24,
|
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'
|
33
|
-
}.freeze
|
34
|
-
|
35
|
-
PROXY_SETTINGS = %i[ssl_ca_file ssl_certificate ssl_private_key foreman_url
|
36
|
-
foreman_ssl_ca foreman_ssl_cert foreman_ssl_key
|
37
|
-
log_file log_level ssl_disabled_ciphers].freeze
|
38
|
-
PLUGIN_SETTINGS = %i[database core_url console_auth
|
39
|
-
execution_plan_cleaner_age].freeze
|
40
|
-
|
41
|
-
def initialize(settings = {})
|
42
|
-
super(DEFAULT_SETTINGS.merge(settings))
|
43
|
-
end
|
44
|
-
|
45
|
-
def self.instance
|
46
|
-
SmartProxyDynflowCore::SETTINGS
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.load_global_settings(path)
|
50
|
-
if File.exist? File.join(path)
|
51
|
-
YAML.load_file(path).each do |key, value|
|
52
|
-
SETTINGS[key] = value
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def self.loaded!
|
58
|
-
Settings.instance.loaded = true
|
59
|
-
Log.instance.info 'Settings loaded, reloading logger'
|
60
|
-
Log.reload!
|
61
|
-
end
|
62
|
-
|
63
|
-
def self.load_from_proxy(plugin)
|
64
|
-
settings = plugin.settings.to_h
|
65
|
-
PROXY_SETTINGS.each do |key|
|
66
|
-
SETTINGS[key] = Proxy::SETTINGS[key]
|
67
|
-
end
|
68
|
-
PLUGIN_SETTINGS.each do |key|
|
69
|
-
SETTINGS[key] = settings[key] if settings.key?(key)
|
70
|
-
end
|
71
|
-
SETTINGS.plugins.values.each(&:load_settings_from_proxy)
|
72
|
-
Settings.loaded!
|
73
|
-
end
|
74
|
-
|
75
|
-
def self.load_plugin_settings(path)
|
76
|
-
settings = YAML.load_file(path)
|
77
|
-
name = File.basename(path).gsub(/\.yml$/, '')
|
78
|
-
if SETTINGS.plugins.key? name
|
79
|
-
settings = SETTINGS.plugins[name].to_h.merge(settings)
|
80
|
-
end
|
81
|
-
SETTINGS.plugins[name] = OpenStruct.new settings
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
SmartProxyDynflowCore::SETTINGS = SmartProxyDynflowCore::Settings.new
|