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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85ca85442234e6c70fe35cf796b48921c051f2ac11696ad79b475cf326e3415a
4
- data.tar.gz: f7fe7ba6d9028def717e0e0f6250d2d50cac55c8512ed7481d9a58733cb754f2
3
+ metadata.gz: 978fe04cd0b2ee65be99fc84f20599f295d51498b8aa1ba0541cc9569f56ee9a
4
+ data.tar.gz: 732c3b575790b37ec8570816f76d10af534b10cf3fb7ea217b7378f645c951f9
5
5
  SHA512:
6
- metadata.gz: 68630d6978885cfbacc7651ea230b2596d915c85ff1373d0be8f4db2e3cb984479c2ddadeb1f43b604b769c154cfd77d7186247dbd8ac8f77d560f17edd2c12a
7
- data.tar.gz: ae6a97eccbe59f9c33a0d49724ab3b58c09190752cfe39223a0afb0089631084148b9a5674198cefc3cfb96fb8370479dc8967385260aaacc31fed19f6ec74c3
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
- $settings_file = '/etc/salt/foreman.yaml'
9
- SETTINGS = YAML.load_file($settings_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
- require 'rubygems' rescue nil
24
+ begin
25
+ require 'rubygems'
26
+ rescue Exception
27
+ nil
28
+ end
23
29
  require 'json'
24
- rescue LoadError => e
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
- begin
41
- grains = {
42
- :name => minion,
43
- :facts => plain_grains(minion).merge({:_timestamp => Time.now, :_type => 'foreman_salt'})
44
- }
45
-
46
- grains[:facts][:operatingsystem] = grains[:facts]['os']
47
- grains[:facts][:operatingsystemrelease] = grains[:facts]['osrelease']
48
-
49
- JSON.pretty_generate(grains)
50
- rescue => e
51
- puts "Could not get grains: #{e}"
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', mode='r+') do |python|
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.load(result)
97
+ grains = JSON.parse(result)
94
98
 
95
- if grains
96
- plainify(grains[minion]).flatten.inject(&:merge)
97
- else
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
- begin
130
- grains = get_grains(minion)
131
- uri = URI.parse("#{foreman_url}/api/hosts/facts")
132
- req = Net::HTTP::Post.new(uri.request_uri)
133
- req.add_field('Accept', 'application/json,version=2' )
134
- req.content_type = 'application/json'
135
- req.body = grains
136
- res = Net::HTTP.new(uri.host, uri.port)
137
- res.use_ssl = uri.scheme == 'https'
138
- if res.use_ssl?
139
- if SETTINGS[:ssl_ca] && !SETTINGS[:ssl_ca].empty?
140
- res.ca_file = SETTINGS[:ssl_ca]
141
- res.verify_mode = OpenSSL::SSL::VERIFY_PEER
142
- else
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
- res.start { |http| http.request(req) }
153
- rescue => e
154
- raise "Could not send facts to Foreman: #{e}"
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 { |http| http.request(req) }
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 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem 'smart_proxy_salt'
@@ -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::Salt
7
- class ApiError < RuntimeError; end
8
-
9
- class ApiRequest
10
- attr_reader :url, :username, :password, :auth
11
-
12
- def initialize
13
- @url = Proxy::Salt::Plugin.settings.api_url
14
- @auth = Proxy::Salt::Plugin.settings.api_auth
15
- @username = Proxy::Salt::Plugin.settings.api_username
16
- @password = Proxy::Salt::Plugin.settings.api_password
17
-
18
- begin
19
- URI.parse(url)
20
- rescue URI::InvalidURIError => e
21
- raise ConfigurationError.new("Invalid Salt api_url setting: #{e}")
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
- def post(path, options = {})
26
- uri = URI.parse(url)
27
- http = Net::HTTP.new(uri.host, uri.port)
28
- http.use_ssl = uri.scheme == 'https'
29
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
30
- path = [uri.path, path].join('/') unless uri.path.empty?
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
- request = Net::HTTP::Post.new(URI.join(uri.to_s, path).path)
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
- response = http.request(request)
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
- if response.is_a? Net::HTTPOK
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
@@ -1,163 +1,168 @@
1
- module Proxy::Salt::CLI
2
- extend ::Proxy::Log
3
- extend ::Proxy::Util
1
+ # frozen_string_literal: true
4
2
 
5
- class << self
6
- def autosign_file
7
- Proxy::Salt::Plugin.settings.autosign_file
8
- end
3
+ require 'English'
9
4
 
10
- def autosign_create(host)
11
- FileUtils.touch(autosign_file) unless File.exist?(autosign_file)
5
+ module Proxy
6
+ module Salt
7
+ # CLI methods
8
+ module CLI
9
+ extend ::Proxy::Log
10
+ extend ::Proxy::Util
12
11
 
13
- autosign = open(autosign_file, File::RDWR)
12
+ class << self
13
+ def autosign_file
14
+ Proxy::Salt::Plugin.settings.autosign_file
15
+ end
14
16
 
15
- found = false
16
- autosign.each_line { |line| found = true if line.chomp == host }
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
- result = {:message => "Added #{host} to autosign"}
21
- logger.info result[:message]
22
- result
23
- end
20
+ autosign = open(autosign_file, File::RDWR)
24
21
 
25
- def autosign_remove(host)
26
- raise "No such file #{autosign_file}" unless File.exists?(autosign_file)
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
- found = false
29
- entries = open(autosign_file, File::RDONLY).readlines.collect do |l|
30
- if l.chomp != host
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
- def highstate(host)
61
- find_salt_binaries
62
- cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt, '--async', escape_for_shell(host), 'state.highstate']
63
- logger.info "Will run state.highstate for #{host}. Full command: #{cmd.join(' ')}"
64
- shell_command(cmd)
65
- end
66
-
67
- def key_delete(host)
68
- find_salt_binaries
69
- cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt_key, '--yes', '-d', escape_for_shell(host)]
70
- shell_command(cmd)
71
- end
72
-
73
- def key_reject(host)
74
- find_salt_binaries
75
- cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt_key, '--include-accepted', '--yes', '-r', escape_for_shell(host)]
76
- shell_command(cmd)
77
- end
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
- def key_accept(host)
80
- find_salt_binaries
81
- cmd = [@sudo, '-u', Proxy::Salt::Plugin.settings.salt_command_user, @salt_key, '--include-rejected', '--yes', '-a', escape_for_shell(host)]
82
- shell_command(cmd)
83
- end
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
- def key_list
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
- find_salt_binaries
88
- command = "#{@sudo} -u #{Proxy::Salt::Plugin.settings.salt_command_user} #{@salt_key} --finger-all --output=json"
89
- logger.debug "Executing #{command}"
90
- response = `#{command}`
91
- unless $? == 0
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
- keys_hash = {}
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
- sk_hash = JSON.parse(response)
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
- accepted_minions = sk_hash['minions']
101
- accepted_minions.keys.each { | accepted_minion | keys_hash[accepted_minion] = { 'state' => 'accepted', 'fingerprint' => accepted_minions[accepted_minion]} } if sk_hash.key? 'minions'
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
- rejected_minions = sk_hash['minions_rejected']
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
- unaccepted_minions = sk_hash['minions_pre']
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
- keys_hash
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
- end
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
- private
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
- def shell_command(cmd, wait = true)
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
- def popen(cmd)
133
- # 1.8.7 note: this assumes that cli options are space-separated
134
- cmd = cmd.join(' ') unless RUBY_VERSION > '1.8.7'
135
- logger.debug("about to execute: #{cmd}")
136
- IO.popen(cmd)
137
- end
138
-
139
- def find_salt_binaries
140
- @salt_key = which('salt-key')
141
- unless File.exists?("#{@salt_key}")
142
- logger.warn 'unable to find salt-key binary'
143
- raise 'unable to find salt-key'
144
- end
145
- logger.debug "Found salt-key at #{@salt_key}"
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
- @salt = which('salt')
148
- unless File.exists?("#{@salt}")
149
- logger.warn 'unable to find salt binary'
150
- raise 'unable to find salt'
151
- end
152
- logger.debug "Found salt at #{@salt}"
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
- @sudo = which('sudo')
155
- unless File.exists?(@sudo)
156
- logger.warn 'unable to find sudo binary'
157
- raise 'Unable to find sudo'
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::Salt::Rest
5
- extend ::Proxy::Log
6
- extend ::Proxy::Util
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
- class << self
9
- def environments_list
10
- JSON.dump(Proxy::Salt::ApiRequest.new.post('/run', :fun => 'fileserver.envs', :client => 'runner')['return'][0])
11
- end
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
- def states_list(environment)
14
- states = []
15
- files = Proxy::Salt::ApiRequest.new.post('/run', :fun => 'fileserver.file_list',
16
- :saltenv => environment,
17
- :client => 'runner')['return'][0]
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
- files.each do |file|
20
- if file =~ /\.sls\Z/ && file != "top.sls"
21
- states << file.gsub('.sls', '').
22
- gsub('/init', '').
23
- chomp('/').
24
- gsub('/', '.')
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::Salt
5
- class NotFound < RuntimeError; end
6
+ module Proxy
7
+ # SmartProxy Salt
8
+ module Salt
9
+ class NotFound < RuntimeError; end
6
10
 
