smart_proxy_dynflow_core 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,92 +0,0 @@
1
- #!/bin/bash
2
- #
3
- # Init script for foreman smart proxy dynflow core service
4
- #
5
- # chkconfig: - 85 15
6
- # description: Init script for foreman proxy dynflow core service
7
-
8
- # Source function library.
9
- . /etc/rc.d/init.d/functions
10
-
11
- prog=smart_proxy_dynflow_core
12
- RETVAL=0
13
- SMART_PROXY_DYNFLOW_CORE_PID=/var/run/foreman-proxy/$prog.pid
14
- SMART_PROXY_DYNFLOW_CORE_USER=${SMART_PROXY_DYNFLOW_CORE_USER:-foreman-proxy}
15
-
16
- start() {
17
- echo -n $"Starting $prog: "
18
- ulimit -n 65536
19
- daemon --user ${SMART_PROXY_DYNFLOW_CORE_USER} /usr/bin/smart_proxy_dynflow_core -d -p $SMART_PROXY_DYNFLOW_CORE_PID > /dev/null
20
- RETVAL=$?
21
- if [ $RETVAL = 0 ]
22
- then
23
- echo_success
24
- else
25
- echo_failure
26
- fi
27
-
28
- echo
29
- return $RETVAL
30
- }
31
-
32
- stop() {
33
- echo -n $"Stopping $prog: "
34
- if [ -f ${SMART_PROXY_DYNFLOW_CORE_PID} ]; then
35
- killproc -p ${SMART_PROXY_DYNFLOW_CORE_PID}
36
- RETVAL=$?
37
- else
38
- echo -n $"$prog was not running.";
39
- failure $"$prog was not running.";
40
- echo
41
- return 1
42
- fi
43
- echo
44
- [ $RETVAL -eq 0 ] && rm -f ${SMART_PROXY_DYNFLOW_CORE_PID}
45
- return $RETVAL
46
- }
47
-
48
- logrotate() {
49
- echo -n $"Rotating logs for $prog: "
50
- if [ -f ${SMART_PROXY_DYNFLOW_CORE_PID} ]; then
51
- killproc -p ${SMART_PROXY_DYNFLOW_CORE_PID} $prog -USR1
52
- RETVAL=$?
53
- echo
54
- else
55
- echo -n $"$prog was not running.";
56
- failure $"$prog was not running.";
57
- echo
58
- return 1
59
- fi
60
- return $RETVAL
61
- }
62
-
63
- # See how we were called.
64
- case "$1" in
65
- start)
66
- start
67
- ;;
68
- stop)
69
- stop
70
- ;;
71
- status)
72
- echo -n "$prog"
73
- status -p $SMART_PROXY_DYNFLOW_CORE_PID
74
- RETVAL=$?
75
- ;;
76
- restart)
77
- stop
78
- start
79
- ;;
80
- condrestart)
81
- stop
82
- [ $? -eq 0 ] && start
83
- ;;
84
- logrotate)
85
- logrotate
86
- ;;
87
- *)
88
- echo $"Usage: $prog {start|stop|restart|condrestart|logrotate}"
89
- exit 1
90
- esac
91
-
92
- exit $RETVAL
@@ -1,13 +0,0 @@
1
- [Unit]
2
- Description=Foreman smart proxy dynflow core service
3
- Documentation=https://github.com/theforeman/smart_proxy_dynflow
4
- After=network.target remote-fs.target nss-lookup.target
5
-
6
- [Service]
7
- Type=notify
8
- User=foreman-proxy
9
- ExecStart=/usr/bin/smart_proxy_dynflow_core --no-daemonize
10
- EnvironmentFile=-/etc/sysconfig/smart_proxy_dynflow_core
11
-
12
- [Install]
13
- WantedBy=multi-user.target
@@ -1,89 +0,0 @@
1
- require 'sinatra/base'
2
- require 'multi_json'
3
-
4
- module SmartProxyDynflowCore
5
- class Api < ::Sinatra::Base
6
- TASK_UPDATE_REGEXP_PATH = %r{/tasks/(\S+)/(update|done)}
7
- helpers Helpers
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
-
17
- before do
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
25
- content_type :json
26
- end
27
-
28
- post "/tasks/status" do
29
- params = MultiJson.load(request.body.read)
30
- ids = params.fetch('task_ids', [])
31
- result = world.persistence
32
- .find_execution_plans(:filters => { :uuid => ids }).reduce({}) do |acc, plan|
33
- acc.update(plan.id => { 'state' => plan.state, 'result' => plan.result })
34
- end
35
- MultiJson.dump(result)
36
- end
37
-
38
- post "/tasks/launch/?" do
39
- params = MultiJson.load(request.body.read)
40
- launcher = launcher_class(params).new(world, callback_host(params, request), params.fetch('options', {}))
41
- launcher.launch!(params['input'])
42
- launcher.results.to_json
43
- end
44
-
45
- post "/tasks/?" do
46
- params = MultiJson.load(request.body.read)
47
- trigger_task(::Dynflow::Utils.constantize(params['action_name']),
48
- params['action_input'].merge(:callback_host => callback_host(params, request))).to_json
49
- end
50
-
51
- post "/tasks/:task_id/cancel" do |task_id|
52
- cancel_task(task_id).to_json
53
- end
54
-
55
- get "/tasks/:task_id/status" do |task_id|
56
- task_status(task_id).to_json
57
- end
58
-
59
- get "/tasks/count" do
60
- tasks_count(params['state']).to_json
61
- end
62
-
63
- # capturing post "/tasks/:task_id/(update|done)"
64
- post TASK_UPDATE_REGEXP_PATH do |task_id, _action|
65
- data = MultiJson.load(request.body.read)
66
- dispatch_external_event(task_id, data)
67
- end
68
-
69
- get "/tasks/operations" do
70
- TaskLauncherRegistry.operations.to_json
71
- end
72
-
73
- private
74
-
75
- def callback_host(params, request)
76
- params.fetch('action_input', {})['proxy_url'] ||
77
- request.env.values_at('HTTP_X_FORWARDED_FOR', 'HTTP_HOST').compact.first
78
- end
79
-
80
- def launcher_class(params)
81
- operation = params.fetch('operation')
82
- if TaskLauncherRegistry.key?(operation)
83
- TaskLauncherRegistry.fetch(operation)
84
- else
85
- halt 404, MultiJson.dump(:error => "Unknown operation '#{operation}' requested.")
86
- end
87
- end
88
- end
89
- end
@@ -1,30 +0,0 @@
1
- module SmartProxyDynflowCore
2
- class BundlerHelper
3
- def self.require_groups(*groups)
4
- if File.exist?(File.expand_path('../../../Gemfile.in', __FILE__))
5
- # If there is a Gemfile.in file, we will not use Bundler but BundlerExt
6
- # gem which parses this file and loads all dependencies from the system
7
- # rathern then trying to download them from rubygems.org. It always
8
- # loads all gemfile groups.
9
- begin
10
- require 'bundler_ext' unless defined?(BundlerExt)
11
- rescue LoadError
12
- # Debian packaging guidelines state to avoid needing rubygems, so
13
- # we only try to load it if the first require fails (for RPMs)
14
- begin
15
- require 'rubygems' rescue nil
16
- require 'bundler_ext'
17
- rescue LoadError
18
- puts "`bundler_ext` gem is required to run smart_proxy"
19
- exit 1
20
- end
21
- end
22
- BundlerExt.system_require(File.expand_path('../../../Gemfile.in', __FILE__), *groups)
23
- else
24
- require 'bundler' unless defined?(Bundler)
25
- Bundler.require(*groups)
26
- end
27
- end
28
- # rubocop:enable Metrics/PerceivedComplexity
29
- end
30
- end
@@ -1,85 +0,0 @@
1
- require 'rest-client'
2
-
3
- # rubocop:disable Lint/HandleExceptions
4
- begin
5
- require 'smart_proxy_dynflow/callback'
6
- rescue LoadError
7
- end
8
- # rubocop:enable Lint/HandleExceptions
9
-
10
- module SmartProxyDynflowCore
11
- module Callback
12
- class Request
13
- class << self
14
- def send_to_foreman_tasks(callback_info, data)
15
- self.new.callback(prepare_payload(callback_info, data))
16
- end
17
-
18
- def ssl_options
19
- return @ssl_options if defined? @ssl_options
20
- @ssl_options = {}
21
- settings = Settings.instance
22
- return @ssl_options unless URI.parse(settings.foreman_url).scheme == 'https'
23
-
24
- @ssl_options[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
25
-
26
- private_key_file = settings.foreman_ssl_key || settings.ssl_private_key
27
- if private_key_file
28
- private_key = File.read(private_key_file)
29
- @ssl_options[:ssl_client_key] = OpenSSL::PKey::RSA.new(private_key)
30
- end
31
- certificate_file = settings.foreman_ssl_cert || settings.ssl_certificate
32
- if certificate_file
33
- certificate = File.read(certificate_file)
34
- @ssl_options[:ssl_client_cert] = OpenSSL::X509::Certificate.new(certificate)
35
- end
36
- ca_file = settings.foreman_ssl_ca || settings.ssl_ca_file
37
- @ssl_options[:ssl_ca_file] = ca_file if ca_file
38
- @ssl_options
39
- end
40
- # rubocop:enable Metrics/PerceivedComplexity
41
-
42
- private
43
-
44
- def prepare_payload(callback, data)
45
- { :callback => callback, :data => data }.to_json
46
- end
47
- end
48
-
49
- def callback(payload)
50
- response = callback_resource.post(payload, :content_type => :json)
51
- if response.code.to_s != "200"
52
- raise "Failed performing callback to Foreman server: #{response.code} #{response.body}"
53
- end
54
- response
55
- end
56
-
57
- private
58
-
59
- def callback_resource
60
- @resource ||= RestClient::Resource.new(Settings.instance.foreman_url + '/foreman_tasks/api/tasks/callback',
61
- self.class.ssl_options)
62
- end
63
- end
64
-
65
- class Action < ::Dynflow::Action
66
- def plan(callback, data)
67
- plan_self(:callback => callback, :data => data)
68
- end
69
-
70
- def run
71
- Callback::Request.send_to_foreman_tasks(input[:callback], input[:data])
72
- end
73
- end
74
-
75
- module PlanHelper
76
- def plan_with_callback(input)
77
- input = input.dup
78
- callback = input.delete('callback')
79
-
80
- planned_action = plan_self(input)
81
- plan_action(Callback::Action, callback, planned_action.output) if callback
82
- end
83
- end
84
- end
85
- end
@@ -1,124 +0,0 @@
1
- module SmartProxyDynflowCore
2
- class Core
3
- attr_accessor :world, :accepted_cert_serial
4
-
5
- def initialize
6
- @world = create_world
7
- cert_file = Settings.instance.foreman_ssl_cert || Settings.instance.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
- if Settings.instance.standalone
57
- Log::ProxyAdapter.new(Log.instance, Log.instance.level)
58
- else
59
- Log::ProxyAdapter.new(Proxy::LogBuffer::Decorator.instance, Log.instance.level)
60
- end
61
- end
62
-
63
- def execution_plan_cleaner
64
- proc do |world|
65
- age = Settings.instance.execution_plan_cleaner_age
66
- options = { :poll_interval => age, :max_age => age }
67
- ::Dynflow::Actors::ExecutionPlanCleaner.new(world, options)
68
- end
69
- end
70
-
71
- class << self
72
- attr_reader :instance
73
-
74
- def ensure_initialized
75
- return @instance if @instance
76
- @instance = Core.new
77
- after_initialize_blocks.each { |block| block.call(@instance) }
78
- @instance
79
- end
80
-
81
- def silencer_matchers
82
- @matchers ||= []
83
- end
84
-
85
- def register_silencer_matchers(matchers)
86
- silencer_matchers.concat matchers
87
- end
88
-
89
- def web_console
90
- require 'dynflow/web'
91
- dynflow_console = ::Dynflow::Web.setup do
92
- # we can't use the proxy's after_activation hook, as
93
- # it happens before the Daemon forks the process (including
94
- # closing opened file descriptors)
95
- # TODO: extend smart proxy to enable hooks that happen after
96
- # the forking
97
- helpers Helpers
98
-
99
- before do
100
- authorize_with_ssl_client if Settings.instance.console_auth
101
- end
102
-
103
- Core.ensure_initialized
104
- set :world, Core.world
105
- end
106
- dynflow_console
107
- end
108
-
109
- def world
110
- instance.world
111
- end
112
-
113
- def after_initialize(&block)
114
- after_initialize_blocks << block
115
- end
116
-
117
- private
118
-
119
- def after_initialize_blocks
120
- @after_initialize_blocks ||= []
121
- end
122
- end
123
- end
124
- end
@@ -1,73 +0,0 @@
1
- module SmartProxyDynflowCore
2
- module Helpers
3
- def world
4
- SmartProxyDynflowCore::Core.world
5
- end
6
-
7
- def authorize_with_token(task_id:, clear: true)
8
- if request.env.key? 'HTTP_AUTHORIZATION'
9
- if defined?(::ForemanTasksCore)
10
- auth = request.env['HTTP_AUTHORIZATION']
11
- basic_prefix = /\ABasic /
12
- if !auth.to_s.empty? && auth =~ basic_prefix &&
13
- ForemanTasksCore::OtpManager.authenticate(auth.gsub(basic_prefix, ''),
14
- expected_user: task_id, clear: clear)
15
- Log.instance.debug('authorized with token')
16
- return true
17
- end
18
- end
19
- halt 403, MultiJson.dump(:error => 'Invalid username or password supplied')
20
- end
21
- false
22
- end
23
-
24
- def authorize_with_ssl_client
25
- if %w[yes on 1].include? request.env['HTTPS'].to_s
26
- if request.env['SSL_CLIENT_CERT'].to_s.empty?
27
- Log.instance.error "No client SSL certificate supplied"
28
- halt 403, MultiJson.dump(:error => "No client SSL certificate supplied")
29
- else
30
- client_cert = OpenSSL::X509::Certificate.new(request.env['SSL_CLIENT_CERT'])
31
- unless SmartProxyDynflowCore::Core.instance.accepted_cert_serial == client_cert.serial
32
- Log.instance.error "SSL certificate with unexpected serial supplied"
33
- halt 403, MultiJson.dump(:error => "SSL certificate with unexpected serial supplied")
34
- end
35
- end
36
- else
37
- Log.instance.debug 'require_ssl_client_verification: skipping, non-HTTPS request'
38
- end
39
- end
40
-
41
- def trigger_task(*args)
42
- triggered = world.trigger(*args)
43
- { :task_id => triggered.id }
44
- end
45
-
46
- def cancel_task(task_id)
47
- execution_plan = world.persistence.load_execution_plan(task_id)
48
- cancel_events = execution_plan.cancel
49
- { :task_id => task_id, :canceled_steps_count => cancel_events.size }
50
- end
51
-
52
- def task_status(task_id)
53
- ep = world.persistence.load_execution_plan(task_id)
54
- ep.to_hash.merge(:actions => ep.actions.map(&:to_hash))
55
- rescue KeyError => _e
56
- status 404
57
- {}
58
- end
59
-
60
- def tasks_count(state)
61
- state ||= 'all'
62
- filter = state != 'all' ? { :filters => { :state => [state] } } : {}
63
- tasks = world.persistence.find_execution_plans(filter)
64
- { :count => tasks.count, :state => state }
65
- end
66
-
67
- def dispatch_external_event(task_id, params)
68
- world.event(task_id,
69
- params['step_id'].to_i,
70
- ::ForemanTasksCore::Runner::ExternalEvent.new(params))
71
- end
72
- end
73
- end