smart_proxy_salt 3.1.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/foreman-node +25 -6
- data/bin/salt_python_wrapper +16 -0
- data/etc/foreman.conf.example +65 -0
- data/lib/smart_proxy_salt/cli.rb +70 -32
- data/lib/smart_proxy_salt/salt.rb +20 -3
- data/lib/smart_proxy_salt/salt_api.rb +23 -3
- data/lib/smart_proxy_salt/salt_runner.rb +55 -0
- data/lib/smart_proxy_salt/salt_task_launcher.rb +27 -0
- data/lib/smart_proxy_salt/version.rb +1 -1
- data/salt/minion_auth/README.md +48 -0
- data/salt/minion_auth/foreman_minion_auth.sls +10 -0
- data/salt/minion_auth/master.snippet +5 -0
- data/salt/minion_auth/srv/salt/_runners/foreman_file.py +26 -0
- data/salt/minion_auth/srv/salt/_runners/foreman_https.py +121 -0
- data/salt/report_upload/README.md +36 -0
- data/salt/report_upload/foreman_report_upload.sls +10 -0
- data/salt/report_upload/master.snippet +3 -0
- data/salt/report_upload/srv/salt/_runners/foreman_report_upload.py +106 -0
- data/sbin/upload-salt-reports +24 -12
- data/settings.d/salt.yml.example +1 -0
- metadata +34 -66
- data/lib/smart_proxy_salt_core/salt_runner.rb +0 -51
- data/lib/smart_proxy_salt_core/salt_task_launcher.rb +0 -21
- data/lib/smart_proxy_salt_core/version.rb +0 -3
- data/lib/smart_proxy_salt_core.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b25650758e39a88cb8683f8ebe295d343c0752d0fff229fbe21f86ee585848c6
|
4
|
+
data.tar.gz: 9f26eb7258a77a426c1addf5e8ba949c432332f1b4b35897e193762cf1a60d3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd79c67ac4733c9b1f19a7e5f6a62a6019e39209d37abd21d7a67f48aea4e24da875bbdaecd3c8db574e566cb451038904fd9e21d045db61896df0a96ccab51e
|
7
|
+
data.tar.gz: 9c8640cc30f494fe2406b0a840643aacc2211c738fc25864c8c7e1024e4d2a6b6ad72e459eac947a2af9ac2b86ee14a87ba0cc84e27018742906d8a8ad50e18b
|
data/bin/foreman-node
CHANGED
@@ -14,6 +14,7 @@ require 'net/http'
|
|
14
14
|
require 'net/https'
|
15
15
|
require 'etc'
|
16
16
|
require 'timeout'
|
17
|
+
require 'msgpack' if SETTINGS[:filecache]
|
17
18
|
|
18
19
|
begin
|
19
20
|
require 'json'
|
@@ -57,19 +58,37 @@ rescue Exception => e
|
|
57
58
|
exit 1
|
58
59
|
end
|
59
60
|
|
61
|
+
def get_grains_from_filecache(minion)
|
62
|
+
# Use the grains from the salt master's filesystem based cache
|
63
|
+
# This requires the following settings in /etc/salt/foreman.yaml:
|
64
|
+
# :filecache: true
|
65
|
+
# :cachedir: "/path/to/master/cache" (default: "/var/cache/salt/master")
|
66
|
+
# Also, the msgpack rubygem needs to be present
|
67
|
+
cachedir = SETTINGS[:cachedir] || '/var/cache/salt/master'
|
68
|
+
content = File.read("#{cachedir}/minions/#{minion}/data.p")
|
69
|
+
data = MessagePack.unpack(content)
|
70
|
+
data['grains']
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_grains_from_saltrun(minion)
|
74
|
+
result = IO.popen(['salt-run', '-l', 'quiet', '--output=json', 'cache.grains', minion], &:read)
|
75
|
+
data = JSON.parse(result)
|
76
|
+
data[minion]
|
77
|
+
end
|
78
|
+
|
60
79
|
def plain_grains(minion)
|
61
80
|
# We have to get the grains from the cache, because the client
|
62
81
|
# is probably running 'state.highstate' right now.
|
63
82
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
83
|
+
grains = if SETTINGS[:filecache]
|
84
|
+
get_grains_from_filecache(minion)
|
85
|
+
else
|
86
|
+
get_grains_from_saltrun(minion)
|
87
|
+
end
|
69
88
|
|
70
89
|
raise 'No grains received from Salt master' unless grains
|
71
90
|
|
72
|
-
plainify(grains
|
91
|
+
plainify(grains).flatten.inject(&:merge)
|
73
92
|
end
|
74
93
|
|
75
94
|
def plainify(hash, prefix = nil)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
set -u
|
4
|
+
|
5
|
+
for py in 'python3' 'python'; do
|
6
|
+
exe=$(type -p ${py})
|
7
|
+
if [ -n "${exe}" ]; then
|
8
|
+
if ${exe} -c 'import salt.config'; then
|
9
|
+
${exe} "$@"
|
10
|
+
exit $?
|
11
|
+
fi
|
12
|
+
fi
|
13
|
+
done
|
14
|
+
|
15
|
+
echo "No usable python version found, check if python or python3 can import salt.config!" 1>&2
|
16
|
+
exit 1
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# /etc/salt/master.d/foreman.config Example configuration
|
2
|
+
#
|
3
|
+
# This file summarizes configurations for the salt-master. Modify directories and
|
4
|
+
# parameters to fit your setup. When you're done, remove the .example from the
|
5
|
+
# filename so the salt-master will make use of it.
|
6
|
+
# Have a look at the [Foreman Salt Plugin Documentation](https://theforeman.org/plugins/foreman_salt/) for detailed explanations.
|
7
|
+
#
|
8
|
+
# After editing this file, run the following command to active the changes:
|
9
|
+
# $ systemctl restart salt-master
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
# Autosign
|
14
|
+
autosign_grains_dir: /var/lib/foreman-proxy/salt/grains
|
15
|
+
autosign_file: /etc/salt/autosign.conf
|
16
|
+
# Uncomment the next line to make use of the autosign host name file (not recommended)
|
17
|
+
# permissive_pki_access: True
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
# Node classifier
|
22
|
+
master_tops:
|
23
|
+
ext_nodes: /usr/bin/foreman-node
|
24
|
+
|
25
|
+
|
26
|
+
##
|
27
|
+
# Pillar data access
|
28
|
+
ext_pillar:
|
29
|
+
- puppet: /usr/bin/foreman-node
|
30
|
+
|
31
|
+
|
32
|
+
##
|
33
|
+
# Salt API access
|
34
|
+
external_auth:
|
35
|
+
pam:
|
36
|
+
saltuser: # Username of your salt user
|
37
|
+
- '@runner'
|
38
|
+
|
39
|
+
rest_cherrypy:
|
40
|
+
port: 9191
|
41
|
+
ssl_key: /etc/puppet/example.key # Add the path to your Puppet ssl key here
|
42
|
+
ssl_crt: /etc/puppet/example.crt # Add the path to your Puppet ssl certificate here
|
43
|
+
|
44
|
+
|
45
|
+
##
|
46
|
+
# Remote execution provider
|
47
|
+
publisher_acl:
|
48
|
+
foreman-proxy:
|
49
|
+
- state.template_str
|
50
|
+
|
51
|
+
|
52
|
+
##
|
53
|
+
# Salt environment (optional)
|
54
|
+
file_roots:
|
55
|
+
base:
|
56
|
+
- /srv/salt
|
57
|
+
|
58
|
+
|
59
|
+
##
|
60
|
+
# Reactors
|
61
|
+
reactor:
|
62
|
+
- 'salt/auth': # Autosign reactor
|
63
|
+
- /var/lib/foreman-proxy/salt/reactors/foreman_minion_auth.sls
|
64
|
+
- 'salt/job/*/ret/*': # Report reactor
|
65
|
+
- /var/lib/foreman-proxy/salt/reactors/foreman_report_upload.sls
|
data/lib/smart_proxy_salt/cli.rb
CHANGED
@@ -14,44 +14,43 @@ module Proxy
|
|
14
14
|
Proxy::Salt::Plugin.settings.autosign_file
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
autosign = open(autosign_file, File::RDWR)
|
17
|
+
def autosign_key_file
|
18
|
+
Proxy::Salt::Plugin.settings.autosign_key_file
|
19
|
+
end
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
def autosign_create_hostname(hostname)
|
22
|
+
if append_value_to_file(autosign_file, hostname)
|
23
|
+
{ message: 'Added hostname successfully.' }
|
24
|
+
else
|
25
|
+
{ message: 'Failed to add hostname.' \
|
26
|
+
' See smart proxy error log for more information.' }
|
27
|
+
end
|
28
|
+
end
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
def autosign_remove_hostname(hostname)
|
31
|
+
if remove_value_from_file(autosign_file, hostname)
|
32
|
+
{ message: 'Removed hostname successfully.' }
|
33
|
+
else
|
34
|
+
{ message: 'Failed to remove hostname.' \
|
35
|
+
' See smart proxy error log for more information.' }
|
36
|
+
end
|
30
37
|
end
|
31
38
|
|
32
|
-
def
|
33
|
-
|
39
|
+
def autosign_create_key(key)
|
40
|
+
if append_value_to_file(autosign_key_file, key)
|
41
|
+
{ message: 'Added key successfully.' }
|
42
|
+
else
|
43
|
+
{ message: 'Failed to add key.' \
|
44
|
+
' See smart proxy error log for more information.' }
|
45
|
+
end
|
46
|
+
end
|
34
47
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
l
|
39
|
-
else
|
40
|
-
found = true
|
41
|
-
nil
|
42
|
-
end
|
43
|
-
end.uniq.compact
|
44
|
-
if found
|
45
|
-
autosign = open(autosign_file, File::TRUNC | File::RDWR)
|
46
|
-
autosign.write entries.join("\n")
|
47
|
-
autosign.write "\n"
|
48
|
-
autosign.close
|
49
|
-
result = { :message => "Removed #{host} from autosign" }
|
50
|
-
logger.info result[:message]
|
51
|
-
result
|
48
|
+
def autosign_remove_key(key)
|
49
|
+
if remove_value_from_file(autosign_key_file, key)
|
50
|
+
{ message: 'Removed key successfully.' }
|
52
51
|
else
|
53
|
-
|
54
|
-
|
52
|
+
{ message: 'Failed to remove key.' \
|
53
|
+
' See smart proxy error log for more information.' }
|
55
54
|
end
|
56
55
|
end
|
57
56
|
|
@@ -62,6 +61,45 @@ module Proxy
|
|
62
61
|
end.map(&:chomp)
|
63
62
|
end
|
64
63
|
|
64
|
+
def append_value_to_file(filepath, value)
|
65
|
+
File.open(filepath, File::CREAT|File::RDWR) do |file|
|
66
|
+
unless file.any? { |line| line.chomp == value}
|
67
|
+
file.puts value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
logger.info "Added an entry to '#{filepath}' successfully."
|
71
|
+
true
|
72
|
+
rescue IOError => e
|
73
|
+
logger.info "Attempted to add an entry to '#{filepath}', but an exception occurred: #{e}"
|
74
|
+
false
|
75
|
+
end
|
76
|
+
|
77
|
+
def remove_value_from_file(filepath, value)
|
78
|
+
|
79
|
+
return true unless File.exist?(filepath)
|
80
|
+
|
81
|
+
found = false
|
82
|
+
entries = File.readlines(filepath).collect do |line|
|
83
|
+
entry = line.chomp
|
84
|
+
if entry == value
|
85
|
+
found = true
|
86
|
+
nil
|
87
|
+
elsif entry == ""
|
88
|
+
nil
|
89
|
+
else
|
90
|
+
line
|
91
|
+
end
|
92
|
+
end.uniq.compact
|
93
|
+
if found
|
94
|
+
File.write(filepath, entries.join())
|
95
|
+
logger.info "Removed an entry from '#{filepath}' successfully."
|
96
|
+
end
|
97
|
+
true
|
98
|
+
rescue IOError => e
|
99
|
+
logger.info "Attempted to remove an entry from '#{filepath}', but an exception occurred: #{e}"
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
65
103
|
def highstate(host)
|
66
104
|
find_salt_binaries
|
67
105
|
cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt, '--async', escape_for_shell(host), 'state.highstate']
|
@@ -13,11 +13,24 @@ module Proxy
|
|
13
13
|
plugin 'salt', Proxy::Salt::VERSION
|
14
14
|
|
15
15
|
default_settings :autosign_file => '/etc/salt/autosign.conf',
|
16
|
+
:autosign_key_file => '/var/lib/foreman-proxy/salt/grains/autosign_key',
|
16
17
|
:salt_command_user => 'root',
|
17
|
-
:use_api => false
|
18
|
+
:use_api => false,
|
19
|
+
:saltfile => '/etc/foreman-proxy/settings.d/salt.saltfile'
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
+
requires :dynflow, '>= 0.5.0'
|
22
|
+
|
23
|
+
rackup_path File.expand_path('salt_http_config.ru', __dir__)
|
24
|
+
|
25
|
+
load_classes do
|
26
|
+
require 'smart_proxy_dynflow'
|
27
|
+
require 'smart_proxy_salt/salt_runner'
|
28
|
+
require 'smart_proxy_salt/salt_task_launcher'
|
29
|
+
end
|
30
|
+
|
31
|
+
load_dependency_injection_wirings do |_container_instance, _settings|
|
32
|
+
Proxy::Dynflow::TaskLauncherRegistry.register('salt', SaltTaskLauncher)
|
33
|
+
end
|
21
34
|
end
|
22
35
|
|
23
36
|
class << self
|
@@ -33,6 +46,10 @@ module Proxy
|
|
33
46
|
super
|
34
47
|
end
|
35
48
|
end
|
49
|
+
|
50
|
+
def respond_to_missing?(method, include_private = false)
|
51
|
+
Proxy::Salt::Rest.respond_to?(method) || Proxy::Salt::CLI.respond_to?(method) || super
|
52
|
+
end
|
36
53
|
end
|
37
54
|
end
|
38
55
|
end
|
@@ -9,12 +9,32 @@ module Proxy
|
|
9
9
|
class Api < ::Sinatra::Base
|
10
10
|
include ::Proxy::Log
|
11
11
|
helpers ::Proxy::Helpers
|
12
|
-
|
12
|
+
authorize_with_trusted_hosts
|
13
|
+
|
14
|
+
post '/autosign_key/:key' do
|
15
|
+
content_type :json
|
16
|
+
begin
|
17
|
+
Proxy::Salt.autosign_create_key(params[:key]).to_json
|
18
|
+
rescue Exception => e
|
19
|
+
log_halt 406, "Failed to create autosign key #{params[:key]}: #{e}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
delete '/autosign_key/:key' do
|
24
|
+
content_type :json
|
25
|
+
begin
|
26
|
+
Proxy::Salt.autosign_remove_key(params[:key]).to_json
|
27
|
+
rescue Proxy::Salt::NotFound => e
|
28
|
+
log_halt 404, e.to_s
|
29
|
+
rescue Exception => e
|
30
|
+
log_halt 406, "Failed to remove autosign key #{params[:key]}: #{e}"
|
31
|
+
end
|
32
|
+
end
|
13
33
|
|
14
34
|
post '/autosign/:host' do
|
15
35
|
content_type :json
|
16
36
|
begin
|
17
|
-
Proxy::Salt.
|
37
|
+
Proxy::Salt.autosign_create_hostname(params[:host]).to_json
|
18
38
|
rescue Exception => e
|
19
39
|
log_halt 406, "Failed to create autosign for #{params[:host]}: #{e}"
|
20
40
|
end
|
@@ -23,7 +43,7 @@ module Proxy
|
|
23
43
|
delete '/autosign/:host' do
|
24
44
|
content_type :json
|
25
45
|
begin
|
26
|
-
Proxy::Salt.
|
46
|
+
Proxy::Salt.autosign_remove_hostname(params[:host]).to_json
|
27
47
|
rescue Proxy::Salt::NotFound => e
|
28
48
|
log_halt 404, e.to_s
|
29
49
|
rescue Exception => e
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'smart_proxy_dynflow/runner/base'
|
2
|
+
require 'smart_proxy_dynflow/runner/command_runner'
|
3
|
+
|
4
|
+
module Proxy
|
5
|
+
module Salt
|
6
|
+
# Implements the SaltRunner to be used by foreman_remote_execution
|
7
|
+
class SaltRunner < Proxy::Dynflow::Runner::CommandRunner
|
8
|
+
DEFAULT_REFRESH_INTERVAL = 1
|
9
|
+
|
10
|
+
attr_reader :jid
|
11
|
+
|
12
|
+
def initialize(options, suspended_action)
|
13
|
+
super(options, :suspended_action => suspended_action)
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
def start
|
18
|
+
command = generate_command
|
19
|
+
logger.debug("Running command '#{command.join(' ')}'")
|
20
|
+
initialize_command(*command)
|
21
|
+
end
|
22
|
+
|
23
|
+
def kill
|
24
|
+
publish_data('== TASK ABORTED BY USER ==', 'stdout')
|
25
|
+
publish_exit_status(1)
|
26
|
+
::Process.kill('SIGTERM', @command_pid)
|
27
|
+
end
|
28
|
+
|
29
|
+
def publish_data(data, type)
|
30
|
+
if @jid.nil? && (match = data.match(/jid: ([0-9]+)/))
|
31
|
+
@jid = match[1]
|
32
|
+
end
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def publish_exit_status(status)
|
37
|
+
# If there was no salt job associated with this run, mark the job as failed
|
38
|
+
status = 1 if @jid.nil?
|
39
|
+
super status
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def generate_command
|
45
|
+
saltfile_path = ::Proxy::Salt::Plugin.settings[:saltfile]
|
46
|
+
command = %w[salt --show-jid]
|
47
|
+
command << "--saltfile=#{saltfile_path}" if File.file?(saltfile_path)
|
48
|
+
command << @options['name']
|
49
|
+
command << 'state.template_str'
|
50
|
+
command << @options['script']
|
51
|
+
command
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'smart_proxy_dynflow/task_launcher'
|
2
|
+
|
3
|
+
module Proxy
|
4
|
+
module Salt
|
5
|
+
# Implements the TaskLauncher::Batch for Salt
|
6
|
+
class SaltTaskLauncher < ::Proxy::Dynflow::TaskLauncher::Batch
|
7
|
+
# Implements the Runner::Action for Salt
|
8
|
+
class SaltRunnerAction < ::Proxy::Dynflow::Action::Runner
|
9
|
+
def initiate_runner
|
10
|
+
additional_options = {
|
11
|
+
:step_id => run_step_id,
|
12
|
+
:uuid => execution_plan_id
|
13
|
+
}
|
14
|
+
::Proxy::Salt::SaltRunner.new(
|
15
|
+
input.merge(additional_options),
|
16
|
+
suspended_action
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def child_launcher(parent)
|
22
|
+
::Proxy::Dynflow::TaskLauncher::Single.new(world, callback, :parent => parent,
|
23
|
+
:action_class_override => SaltRunnerAction)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Foreman Salt Minion Authentication
|
2
|
+
|
3
|
+
Currently, there are two possibilites to authenticate a newly deployed minion automatically:
|
4
|
+
1. Use the _/etc/salt/autosign.conf_ file which stores the hostnames of acceptable hosts.
|
5
|
+
2. Use _Salt Autosign Grains_ for a more secure way which relies on a shared secret key.
|
6
|
+
|
7
|
+
This README, handles the second option and how to configure it
|
8
|
+
|
9
|
+
## Setup
|
10
|
+
Add the content of 'master.snippet' to '/etc/salt/master' which configures the grains key file on the master and a reactor. The grains file holds the acceptable keys and will be written by the Smart Proxy when a new minion is deployed. The reactor initiates an interaction with Foreman Salt if a new minion was authenticated successfully.
|
11
|
+
In case there is already a reactor configured, you need to adapt it using the options mentioned in 'master.snippet'. The directories given in 'master.snippet' are the default ones. In case you want your files in a different place, you have to change the paths accordingly.
|
12
|
+
|
13
|
+
If '/srv/salt' is configured as 'file_roots' in your '/etc/salt/master' config, setup the necessary Salt runners:
|
14
|
+
|
15
|
+
```
|
16
|
+
/srv/salt/_runners/foreman_file.py
|
17
|
+
/srv/salt/_runners/foreman_https.py
|
18
|
+
```
|
19
|
+
|
20
|
+
Check if the reactor ('foreman_minion_auth.sls') is at the appropriated location:
|
21
|
+
|
22
|
+
```
|
23
|
+
/var/lib/foreman-proxy/salt/reactors/foreman_minion_auth.sls
|
24
|
+
```
|
25
|
+
|
26
|
+
Restart the salt-master service:
|
27
|
+
|
28
|
+
```
|
29
|
+
systemctl restart salt-master
|
30
|
+
```
|
31
|
+
|
32
|
+
After checking the reactor and runners, run the following command to make them available in the Salt environment:
|
33
|
+
|
34
|
+
```
|
35
|
+
salt-run saltutil.sync_all
|
36
|
+
```
|
37
|
+
|
38
|
+
## Procedure
|
39
|
+
|
40
|
+
1. A new host, configured as Salt minion, is deployed with Foreman Salt.
|
41
|
+
2. Foreman Salt generates a unique key for that minion and distributes it via the Provisioning Template to the host and via an API call to the Smart Proxy.
|
42
|
+
3. The Smart Proxy makes the key available for the Salt Autosign Grains procedure by adding it to the previously defined file (by default: /var/lib/foreman-proxy/salt/grains/autosign_key).
|
43
|
+
4. The Salt minion is started and uses the configured Salt autosign grain for authentication to the Salt master.
|
44
|
+
5. The Salt master accepts the minion depending on the key and the corresponding auth reactor is triggered on the Salt master.
|
45
|
+
6. The Salt master initiates an API call to Foreman Salt which marks the corresponding host status as _authenticated_.
|
46
|
+
7. Foreman Salt triggers an API call to Smart Proxy Salt which deletes the key from the acceptable keys list of the Salt master (since the minion was authenticated already and shall not be reused).
|
47
|
+
|
48
|
+
The minion was authenticated successfully.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{% if 'act' in data and 'id' in data %}
|
2
|
+
{% if data['act'] == 'accept' %}
|
3
|
+
{% if salt['saltutil.runner']('foreman_file.check_key', (data['id'], 100)) == True %}
|
4
|
+
{%- do salt.log.info('Minion authenticated successfully, starting HTTPS request to delete autosign key.') -%}
|
5
|
+
remove_autosign_key_custom_runner:
|
6
|
+
runner.foreman_https.remove_key:
|
7
|
+
- minion: {{ data['id'] }}
|
8
|
+
{% endif %}
|
9
|
+
{% endif %}
|
10
|
+
{% endif %}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""
|
2
|
+
Salt runner to check the age of a minion key file.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
import time
|
7
|
+
|
8
|
+
SALT_KEY_PATH = "/etc/salt/pki/master/minions/"
|
9
|
+
|
10
|
+
|
11
|
+
def time_secs(path):
|
12
|
+
stat = os.stat(path)
|
13
|
+
return stat.st_mtime
|
14
|
+
|
15
|
+
|
16
|
+
def younger_than_secs(path, seconds):
|
17
|
+
|
18
|
+
now_time = time.time()
|
19
|
+
file_time = time_secs(path)
|
20
|
+
if now_time - file_time <= seconds:
|
21
|
+
return True
|
22
|
+
return False
|
23
|
+
|
24
|
+
|
25
|
+
def check_key(hostname, seconds):
|
26
|
+
return younger_than_secs(SALT_KEY_PATH + hostname, seconds)
|
@@ -0,0 +1,121 @@
|
|
1
|
+
"""
|
2
|
+
Salt runner to make generic https requests or perform directly
|
3
|
+
an autosign key removal.
|
4
|
+
"""
|
5
|
+
|
6
|
+
|
7
|
+
from http.client import HTTPSConnection
|
8
|
+
import ssl
|
9
|
+
import base64
|
10
|
+
import json
|
11
|
+
import logging
|
12
|
+
import yaml
|
13
|
+
|
14
|
+
FOREMAN_CONFIG = '/etc/salt/foreman.yaml'
|
15
|
+
log = logging.getLogger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
def salt_config():
|
19
|
+
"""
|
20
|
+
Read the foreman configuratoin from FOREMAN_CONFIG
|
21
|
+
"""
|
22
|
+
with open(FOREMAN_CONFIG, 'r') as config_file:
|
23
|
+
config = yaml.load(config_file.read())
|
24
|
+
return config
|
25
|
+
|
26
|
+
|
27
|
+
def remove_key(minion):
|
28
|
+
"""
|
29
|
+
Perform an HTTPS request to the configured foreman host and trigger
|
30
|
+
the autosign key removal process.
|
31
|
+
"""
|
32
|
+
config = salt_config()
|
33
|
+
host_name = config[':host']
|
34
|
+
port = config[':port']
|
35
|
+
timeout = config[':timeout']
|
36
|
+
method = 'PUT'
|
37
|
+
path = '/salt/api/v2/salt_autosign_auth?name=%s' % minion
|
38
|
+
|
39
|
+
# Differentiate between cert and user authentication
|
40
|
+
if config[':proto'] == 'https':
|
41
|
+
query_cert(host=host_name,
|
42
|
+
path=path,
|
43
|
+
port=port,
|
44
|
+
method=method,
|
45
|
+
cert=config[':ssl_cert'],
|
46
|
+
key=config[':ssl_key'],
|
47
|
+
timeout=timeout)
|
48
|
+
else:
|
49
|
+
query_user(host=host_name,
|
50
|
+
path=path,
|
51
|
+
port=port,
|
52
|
+
method=method,
|
53
|
+
username=config[':username'],
|
54
|
+
password=config[':password'],
|
55
|
+
timeout=timeout)
|
56
|
+
|
57
|
+
|
58
|
+
def query_cert(host, path, port, method, cert, key,
|
59
|
+
payload=None, timeout=10):
|
60
|
+
"""
|
61
|
+
Perform an HTTPS query with certificate credentials.
|
62
|
+
"""
|
63
|
+
|
64
|
+
headers = {"Accept": "application/json"}
|
65
|
+
|
66
|
+
if payload is not None or method.lower() in ['put', 'post']:
|
67
|
+
headers["Content-Type"] = "application/json"
|
68
|
+
|
69
|
+
ctx = ssl.create_default_context()
|
70
|
+
ctx.load_cert_chain(certfile=cert, keyfile=key)
|
71
|
+
|
72
|
+
connection = HTTPSConnection(host,
|
73
|
+
port=port,
|
74
|
+
context=ctx,
|
75
|
+
timeout=timeout)
|
76
|
+
if payload is None:
|
77
|
+
connection.request(method,
|
78
|
+
path,
|
79
|
+
headers=headers)
|
80
|
+
else:
|
81
|
+
payload_json = json.dumps(payload)
|
82
|
+
connection.request(method=method,
|
83
|
+
url=path,
|
84
|
+
body=payload_json,
|
85
|
+
headers=headers)
|
86
|
+
response = connection.getresponse()
|
87
|
+
response_str = response.read().decode('utf-8')
|
88
|
+
print(response_str)
|
89
|
+
|
90
|
+
|
91
|
+
def query_user(host, path, port, method, username, password,
|
92
|
+
payload=None, timeout=10):
|
93
|
+
"""
|
94
|
+
Perform an HTTPS query with user credentials.
|
95
|
+
"""
|
96
|
+
|
97
|
+
auth = "{}:{}".format(username, password)
|
98
|
+
token = base64.b64encode(auth.encode('utf-8')).decode('ascii')
|
99
|
+
headers = {"Authorization": "Basic {}".format(token),
|
100
|
+
"Accept": "application/json"}
|
101
|
+
if payload is not None or method.lower() in ["put", "post"]:
|
102
|
+
headers["Content-Type"] = "application/json"
|
103
|
+
|
104
|
+
ctx = ssl._create_unverified_context()
|
105
|
+
connection = HTTPSConnection(host,
|
106
|
+
port=port,
|
107
|
+
context=ctx,
|
108
|
+
timeout=timeout)
|
109
|
+
if payload is None:
|
110
|
+
connection.request(method=method,
|
111
|
+
url=path,
|
112
|
+
headers=headers)
|
113
|
+
else:
|
114
|
+
payload_json = json.dumps(payload)
|
115
|
+
connection.request(method=method,
|
116
|
+
url=path,
|
117
|
+
body=payload_json,
|
118
|
+
headers=headers)
|
119
|
+
response = connection.getresponse()
|
120
|
+
response_str = response.read().decode('utf-8')
|
121
|
+
print(response_str)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Foreman Salt Report Upload
|
2
|
+
|
3
|
+
Currently, there are two possibilites to upload the salt report to Foreman:
|
4
|
+
1. Use /usr/sbin/upload-salt-reports which is called by a cron job every 10 minutes by default
|
5
|
+
2. Upload the report immediately by using a Salt Reactor.
|
6
|
+
|
7
|
+
This README, handles the second option and how to configure it
|
8
|
+
|
9
|
+
## Setup
|
10
|
+
Add the content of 'master.snippet' to '/etc/salt/master' which configures a reactor.
|
11
|
+
In case there is already a reactor configured, you need to adapt it using the options mentioned in 'master.snippet'.
|
12
|
+
|
13
|
+
Check the reactor file to be in the following folder (or a different one depending on your master configuration):
|
14
|
+
|
15
|
+
```
|
16
|
+
/var/lib/foreman-proxy/salt/reactors/foreman_report_upload.sls
|
17
|
+
```
|
18
|
+
|
19
|
+
In case '/srv/salt' is configured as 'file_roots' in your '/etc/salt/master' config, setup the necessary Salt runner:
|
20
|
+
|
21
|
+
```
|
22
|
+
/srv/salt/_runners/foreman_report_upload.py
|
23
|
+
```
|
24
|
+
|
25
|
+
After changing the salt master run:
|
26
|
+
|
27
|
+
```
|
28
|
+
systemctl restart salt-master
|
29
|
+
```
|
30
|
+
|
31
|
+
After adding the foreman_report_upload.sls and foreman_report_upload.py, run the following:
|
32
|
+
|
33
|
+
```
|
34
|
+
salt-run saltutil.sync_all
|
35
|
+
```
|
36
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
{% if 'cmd' in data and data['cmd'] == '_return' and 'fun' in data and (
|
2
|
+
data['fun'] == 'state.highstate' or (data['fun'] == 'state.template_str' and
|
3
|
+
'fun_args' in data and
|
4
|
+
data['fun_args'][0].startswith('state.highstate:')
|
5
|
+
)) %}
|
6
|
+
foreman_report_upload:
|
7
|
+
runner.foreman_report_upload.now:
|
8
|
+
- args:
|
9
|
+
- highstate: '{{ data|json|base64_encode }}'
|
10
|
+
{% endif %}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
'''
|
3
|
+
Uploads reports from the Salt job cache to Foreman
|
4
|
+
'''
|
5
|
+
from __future__ import absolute_import, print_function, unicode_literals
|
6
|
+
|
7
|
+
FOREMAN_CONFIG = '/etc/salt/foreman.yaml'
|
8
|
+
|
9
|
+
try:
|
10
|
+
from http.client import HTTPConnection, HTTPSConnection
|
11
|
+
except ImportError:
|
12
|
+
from httplib import HTTPSConnection, HTTPSConnection
|
13
|
+
|
14
|
+
import ssl
|
15
|
+
import json
|
16
|
+
import yaml
|
17
|
+
import os
|
18
|
+
import sys
|
19
|
+
import base64
|
20
|
+
|
21
|
+
# Import python libs
|
22
|
+
import logging
|
23
|
+
|
24
|
+
log = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
|
27
|
+
def salt_config():
|
28
|
+
with open(FOREMAN_CONFIG, 'r') as f:
|
29
|
+
config = yaml.load(f.read())
|
30
|
+
return config
|
31
|
+
|
32
|
+
|
33
|
+
def upload(report):
|
34
|
+
config = salt_config()
|
35
|
+
headers = {'Accept': 'application/json',
|
36
|
+
'Content-Type': 'application/json'}
|
37
|
+
|
38
|
+
if config[':proto'] == 'https':
|
39
|
+
ctx = ssl.create_default_context()
|
40
|
+
ctx.load_cert_chain(certfile=config[':ssl_cert'], keyfile=config[':ssl_key'])
|
41
|
+
if config[':ssl_ca']:
|
42
|
+
ctx.load_verify_locations(cafile=config[':ssl_ca'])
|
43
|
+
connection = HTTPSConnection(config[':host'],
|
44
|
+
port=config[':port'], context=ctx)
|
45
|
+
else:
|
46
|
+
connection = HTTPConnection(config[':host'],
|
47
|
+
port=config[':port'])
|
48
|
+
if ':username' in config and ':password' in config:
|
49
|
+
token = base64.b64encode('{}:{}'.format(config[':username'],
|
50
|
+
config[':password']))
|
51
|
+
headers['Authorization'] = 'Basic {}'.format(token)
|
52
|
+
|
53
|
+
connection.request('POST', '/salt/api/v2/jobs/upload',
|
54
|
+
json.dumps(report), headers)
|
55
|
+
response = connection.getresponse()
|
56
|
+
|
57
|
+
if response.status == 200:
|
58
|
+
info_msg = 'Success {0}: {1}'.format(report['job']['job_id'], response.read())
|
59
|
+
log.info(info_msg)
|
60
|
+
else:
|
61
|
+
log.error("Unable to upload job - aborting report upload")
|
62
|
+
log.error(response.read())
|
63
|
+
|
64
|
+
|
65
|
+
def create_report(json_str):
|
66
|
+
msg = json.loads(json_str)
|
67
|
+
|
68
|
+
if msg['fun'] == 'state.highstate':
|
69
|
+
return {'job':
|
70
|
+
{
|
71
|
+
'result': {
|
72
|
+
msg['id']: msg['return'],
|
73
|
+
},
|
74
|
+
'function': 'state.highstate',
|
75
|
+
'job_id': msg['jid']
|
76
|
+
}
|
77
|
+
}
|
78
|
+
elif msg['fun'] == 'state.template_str':
|
79
|
+
for key, entry in msg['return'].items():
|
80
|
+
if key.startswith('module_') and entry['__id__'] == 'state.highstate':
|
81
|
+
return {'job':
|
82
|
+
{
|
83
|
+
'result': {
|
84
|
+
msg['id']: entry['changes']['ret'],
|
85
|
+
},
|
86
|
+
'function': 'state.highstate',
|
87
|
+
'job_id': msg['jid']
|
88
|
+
}
|
89
|
+
}
|
90
|
+
raise Exception('No state.highstate found')
|
91
|
+
|
92
|
+
|
93
|
+
def now(highstate):
|
94
|
+
'''
|
95
|
+
Upload a highstate to Foreman
|
96
|
+
highstate :
|
97
|
+
dictionary containing a highstate (generated by a Salt reactor)
|
98
|
+
'''
|
99
|
+
log.debug('Upload highstate to Foreman')
|
100
|
+
|
101
|
+
try:
|
102
|
+
report = create_report(base64.b64decode(highstate))
|
103
|
+
upload(report)
|
104
|
+
except Exception as exc:
|
105
|
+
log.error('Exception encountered: %s', exc)
|
106
|
+
|
data/sbin/upload-salt-reports
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/
|
1
|
+
#!/usr/bin/salt_python_wrapper
|
2
2
|
# Uploads reports from the Salt job cache to Foreman
|
3
3
|
|
4
4
|
from __future__ import print_function
|
@@ -18,12 +18,13 @@ import io
|
|
18
18
|
import os
|
19
19
|
import sys
|
20
20
|
import base64
|
21
|
-
|
22
21
|
import traceback
|
23
|
-
|
24
22
|
import salt.config
|
25
23
|
import salt.runner
|
26
24
|
|
25
|
+
if sys.version_info.major == 3:
|
26
|
+
unicode = str
|
27
|
+
|
27
28
|
|
28
29
|
def salt_config():
|
29
30
|
with io.open(FOREMAN_CONFIG, 'r') as f:
|
@@ -33,12 +34,22 @@ def salt_config():
|
|
33
34
|
|
34
35
|
def get_job(job_id):
|
35
36
|
result = run('jobs.lookup_jid', [job_id])
|
36
|
-
|
37
37
|
# If any minion's results are strings, they're exceptions
|
38
38
|
# and should be wrapped in a list like other errors
|
39
|
+
|
39
40
|
for minion, value in result.items():
|
40
|
-
|
41
|
-
|
41
|
+
try:
|
42
|
+
if isinstance(value,str):
|
43
|
+
result[minion] = [value]
|
44
|
+
elif isinstance(value,list):
|
45
|
+
result[minion] = value
|
46
|
+
else:
|
47
|
+
for key, entry in value.items():
|
48
|
+
if key.startswith('module_') and '__id__' in entry and entry['__id__'] == 'state.highstate':
|
49
|
+
result[minion] = entry['changes']['ret']
|
50
|
+
break
|
51
|
+
except KeyError:
|
52
|
+
traceback.print_exc()
|
42
53
|
|
43
54
|
return {'job':
|
44
55
|
{
|
@@ -66,7 +77,7 @@ def read_last_uploaded():
|
|
66
77
|
|
67
78
|
def write_last_uploaded(last_uploaded):
|
68
79
|
with io.open(LAST_UPLOADED, 'w+') as f:
|
69
|
-
f.write(last_uploaded)
|
80
|
+
f.write(unicode(last_uploaded))
|
70
81
|
|
71
82
|
|
72
83
|
def run(*args, **kwargs):
|
@@ -85,12 +96,11 @@ def run(*args, **kwargs):
|
|
85
96
|
|
86
97
|
def jobs_to_upload():
|
87
98
|
jobs = run('jobs.list_jobs', kwarg={
|
88
|
-
"search_function": "state.highstate",
|
99
|
+
"search_function": ["state.highstate","state.template_str"],
|
89
100
|
})
|
90
101
|
last_uploaded = read_last_uploaded()
|
91
102
|
|
92
|
-
job_ids = [jid for
|
93
|
-
if int(jid) > last_uploaded]
|
103
|
+
job_ids = [jid for jid in jobs.keys() if int(jid) > last_uploaded]
|
94
104
|
|
95
105
|
for job_id in sorted(job_ids):
|
96
106
|
yield job_id, get_job(job_id)
|
@@ -112,8 +122,10 @@ def upload(jobs):
|
|
112
122
|
connection = HTTPConnection(config[':host'],
|
113
123
|
port=config[':port'])
|
114
124
|
if ':username' in config and ':password' in config:
|
115
|
-
|
116
|
-
|
125
|
+
auth = '{}:{}'.format(config[':username'], config[':password'])
|
126
|
+
if not isinstance(auth, bytes):
|
127
|
+
auth = auth.encode('UTF-8')
|
128
|
+
token = base64.b64encode(auth)
|
117
129
|
headers['Authorization'] = 'Basic {}'.format(token)
|
118
130
|
|
119
131
|
for job_id, job in jobs:
|
data/settings.d/salt.yml.example
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_proxy_salt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Moll
|
@@ -9,64 +9,8 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-02-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
-
- !ruby/object:Gem::Dependency
|
15
|
-
name: json
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
requirements:
|
18
|
-
- - ">="
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: '0'
|
21
|
-
type: :runtime
|
22
|
-
prerelease: false
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - ">="
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
version: '0'
|
28
|
-
- !ruby/object:Gem::Dependency
|
29
|
-
name: rack
|
30
|
-
requirement: !ruby/object:Gem::Requirement
|
31
|
-
requirements:
|
32
|
-
- - ">="
|
33
|
-
- !ruby/object:Gem::Version
|
34
|
-
version: '1.1'
|
35
|
-
type: :runtime
|
36
|
-
prerelease: false
|
37
|
-
version_requirements: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ">="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '1.1'
|
42
|
-
- !ruby/object:Gem::Dependency
|
43
|
-
name: sinatra
|
44
|
-
requirement: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - ">="
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
49
|
-
type: :runtime
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - ">="
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: logging
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
63
|
-
type: :runtime
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
14
|
- !ruby/object:Gem::Dependency
|
71
15
|
name: test-unit
|
72
16
|
requirement: !ruby/object:Gem::Requirement
|
@@ -115,28 +59,28 @@ dependencies:
|
|
115
59
|
requirements:
|
116
60
|
- - "~>"
|
117
61
|
- !ruby/object:Gem::Version
|
118
|
-
version: '
|
62
|
+
version: '13'
|
119
63
|
type: :development
|
120
64
|
prerelease: false
|
121
65
|
version_requirements: !ruby/object:Gem::Requirement
|
122
66
|
requirements:
|
123
67
|
- - "~>"
|
124
68
|
- !ruby/object:Gem::Version
|
125
|
-
version: '
|
69
|
+
version: '13'
|
126
70
|
- !ruby/object:Gem::Dependency
|
127
71
|
name: rubocop
|
128
72
|
requirement: !ruby/object:Gem::Requirement
|
129
73
|
requirements:
|
130
74
|
- - '='
|
131
75
|
- !ruby/object:Gem::Version
|
132
|
-
version: 0.
|
76
|
+
version: 0.50.0
|
133
77
|
type: :development
|
134
78
|
prerelease: false
|
135
79
|
version_requirements: !ruby/object:Gem::Requirement
|
136
80
|
requirements:
|
137
81
|
- - '='
|
138
82
|
- !ruby/object:Gem::Version
|
139
|
-
version: 0.
|
83
|
+
version: 0.50.0
|
140
84
|
- !ruby/object:Gem::Dependency
|
141
85
|
name: rack-test
|
142
86
|
requirement: !ruby/object:Gem::Requirement
|
@@ -151,10 +95,25 @@ dependencies:
|
|
151
95
|
- - "~>"
|
152
96
|
- !ruby/object:Gem::Version
|
153
97
|
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: smart_proxy_dynflow
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 0.5.0
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 0.5.0
|
154
112
|
description: SaltStack Plug-In for Foreman's Smart Proxy
|
155
113
|
email: foreman-dev@googlegroups.com
|
156
114
|
executables:
|
157
115
|
- foreman-node
|
116
|
+
- salt_python_wrapper
|
158
117
|
extensions: []
|
159
118
|
extra_rdoc_files:
|
160
119
|
- README.md
|
@@ -163,8 +122,10 @@ files:
|
|
163
122
|
- LICENSE
|
164
123
|
- README.md
|
165
124
|
- bin/foreman-node
|
125
|
+
- bin/salt_python_wrapper
|
166
126
|
- bundler.d/salt.rb
|
167
127
|
- cron/smart_proxy_salt
|
128
|
+
- etc/foreman.conf.example
|
168
129
|
- etc/foreman.yaml.example
|
169
130
|
- lib/smart_proxy_salt.rb
|
170
131
|
- lib/smart_proxy_salt/api_request.rb
|
@@ -173,11 +134,18 @@ files:
|
|
173
134
|
- lib/smart_proxy_salt/salt.rb
|
174
135
|
- lib/smart_proxy_salt/salt_api.rb
|
175
136
|
- lib/smart_proxy_salt/salt_http_config.ru
|
137
|
+
- lib/smart_proxy_salt/salt_runner.rb
|
138
|
+
- lib/smart_proxy_salt/salt_task_launcher.rb
|
176
139
|
- lib/smart_proxy_salt/version.rb
|
177
|
-
-
|
178
|
-
-
|
179
|
-
-
|
180
|
-
-
|
140
|
+
- salt/minion_auth/README.md
|
141
|
+
- salt/minion_auth/foreman_minion_auth.sls
|
142
|
+
- salt/minion_auth/master.snippet
|
143
|
+
- salt/minion_auth/srv/salt/_runners/foreman_file.py
|
144
|
+
- salt/minion_auth/srv/salt/_runners/foreman_https.py
|
145
|
+
- salt/report_upload/README.md
|
146
|
+
- salt/report_upload/foreman_report_upload.sls
|
147
|
+
- salt/report_upload/master.snippet
|
148
|
+
- salt/report_upload/srv/salt/_runners/foreman_report_upload.py
|
181
149
|
- sbin/upload-salt-reports
|
182
150
|
- settings.d/salt.saltfile.example
|
183
151
|
- settings.d/salt.yml.example
|
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'foreman_tasks_core/runner/command_runner'
|
2
|
-
|
3
|
-
module SmartProxySaltCore
|
4
|
-
class SaltRunner < ForemanTasksCore::Runner::CommandRunner
|
5
|
-
DEFAULT_REFRESH_INTERVAL = 1
|
6
|
-
|
7
|
-
attr_reader :jid
|
8
|
-
|
9
|
-
def initialize(options, suspended_action:)
|
10
|
-
super(options, :suspended_action => suspended_action)
|
11
|
-
@options = options
|
12
|
-
end
|
13
|
-
|
14
|
-
def start
|
15
|
-
command = generate_command
|
16
|
-
logger.debug("Running command '#{command.join(' ')}'")
|
17
|
-
initialize_command(*command)
|
18
|
-
end
|
19
|
-
|
20
|
-
def kill
|
21
|
-
publish_data('== TASK ABORTED BY USER ==', 'stdout')
|
22
|
-
publish_exit_status(1)
|
23
|
-
::Process.kill('SIGTERM', @command_pid)
|
24
|
-
end
|
25
|
-
|
26
|
-
def publish_data(data, type)
|
27
|
-
if @jid.nil? && (match = data.match(/jid: ([0-9]+)/))
|
28
|
-
@jid = match[1]
|
29
|
-
end
|
30
|
-
super
|
31
|
-
end
|
32
|
-
|
33
|
-
def publish_exit_status(status)
|
34
|
-
# If there was no salt job associated with this run, mark the job as failed
|
35
|
-
status = 1 if @jid.nil?
|
36
|
-
super status
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def generate_command
|
42
|
-
saltfile_path = SmartProxySaltCore.settings[:saltfile]
|
43
|
-
command = %w(salt --show-jid)
|
44
|
-
command << "--saltfile=#{saltfile_path}" if File.file?(saltfile_path)
|
45
|
-
command << @options['name']
|
46
|
-
command << 'state.template_str'
|
47
|
-
command << @options['script']
|
48
|
-
command
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
@@ -1,21 +0,0 @@
|
|
1
|
-
module SmartProxySaltCore
|
2
|
-
class SaltTaskLauncher < ForemanTasksCore::TaskLauncher::Batch
|
3
|
-
class SaltRunnerAction < ForemanTasksCore::Runner::Action
|
4
|
-
def initiate_runner
|
5
|
-
additional_options = {
|
6
|
-
:step_id => run_step_id,
|
7
|
-
:uuid => execution_plan_id
|
8
|
-
}
|
9
|
-
::SmartProxySaltCore::SaltRunner.new(
|
10
|
-
input.merge(additional_options),
|
11
|
-
:suspended_action => suspended_action
|
12
|
-
)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def child_launcher(parent)
|
17
|
-
ForemanTasksCore::TaskLauncher::Single.new(world, callback, :parent => parent,
|
18
|
-
:action_class_override => SaltRunnerAction)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
require 'foreman_tasks_core'
|
2
|
-
require 'foreman_remote_execution_core'
|
3
|
-
|
4
|
-
module SmartProxySaltCore
|
5
|
-
extend ForemanTasksCore::SettingsLoader
|
6
|
-
register_settings(:salt,
|
7
|
-
:saltfile => '/etc/foreman-proxy/settings.d/salt.saltfile')
|
8
|
-
|
9
|
-
if ForemanTasksCore.dynflow_present?
|
10
|
-
require 'smart_proxy_salt_core/salt_runner'
|
11
|
-
require 'smart_proxy_salt_core/salt_task_launcher'
|
12
|
-
|
13
|
-
if defined?(SmartProxyDynflowCore)
|
14
|
-
SmartProxyDynflowCore::TaskLauncherRegistry.register('salt', SaltTaskLauncher)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|