7
- class Plugin < ::Proxy::Plugin
8
- plugin 'salt', Proxy::Salt::VERSION
11
+ # Implement a SmartProxy plugin
12
+ class Plugin < ::Proxy::Plugin
13
+ plugin 'salt', Proxy::Salt::VERSION
9
14
 
10
- default_settings :autosign_file => '/etc/salt/autosign.conf',
11
- :salt_command_user => 'root',
12
- :use_api => false
15
+ default_settings :autosign_file => '/etc/salt/autosign.conf',
16
+ :salt_command_user => 'root',
17
+ :use_api => false
13
18
 
14
- http_rackup_path File.expand_path('salt_http_config.ru', File.expand_path('../', __FILE__))
15
- https_rackup_path File.expand_path('salt_http_config.ru', File.expand_path('../', __FILE__))
16
- end
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
- class << self
19
- def method_missing(m, *args, &block)
20
- # Use API, if it supports it, otherwise fallback to the CLI
21
- if Proxy::Salt::Plugin.settings.use_api && Proxy::Salt::Rest.respond_to?(m)
22
- Proxy::Salt::Rest.send(m, *args, &block)
23
- elsif Proxy::Salt::CLI.respond_to?(m)
24
- Proxy::Salt::CLI.send(m, *args, &block)
25
- elsif !Proxy::Salt::Plugin.settings.use_api && Proxy::Salt::Rest.respond_to?(m)
26
- raise NotImplementedError, 'You must enable the Salt API to use this feature.'
27
- else
28
- super
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::Salt
5
- class Api < ::Sinatra::Base
6
- include ::Proxy::Log
7
- helpers ::Proxy::Helpers
8
- authorize_with_ssl_client
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
- post '/autosign/:host' do
11
- content_type :json
12
- begin
13
- Proxy::Salt.autosign_create(params[:host]).to_json
14
- rescue => e
15
- log_halt 406, "Failed to create autosign for #{params[:host]}: #{e}"
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
- delete '/autosign/:host' do
20
- content_type :json
21
- begin
22
- Proxy::Salt.autosign_remove(params[:host]).to_json
23
- rescue Proxy::Salt::NotFound => e
24
- log_halt 404, "#{e}"
25
- rescue => e
26
- log_halt 406, "Failed to remove autosign for #{params[:host]}: #{e}"
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
- get '/autosign' do
31
- content_type :json
32
- begin
33
- Proxy::Salt::autosign_list.to_json
34
- rescue => e
35
- log_halt 406, "Failed to list autosign entries: #{e}"
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
- get '/environments' do
40
- content_type :json
41
- begin
42
- Proxy::Salt::environments_list
43
- rescue => e
44
- log_halt 406, "Failed to list environments: #{e}"
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
- get '/environments/:environment' do
49
- content_type :json
50
- begin
51
- Proxy::Salt::states_list params[:environment]
52
- rescue Proxy::Salt::NotFound => e
53
- log_halt 404, "#{e}"
54
- rescue => e
55
- log_halt 406, "Failed to list states for #{params[:host]}: #{e}"
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
- post '/highstate/:host' do
60
- content_type :json
61
- begin
62
- log_halt 500, "Failed salt run for #{params[:host]}: Check Log files" unless (result = Proxy::Salt.highstate(params[:host]))
63
- result
64
- rescue => e
65
- log_halt 406, "Failed salt run for #{params[:host]}: #{e}"
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
- delete '/key/:host' do
70
- content_type :json
71
- begin
72
- Proxy::Salt.key_delete(params[:host])
73
- rescue => e
74
- log_halt 406, "Failed delete salt key for #{params[:host]}: #{e}"
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
- post '/key/:host' do
79
- content_type :json
80
- begin
81
- Proxy::Salt::key_accept(params[:host])
82
- rescue => e
83
- log_halt 406, "Failed to accept salt key for #{params[:host]}: #{e}"
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
- delete '/key/reject/:host' do
88
- content_type :json
89
- begin
90
- Proxy::Salt::key_reject(params[:host])
91
- rescue => e
92
- log_halt 406, "Failed to reject salt key for #{params[:host]}: #{e}"
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
- get '/key' do
97
- content_type :json
98
- begin
99
- Proxy::Salt::key_list.to_json
100
- rescue => e
101
- log_halt 406, "Failed to list keys: #{e}"
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'smart_proxy_salt/salt_api'
2
4
  map '/salt' do
3
5
  run Proxy::Salt::Api
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Proxy
4
+ # Salt module
2
5
  module Salt
3
- VERSION = '2.1.9'
6
+ VERSION = '3.0.0'
4
7
  end
5
8
  end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'smart_proxy_salt/version'
2
4
  require 'smart_proxy_salt/salt'
@@ -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,3 @@
1
+ module SmartProxySaltCore
2
+ VERSION = '0.0.1'.freeze
3
+ 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
@@ -0,0 +1,3 @@
1
+ salt:
2
+ log_file: /var/log/foreman-proxy/salt.log
3
+ log_level_logfile: debug
@@ -9,3 +9,4 @@
9
9
  :api_auth: pam
10
10
  :api_username: saltuser
11
11
  :api_password: password
12
+ # :saltfile: '/etc/foreman-proxy/settings.d/salt.saltfile'
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: 2.1.9
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: 2018-05-06 00:00:00.000000000 Z
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
- - GPLv3
186
+ - GPL-3.0
42
187
  metadata: {}
43
188
  post_install_message:
44
189
  rdoc_options: []