smart_proxy_salt 2.1.9 → 3.0.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 +4 -4
- data/bin/foreman-node +56 -56
- data/bundler.d/salt.rb +2 -0
- data/lib/smart_proxy_salt/api_request.rb +36 -31
- data/lib/smart_proxy_salt/cli.rb +139 -134
- data/lib/smart_proxy_salt/rest.rb +23 -20
- data/lib/smart_proxy_salt/salt.rb +27 -21
- data/lib/smart_proxy_salt/salt_api.rb +84 -79
- data/lib/smart_proxy_salt/salt_http_config.ru +2 -0
- data/lib/smart_proxy_salt/version.rb +4 -1
- data/lib/smart_proxy_salt.rb +2 -0
- data/lib/smart_proxy_salt_core/salt_runner.rb +51 -0
- data/lib/smart_proxy_salt_core/salt_task_launcher.rb +21 -0
- data/lib/smart_proxy_salt_core/version.rb +3 -0
- data/lib/smart_proxy_salt_core.rb +17 -0
- data/settings.d/salt.saltfile.example +3 -0
- data/settings.d/salt.yml.example +1 -0
- metadata +149 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 978fe04cd0b2ee65be99fc84f20599f295d51498b8aa1ba0541cc9569f56ee9a
|
4
|
+
data.tar.gz: 732c3b575790b37ec8570816f76d10af534b10cf3fb7ea217b7378f645c951f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d291974d5e815bb0a8d84f46d8f4e8509d0c70dc8dc76fa9d35d0f836ff7e55f3fe7f67c3cd0d5fc1452d2747b528ccca05c0de7d881042468b652a30824dd5e
|
7
|
+
data.tar.gz: e41c156691bb2c1c86381658104a4fb54fb4d6a22a8e1778b7dc6ce9f39d40183d649699c49b8c835815c16b073886d68d0988bf0a019c79d8fbb001cdc7133c
|
data/bin/foreman-node
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
# This is the external nodes script to allow Salt to retrieve info about a host
|
3
5
|
# from Foreman. It also uploads a node's grains to Foreman, if the setting is
|
4
6
|
# enabled.
|
5
7
|
|
6
8
|
require 'yaml'
|
7
9
|
|
8
|
-
|
9
|
-
SETTINGS = YAML.load_file(
|
10
|
+
settings_file = '/etc/salt/foreman.yaml'
|
11
|
+
SETTINGS = YAML.load_file(settings_file)
|
10
12
|
|
11
13
|
require 'net/http'
|
12
14
|
require 'net/https'
|
@@ -19,9 +21,13 @@ rescue LoadError
|
|
19
21
|
# Debian packaging guidelines state to avoid needing rubygems, so
|
20
22
|
# we only try to load it if the first require fails (for RPMs)
|
21
23
|
begin
|
22
|
-
|
24
|
+
begin
|
25
|
+
require 'rubygems'
|
26
|
+
rescue Exception
|
27
|
+
nil
|
28
|
+
end
|
23
29
|
require 'json'
|
24
|
-
rescue LoadError
|
30
|
+
rescue LoadError
|
25
31
|
puts 'You need the `json` gem to use the Foreman ENC script'
|
26
32
|
# code 1 is already used below
|
27
33
|
exit 2
|
@@ -37,20 +43,18 @@ def valid_hostname?(hostname)
|
|
37
43
|
end
|
38
44
|
|
39
45
|
def get_grains(minion)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
exit 1
|
53
|
-
end
|
46
|
+
grains = {
|
47
|
+
:name => minion,
|
48
|
+
:facts => plain_grains(minion).merge(:_timestamp => Time.now, :_type => 'foreman_salt')
|
49
|
+
}
|
50
|
+
|
51
|
+
grains[:facts][:operatingsystem] = grains[:facts]['os']
|
52
|
+
grains[:facts][:operatingsystemrelease] = grains[:facts]['osrelease']
|
53
|
+
|
54
|
+
JSON.pretty_generate(grains)
|
55
|
+
rescue Exception => e
|
56
|
+
puts "Could not get grains: #{e}"
|
57
|
+
exit 1
|
54
58
|
end
|
55
59
|
|
56
60
|
def plain_grains(minion)
|
@@ -61,7 +65,7 @@ def plain_grains(minion)
|
|
61
65
|
# so we have resort to python to extract the grains.
|
62
66
|
# Based on https://github.com/saltstack/salt/issues/9444
|
63
67
|
|
64
|
-
script = <<-EOF
|
68
|
+
script = <<-EOF
|
65
69
|
#!/usr/bin/env python
|
66
70
|
import json
|
67
71
|
import os
|
@@ -73,30 +77,28 @@ import salt.runner
|
|
73
77
|
if __name__ == '__main__':
|
74
78
|
__opts__ = salt.config.master_config(
|
75
79
|
os.environ.get('SALT_MASTER_CONFIG', '/etc/salt/master'))
|
76
|
-
runner = salt.runner.Runner(__opts__)
|
77
|
-
|
80
|
+
runner = salt.runner.Runner(__opts__)
|
81
|
+
|
78
82
|
stdout_bak = sys.stdout
|
79
83
|
with open(os.devnull, 'wb') as f:
|
80
|
-
sys.stdout = f
|
84
|
+
sys.stdout = f
|
81
85
|
ret = runner.cmd('cache.grains', ['#{minion}'])
|
82
86
|
sys.stdout = stdout_bak
|
83
87
|
|
84
88
|
print json.dumps(ret)
|
85
89
|
EOF
|
86
90
|
|
87
|
-
result = IO.popen('python 2>/dev/null',
|
91
|
+
result = IO.popen('python 2>/dev/null', 'r+') do |python|
|
88
92
|
python.write script
|
89
93
|
python.close_write
|
90
94
|
result = python.read
|
91
95
|
end
|
92
96
|
|
93
|
-
grains = JSON.
|
97
|
+
grains = JSON.parse(result)
|
94
98
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
raise 'No grains received from Salt master'
|
99
|
-
end
|
99
|
+
raise 'No grains received from Salt master' unless grains
|
100
|
+
|
101
|
+
plainify(grains[minion]).flatten.inject(&:merge)
|
100
102
|
end
|
101
103
|
|
102
104
|
def plainify(hash, prefix = nil)
|
@@ -126,33 +128,31 @@ def get_key(key, prefix)
|
|
126
128
|
end
|
127
129
|
|
128
130
|
def upload_grains(minion)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
if
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
res.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
144
|
-
end
|
145
|
-
if SETTINGS[:ssl_cert] && !SETTINGS[:ssl_cert].empty? && SETTINGS[:ssl_key] && !SETTINGS[:ssl_key].empty?
|
146
|
-
res.cert = OpenSSL::X509::Certificate.new(File.read(SETTINGS[:ssl_cert]))
|
147
|
-
res.key = OpenSSL::PKey::RSA.new(File.read(SETTINGS[:ssl_key]), nil)
|
148
|
-
end
|
149
|
-
elsif SETTINGS[:username] && SETTINGS[:password]
|
150
|
-
req.basic_auth(SETTINGS[:username], SETTINGS[:password])
|
131
|
+
grains = get_grains(minion)
|
132
|
+
uri = URI.parse("#{foreman_url}/api/hosts/facts")
|
133
|
+
req = Net::HTTP::Post.new(uri.request_uri)
|
134
|
+
req.add_field('Accept', 'application/json,version=2')
|
135
|
+
req.content_type = 'application/json'
|
136
|
+
req.body = grains
|
137
|
+
res = Net::HTTP.new(uri.host, uri.port)
|
138
|
+
res.use_ssl = uri.scheme == 'https'
|
139
|
+
if res.use_ssl?
|
140
|
+
if SETTINGS[:ssl_ca] && !SETTINGS[:ssl_ca].empty?
|
141
|
+
res.ca_file = SETTINGS[:ssl_ca]
|
142
|
+
res.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
143
|
+
else
|
144
|
+
res.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
151
145
|
end
|
152
|
-
|
153
|
-
|
154
|
-
|
146
|
+
if SETTINGS[:ssl_cert] && !SETTINGS[:ssl_cert].empty? && SETTINGS[:ssl_key] && !SETTINGS[:ssl_key].empty?
|
147
|
+
res.cert = OpenSSL::X509::Certificate.new(File.read(SETTINGS[:ssl_cert]))
|
148
|
+
res.key = OpenSSL::PKey::RSA.new(File.read(SETTINGS[:ssl_key]), nil)
|
149
|
+
end
|
150
|
+
elsif SETTINGS[:username] && SETTINGS[:password]
|
151
|
+
req.basic_auth(SETTINGS[:username], SETTINGS[:password])
|
155
152
|
end
|
153
|
+
res.start { |http| http.request(req) }
|
154
|
+
rescue Exception => e
|
155
|
+
raise "Could not send facts to Foreman: #{e}"
|
156
156
|
end
|
157
157
|
|
158
158
|
def enc(minion)
|
@@ -176,7 +176,7 @@ def enc(minion)
|
|
176
176
|
req.basic_auth(SETTINGS[:username], SETTINGS[:password])
|
177
177
|
end
|
178
178
|
|
179
|
-
res = http.start { |
|
179
|
+
res = http.start { |conn| conn.request(req) }
|
180
180
|
|
181
181
|
raise "Error retrieving node #{minion}: #{res.class}\nCheck Foreman's /var/log/foreman/production.log for more information." unless res.code == '200'
|
182
182
|
res.body
|
@@ -198,7 +198,7 @@ begin
|
|
198
198
|
result = enc(minion)
|
199
199
|
end
|
200
200
|
puts result
|
201
|
-
rescue => e
|
201
|
+
rescue Exception => e
|
202
202
|
puts "Couldn't retrieve ENC data: #{e}"
|
203
203
|
exit 1
|
204
204
|
end
|
data/bundler.d/salt.rb
CHANGED
@@ -1,44 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'net/http'
|
3
5
|
require 'net/https'
|
4
6
|
require 'uri'
|
5
7
|
|
6
|
-
module Proxy
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
8
|
+
module Proxy
|
9
|
+
module Salt
|
10
|
+
class ApiError < RuntimeError; end
|
11
|
+
class ConfigurationError < RuntimeError; end
|
12
|
+
|
13
|
+
# SaltStack's Rest API
|
14
|
+
class ApiRequest
|
15
|
+
attr_reader :url, :username, :password, :auth
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@url = Proxy::Salt::Plugin.settings.api_url
|
19
|
+
@auth = Proxy::Salt::Plugin.settings.api_auth
|
20
|
+
@username = Proxy::Salt::Plugin.settings.api_username
|
21
|
+
@password = Proxy::Salt::Plugin.settings.api_password
|
22
|
+
|
23
|
+
begin
|
24
|
+
URI.parse(url)
|
25
|
+
rescue URI::InvalidURIError => e
|
26
|
+
raise ConfigurationError.new("Invalid Salt api_url setting: #{e}")
|
27
|
+
end
|
22
28
|
end
|
23
|
-
end
|
24
29
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
30
|
+
def post(path, options = {})
|
31
|
+
uri = URI.parse(url)
|
32
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
33
|
+
http.use_ssl = uri.scheme == 'https'
|
34
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
35
|
+
path = [uri.path, path].join unless uri.path.empty?
|
36
|
+
|
37
|
+
request = Net::HTTP::Post.new(URI.join(uri.to_s, path).path)
|
38
|
+
request.add_field('Accept', 'application/json')
|
39
|
+
request.set_form_data(options.merge(:username => username, :password => password, :eauth => auth))
|
31
40
|
|
32
|
-
|
33
|
-
request.add_field('Accept', 'application/json')
|
34
|
-
request.set_form_data(options.merge(:username => username, :password => password, :eauth => auth))
|
41
|
+
response = http.request(request)
|
35
42
|
|
36
|
-
|
43
|
+
raise NotFound.new("Received 404 from Salt API: #{response.body}") if response.is_a?(Net::HTTPNotFound)
|
44
|
+
raise ApiError.new("Failed to query Salt API (#{response.code}): #{response.body}") unless response.is_a?(Net::HTTPOK)
|
37
45
|
|
38
|
-
|
39
|
-
JSON.load(response.body)
|
40
|
-
else
|
41
|
-
raise ApiError.new("Failed to query Salt API (#{response.code}): #{response.body}")
|
46
|
+
JSON.parse(response.body)
|
42
47
|
end
|
43
48
|
end
|
44
49
|
end
|
data/lib/smart_proxy_salt/cli.rb
CHANGED
@@ -1,163 +1,168 @@
|
|
1
|
-
|
2
|
-
extend ::Proxy::Log
|
3
|
-
extend ::Proxy::Util
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
|
6
|
-
def autosign_file
|
7
|
-
Proxy::Salt::Plugin.settings.autosign_file
|
8
|
-
end
|
3
|
+
require 'English'
|
9
4
|
|
10
|
-
|
11
|
-
|
5
|
+
module Proxy
|
6
|
+
module Salt
|
7
|
+
# CLI methods
|
8
|
+
module CLI
|
9
|
+
extend ::Proxy::Log
|
10
|
+
extend ::Proxy::Util
|
12
11
|
|
13
|
-
|
12
|
+
class << self
|
13
|
+
def autosign_file
|
14
|
+
Proxy::Salt::Plugin.settings.autosign_file
|
15
|
+
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
autosign.puts host unless found
|
18
|
-
autosign.close
|
17
|
+
def autosign_create(host)
|
18
|
+
FileUtils.touch(autosign_file) unless File.exist?(autosign_file)
|
19
19
|
|
20
|
-
|
21
|
-
logger.info result[:message]
|
22
|
-
result
|
23
|
-
end
|
20
|
+
autosign = open(autosign_file, File::RDWR)
|
24
21
|
|
25
|
-
|
26
|
-
|
22
|
+
found = false
|
23
|
+
autosign.each_line { |line| found = true if line.chomp == host }
|
24
|
+
autosign.puts host unless found
|
25
|
+
autosign.close
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
l
|
32
|
-
else
|
33
|
-
found = true
|
34
|
-
nil
|
27
|
+
result = { :message => "Added #{host} to autosign" }
|
28
|
+
logger.info result[:message]
|
29
|
+
result
|
35
30
|
end
|
36
|
-
end.uniq.compact
|
37
|
-
if found
|
38
|
-
autosign = open(autosign_file, File::TRUNC|File::RDWR)
|
39
|
-
autosign.write entries.join("\n")
|
40
|
-
autosign.write "\n"
|
41
|
-
autosign.close
|
42
|
-
result = {:message => "Removed #{host} from autosign"}
|
43
|
-
logger.info result[:message]
|
44
|
-
result
|
45
|
-
else
|
46
|
-
logger.info "Attempt to remove nonexistant client autosign for #{host}"
|
47
|
-
raise Proxy::Salt::NotFound, "Attempt to remove nonexistant client autosign for #{host}"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def autosign_list
|
52
|
-
return [] unless File.exist?(autosign_file)
|
53
|
-
File.read(autosign_file).split("\n").reject { |v|
|
54
|
-
v =~ /^\s*#.*|^$/ ## Remove comments and empty lines
|
55
|
-
}.map { |v|
|
56
|
-
v.chomp ## Strip trailing spaces
|
57
|
-
}
|
58
|
-
end
|
59
31
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
32
|
+
def autosign_remove(host)
|
33
|
+
raise "No such file #{autosign_file}" unless File.exist?(autosign_file)
|
34
|
+
|
35
|
+
found = false
|
36
|
+
entries = open(autosign_file, File::RDONLY).readlines.collect do |l|
|
37
|
+
if l.chomp != host
|
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
|
52
|
+
else
|
53
|
+
logger.info "Attempt to remove nonexistant client autosign for #{host}"
|
54
|
+
raise Proxy::Salt::NotFound.new("Attempt to remove nonexistant client autosign for #{host}")
|
55
|
+
end
|
56
|
+
end
|
78
57
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
58
|
+
def autosign_list
|
59
|
+
return [] unless File.exist?(autosign_file)
|
60
|
+
File.read(autosign_file).split("\n").reject do |v|
|
61
|
+
v =~ /^\s*#.*|^$/ ## Remove comments and empty lines
|
62
|
+
end.map(&:chomp)
|
63
|
+
end
|
84
64
|
|
85
|
-
|
65
|
+
def highstate(host)
|
66
|
+
find_salt_binaries
|
67
|
+
cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt, '--async', escape_for_shell(host), 'state.highstate']
|
68
|
+
logger.info "Will run state.highstate for #{host}. Full command: #{cmd.join(' ')}"
|
69
|
+
shell_command(cmd)
|
70
|
+
end
|
86
71
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
logger.warn "Failed to run salt-key: #{response}"
|
93
|
-
raise 'Execution of salt-key failed, check log files'
|
94
|
-
end
|
72
|
+
def key_delete(host)
|
73
|
+
find_salt_binaries
|
74
|
+
cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt_key, '--yes', '-d', escape_for_shell(host)]
|
75
|
+
shell_command(cmd)
|
76
|
+
end
|
95
77
|
|
96
|
-
|
78
|
+
def key_reject(host)
|
79
|
+
find_salt_binaries
|
80
|
+
cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt_key, '--include-accepted', '--yes', '-r', escape_for_shell(host)]
|
81
|
+
shell_command(cmd)
|
82
|
+
end
|
97
83
|
|
98
|
-
|
84
|
+
def key_accept(host)
|
85
|
+
find_salt_binaries
|
86
|
+
cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt_key, '--include-rejected', '--yes', '-a', escape_for_shell(host)]
|
87
|
+
shell_command(cmd)
|
88
|
+
end
|
99
89
|
|
100
|
-
|
101
|
-
|
90
|
+
def key_list
|
91
|
+
find_salt_binaries
|
92
|
+
command = "#{@sudo} -u #{Proxy::Salt::Plugin.settings.salt_command_user} #{@salt_key} --finger-all --output=json"
|
93
|
+
logger.debug "Executing #{command}"
|
94
|
+
response = `#{command}`
|
95
|
+
unless $CHILD_STATUS == 0
|
96
|
+
logger.warn "Failed to run salt-key: #{response}"
|
97
|
+
raise 'Execution of salt-key failed, check log files'
|
98
|
+
end
|
102
99
|
|
103
|
-
|
104
|
-
rejected_minions.keys.each { | rejected_minion | keys_hash[rejected_minion] = { 'state' => 'rejected', 'fingerprint' => rejected_minions[rejected_minion] } } if sk_hash.key? 'minions_rejected'
|
100
|
+
keys_hash = {}
|
105
101
|
|
106
|
-
|
107
|
-
unaccepted_minions.keys.each { | unaccepted_minion | keys_hash[unaccepted_minion] = { 'state' => 'unaccepted', 'fingerprint' => unaccepted_minions[unaccepted_minion] } } if sk_hash.key? 'minions_pre'
|
102
|
+
sk_hash = JSON.parse(response)
|
108
103
|
|
109
|
-
|
104
|
+
accepted_minions = sk_hash['minions']
|
105
|
+
accepted_minions.each_key { |accepted_minion| keys_hash[accepted_minion] = { 'state' => 'accepted', 'fingerprint' => accepted_minions[accepted_minion] } } if sk_hash.key? 'minions'
|
110
106
|
|
111
|
-
|
107
|
+
rejected_minions = sk_hash['minions_rejected']
|
108
|
+
rejected_minions.each_key { |rejected_minion| keys_hash[rejected_minion] = { 'state' => 'rejected', 'fingerprint' => rejected_minions[rejected_minion] } } if sk_hash.key? 'minions_rejected'
|
112
109
|
|
113
|
-
|
110
|
+
unaccepted_minions = sk_hash['minions_pre']
|
111
|
+
unaccepted_minions.each_key { |unaccepted_minion| keys_hash[unaccepted_minion] = { 'state' => 'unaccepted', 'fingerprint' => unaccepted_minions[unaccepted_minion] } } if sk_hash.key? 'minions_pre'
|
114
112
|
|
115
|
-
|
116
|
-
begin
|
117
|
-
c = popen(cmd)
|
118
|
-
unless wait
|
119
|
-
Process.detach(c.pid)
|
120
|
-
return 0
|
113
|
+
keys_hash
|
121
114
|
end
|
122
|
-
Process.wait(c.pid)
|
123
|
-
logger.info("Result: #{c.read}")
|
124
|
-
rescue Exception => e
|
125
|
-
logger.error("Exception '#{e}' when executing '#{cmd}'")
|
126
|
-
return false
|
127
|
-
end
|
128
|
-
logger.warn("Non-null exit code when executing '#{cmd}'") if $?.exitstatus != 0
|
129
|
-
$?.exitstatus == 0
|
130
|
-
end
|
131
115
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
116
|
+
private
|
117
|
+
|
118
|
+
def shell_command(cmd, wait = true)
|
119
|
+
begin
|
120
|
+
c = popen(cmd)
|
121
|
+
unless wait
|
122
|
+
Process.detach(c.pid)
|
123
|
+
return 0
|
124
|
+
end
|
125
|
+
Process.wait(c.pid)
|
126
|
+
logger.info("Result: #{c.read}")
|
127
|
+
rescue Exception => e
|
128
|
+
logger.error("Exception '#{e}' when executing '#{cmd}'")
|
129
|
+
return false
|
130
|
+
end
|
131
|
+
logger.warn("Non-null exit code when executing '#{cmd}'") unless $CHILD_STATUS.success?
|
132
|
+
$CHILD_STATUS.success?
|
133
|
+
end
|
146
134
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
135
|
+
def popen(cmd)
|
136
|
+
# 1.8.7 note: this assumes that cli options are space-separated
|
137
|
+
cmd = cmd.join(' ') unless RUBY_VERSION > '1.8.7'
|
138
|
+
logger.debug("about to execute: #{cmd}")
|
139
|
+
IO.popen(cmd)
|
140
|
+
end
|
153
141
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
142
|
+
def find_salt_binaries
|
143
|
+
@salt_key = which('salt-key')
|
144
|
+
unless File.exist?(@salt_key.to_s)
|
145
|
+
logger.warn 'unable to find salt-key binary'
|
146
|
+
raise 'unable to find salt-key'
|
147
|
+
end
|
148
|
+
logger.debug "Found salt-key at #{@salt_key}"
|
149
|
+
|
150
|
+
@salt = which('salt')
|
151
|
+
unless File.exist?(@salt.to_s)
|
152
|
+
logger.warn 'unable to find salt binary'
|
153
|
+
raise 'unable to find salt'
|
154
|
+
end
|
155
|
+
logger.debug "Found salt at #{@salt}"
|
156
|
+
|
157
|
+
@sudo = which('sudo')
|
158
|
+
unless File.exist?(@sudo)
|
159
|
+
logger.warn 'unable to find sudo binary'
|
160
|
+
raise 'Unable to find sudo'
|
161
|
+
end
|
162
|
+
logger.debug "Found sudo at #{@sudo}"
|
163
|
+
@sudo = @sudo.to_s
|
164
|
+
end
|
158
165
|
end
|
159
|
-
logger.debug "Found sudo at #{@sudo}"
|
160
|
-
@sudo = "#{@sudo}"
|
161
166
|
end
|
162
167
|
end
|
163
168
|
end
|
@@ -1,31 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'smart_proxy_salt/api_request'
|
3
5
|
|
4
|
-
module Proxy
|
5
|
-
|
6
|
-
|
6
|
+
module Proxy
|
7
|
+
module Salt
|
8
|
+
# Rest Salt API methods
|
9
|
+
module Rest
|
10
|
+
extend ::Proxy::Log
|
11
|
+
extend ::Proxy::Util
|
7
12
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
13
|
+
class << self
|
14
|
+
def environments_list
|
15
|
+
JSON.dump(Proxy::Salt::ApiRequest.new.post('/run', :fun => 'fileserver.envs', :client => 'runner')['return'][0])
|
16
|
+
end
|
12
17
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
+
def states_list(environment)
|
19
|
+
states = []
|
20
|
+
files = Proxy::Salt::ApiRequest.new.post('/run', :fun => 'fileserver.file_list',
|
21
|
+
:saltenv => environment,
|
22
|
+
:client => 'runner')['return'][0]
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
files.each do |file|
|
25
|
+
next unless file =~ /\.sls\Z/ && file != 'top.sls'
|
26
|
+
states << file.gsub('.sls', '').gsub('/init', '').chomp('/').tr('/', '.')
|
27
|
+
end
|
28
|
+
|
29
|
+
JSON.dump(states)
|
25
30
|
end
|
26
31
|
end
|
27
|
-
|
28
|
-
JSON.dump(states)
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
@@ -1,31 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'smart_proxy_salt/cli'
|
2
4
|
require 'smart_proxy_salt/rest'
|
3
5
|
|
4
|
-
module Proxy
|
5
|
-
|
6
|
+
module Proxy
|
7
|
+
# SmartProxy Salt
|
8
|
+
module Salt
|
9
|
+
class NotFound < RuntimeError; end
|
6
10
|
|
7
|
-
|
8
|
-
|
11
|
+
# Implement a SmartProxy plugin
|
12
|
+
class Plugin < ::Proxy::Plugin
|
13
|
+
plugin 'salt', Proxy::Salt::VERSION
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
15
|
+
default_settings :autosign_file => '/etc/salt/autosign.conf',
|
16
|
+
:salt_command_user => 'root',
|
17
|
+
:use_api => false
|
13
18
|
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
http_rackup_path File.expand_path('salt_http_config.ru', File.expand_path('../', __FILE__))
|
20
|
+
https_rackup_path File.expand_path('salt_http_config.ru', File.expand_path('../', __FILE__))
|
21
|
+
end
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
23
|
+
class << self
|
24
|
+
def method_missing(m, *args, &block)
|
25
|
+
# Use API, if it supports it, otherwise fallback to the CLI
|
26
|
+
if Proxy::Salt::Plugin.settings.use_api && Proxy::Salt::Rest.respond_to?(m)
|
27
|
+
Proxy::Salt::Rest.send(m, *args, &block)
|
28
|
+
elsif Proxy::Salt::CLI.respond_to?(m)
|
29
|
+
Proxy::Salt::CLI.send(m, *args, &block)
|
30
|
+
elsif !Proxy::Salt::Plugin.settings.use_api && Proxy::Salt::Rest.respond_to?(m)
|
31
|
+
raise NotImplementedError.new('You must enable the Salt API to use this feature.')
|
32
|
+
else
|
33
|
+
super
|
34
|
+
end
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -1,104 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'sinatra'
|
2
4
|
require 'smart_proxy_salt/salt'
|
3
5
|
|
4
|
-
module Proxy
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
module Proxy
|
7
|
+
module Salt
|
8
|
+
# Implement the SmartProxy API
|
9
|
+
class Api < ::Sinatra::Base
|
10
|
+
include ::Proxy::Log
|
11
|
+
helpers ::Proxy::Helpers
|
12
|
+
authorize_with_ssl_client
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
post '/autosign/:host' do
|
15
|
+
content_type :json
|
16
|
+
begin
|
17
|
+
Proxy::Salt.autosign_create(params[:host]).to_json
|
18
|
+
rescue Exception => e
|
19
|
+
log_halt 406, "Failed to create autosign for #{params[:host]}: #{e}"
|
20
|
+
end
|
16
21
|
end
|
17
|
-
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
delete '/autosign/:host' do
|
24
|
+
content_type :json
|
25
|
+
begin
|
26
|
+
Proxy::Salt.autosign_remove(params[:host]).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 for #{params[:host]}: #{e}"
|
31
|
+
end
|
27
32
|
end
|
28
|
-
end
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
get '/autosign' do
|
35
|
+
content_type :json
|
36
|
+
begin
|
37
|
+
Proxy::Salt.autosign_list.to_json
|
38
|
+
rescue Exception => e
|
39
|
+
log_halt 406, "Failed to list autosign entries: #{e}"
|
40
|
+
end
|
36
41
|
end
|
37
|
-
end
|
38
42
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
get '/environments' do
|
44
|
+
content_type :json
|
45
|
+
begin
|
46
|
+
Proxy::Salt.environments_list
|
47
|
+
rescue Exception => e
|
48
|
+
log_halt 406, "Failed to list environments: #{e}"
|
49
|
+
end
|
45
50
|
end
|
46
|
-
end
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
get '/environments/:environment' do
|
53
|
+
content_type :json
|
54
|
+
begin
|
55
|
+
Proxy::Salt.states_list params[:environment]
|
56
|
+
rescue Proxy::Salt::NotFound => e
|
57
|
+
log_halt 404, e.to_s
|
58
|
+
rescue Exception => e
|
59
|
+
log_halt 406, "Failed to list states for #{params[:host]}: #{e}"
|
60
|
+
end
|
56
61
|
end
|
57
|
-
end
|
58
62
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
post '/highstate/:host' do
|
64
|
+
content_type :json
|
65
|
+
begin
|
66
|
+
log_halt 500, "Failed salt run for #{params[:host]}: Check Log files" unless (result = Proxy::Salt.highstate(params[:host]))
|
67
|
+
result
|
68
|
+
rescue Exception => e
|
69
|
+
log_halt 406, "Failed salt run for #{params[:host]}: #{e}"
|
70
|
+
end
|
66
71
|
end
|
67
|
-
end
|
68
72
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
73
|
+
delete '/key/:host' do
|
74
|
+
content_type :json
|
75
|
+
begin
|
76
|
+
Proxy::Salt.key_delete(params[:host])
|
77
|
+
rescue Exception => e
|
78
|
+
log_halt 406, "Failed delete salt key for #{params[:host]}: #{e}"
|
79
|
+
end
|
75
80
|
end
|
76
|
-
end
|
77
81
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
82
|
+
post '/key/:host' do
|
83
|
+
content_type :json
|
84
|
+
begin
|
85
|
+
Proxy::Salt.key_accept(params[:host])
|
86
|
+
rescue Exception => e
|
87
|
+
log_halt 406, "Failed to accept salt key for #{params[:host]}: #{e}"
|
88
|
+
end
|
84
89
|
end
|
85
|
-
end
|
86
90
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
91
|
+
delete '/key/reject/:host' do
|
92
|
+
content_type :json
|
93
|
+
begin
|
94
|
+
Proxy::Salt.key_reject(params[:host])
|
95
|
+
rescue Exception => e
|
96
|
+
log_halt 406, "Failed to reject salt key for #{params[:host]}: #{e}"
|
97
|
+
end
|
93
98
|
end
|
94
|
-
end
|
95
99
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
100
|
+
get '/key' do
|
101
|
+
content_type :json
|
102
|
+
begin
|
103
|
+
Proxy::Salt.key_list.to_json
|
104
|
+
rescue Exception => e
|
105
|
+
log_halt 406, "Failed to list keys: #{e}"
|
106
|
+
end
|
102
107
|
end
|
103
108
|
end
|
104
109
|
end
|
data/lib/smart_proxy_salt.rb
CHANGED
@@ -0,0 +1,51 @@
|
|
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
|
@@ -0,0 +1,21 @@
|
|
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
|
@@ -0,0 +1,17 @@
|
|
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
|
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: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Moll
|
@@ -9,8 +9,148 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
12
|
+
date: 2019-08-02 00:00:00.000000000 Z
|
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
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: test-unit
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: mocha
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '1'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '1'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: webmock
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '1'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '1'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: rake
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '10'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '10'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rubocop
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - '='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 0.32.1
|
133
|
+
type: :development
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - '='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 0.32.1
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: rack-test
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - "~>"
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - "~>"
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
14
154
|
description: SaltStack Plug-In for Foreman's Smart Proxy
|
15
155
|
email: foreman-dev@googlegroups.com
|
16
156
|
executables:
|
@@ -34,11 +174,16 @@ files:
|
|
34
174
|
- lib/smart_proxy_salt/salt_api.rb
|
35
175
|
- lib/smart_proxy_salt/salt_http_config.ru
|
36
176
|
- lib/smart_proxy_salt/version.rb
|
177
|
+
- lib/smart_proxy_salt_core.rb
|
178
|
+
- lib/smart_proxy_salt_core/salt_runner.rb
|
179
|
+
- lib/smart_proxy_salt_core/salt_task_launcher.rb
|
180
|
+
- lib/smart_proxy_salt_core/version.rb
|
37
181
|
- sbin/upload-salt-reports
|
182
|
+
- settings.d/salt.saltfile.example
|
38
183
|
- settings.d/salt.yml.example
|
39
184
|
homepage: https://github.com/theforeman/smart_proxy_salt
|
40
185
|
licenses:
|
41
|
-
-
|
186
|
+
- GPL-3.0
|
42
187
|
metadata: {}
|
43
188
|
post_install_message:
|
44
189
|
rdoc_options: []
|