smart_proxy_openbolt 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +675 -0
- data/README.md +16 -0
- data/bundler.d/openbolt.rb +1 -0
- data/lib/smart_proxy_openbolt/api.rb +90 -0
- data/lib/smart_proxy_openbolt/error.rb +43 -0
- data/lib/smart_proxy_openbolt/executor.rb +91 -0
- data/lib/smart_proxy_openbolt/http_config.ru +5 -0
- data/lib/smart_proxy_openbolt/job.rb +131 -0
- data/lib/smart_proxy_openbolt/main.rb +326 -0
- data/lib/smart_proxy_openbolt/plugin.rb +40 -0
- data/lib/smart_proxy_openbolt/result.rb +72 -0
- data/lib/smart_proxy_openbolt/task_job.rb +73 -0
- data/lib/smart_proxy_openbolt/version.rb +5 -0
- data/lib/smart_proxy_openbolt.rb +2 -0
- data/settings.d/openbolt.yml +7 -0
- metadata +76 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'open3'
|
|
3
|
+
require 'smart_proxy_openbolt/executor'
|
|
4
|
+
require 'smart_proxy_openbolt/error'
|
|
5
|
+
require 'thread'
|
|
6
|
+
|
|
7
|
+
module Proxy::OpenBolt
|
|
8
|
+
extend ::Proxy::Util
|
|
9
|
+
extend ::Proxy::Log
|
|
10
|
+
|
|
11
|
+
TRANSPORTS = ['ssh', 'winrm']
|
|
12
|
+
# The key should be exactly the flag name passed to OpenBolt
|
|
13
|
+
# Type must be :boolean, :string, or an array of acceptable string values
|
|
14
|
+
# Transport must be an array of transport types it applies to. This is
|
|
15
|
+
# used to filter the openbolt options in the UI to only those relevant
|
|
16
|
+
# Defaults set here are in case the UI does not send any information for
|
|
17
|
+
# the key, and should only be present if this value is required
|
|
18
|
+
# Sensitive should be set to true in order to redact the value from logs
|
|
19
|
+
OPENBOLT_OPTIONS = {
|
|
20
|
+
'transport' => {
|
|
21
|
+
:type => TRANSPORTS,
|
|
22
|
+
:transport => TRANSPORTS,
|
|
23
|
+
:default => 'ssh',
|
|
24
|
+
:sensitive => false,
|
|
25
|
+
:description => 'The transport method to use for connecting to target hosts.',
|
|
26
|
+
},
|
|
27
|
+
'log-level' => {
|
|
28
|
+
:type => ['error', 'warning', 'info', 'debug', 'trace'],
|
|
29
|
+
:transport => ['ssh', 'winrm'],
|
|
30
|
+
:sensitive => false,
|
|
31
|
+
:description => 'Set the log level during OpenBolt execution.',
|
|
32
|
+
},
|
|
33
|
+
'verbose' => {
|
|
34
|
+
:type => :boolean,
|
|
35
|
+
:transport => ['ssh', 'winrm'],
|
|
36
|
+
:sensitive => false,
|
|
37
|
+
:description => 'Run the OpenBolt command with the --verbose flag. This prints additional information during OpenBolt execution and will print any out::verbose plan statements.',
|
|
38
|
+
},
|
|
39
|
+
'noop' => {
|
|
40
|
+
:type => :boolean,
|
|
41
|
+
:transport => ['ssh', 'winrm'],
|
|
42
|
+
:sensitive => false,
|
|
43
|
+
:description => 'Run the OpenBolt command with the --noop flag, which will make no changes to the target host.',
|
|
44
|
+
},
|
|
45
|
+
'tmpdir' => {
|
|
46
|
+
:type => :string,
|
|
47
|
+
:transport => ['ssh', 'winrm'],
|
|
48
|
+
:sensitive => false,
|
|
49
|
+
:description => 'Directory to use for temporary files on target hosts during OpenBolt execution.',
|
|
50
|
+
},
|
|
51
|
+
'user' => {
|
|
52
|
+
:type => :string,
|
|
53
|
+
:transport => ['ssh', 'winrm'],
|
|
54
|
+
:sensitive => false,
|
|
55
|
+
:description => 'Username used for SSH or WinRM authentication.',
|
|
56
|
+
},
|
|
57
|
+
'password' => {
|
|
58
|
+
:type => :string,
|
|
59
|
+
:transport => ['ssh', 'winrm'],
|
|
60
|
+
:sensitive => true,
|
|
61
|
+
:description => 'Password used for SSH or WinRM authentication.',
|
|
62
|
+
},
|
|
63
|
+
'host-key-check' => {
|
|
64
|
+
:type => :boolean,
|
|
65
|
+
:transport => ['ssh'],
|
|
66
|
+
:sensitive => false,
|
|
67
|
+
:description => 'When enabled, perform host key verification when connecting to targets over SSH.',
|
|
68
|
+
},
|
|
69
|
+
'private-key' => {
|
|
70
|
+
:type => :string,
|
|
71
|
+
:transport => ['ssh'],
|
|
72
|
+
:sensitive => false,
|
|
73
|
+
:description => 'Path on the smart proxy host to the private key used for SSH authentication. This key must be readable by the foreman-proxy user.',
|
|
74
|
+
},
|
|
75
|
+
'run-as' => {
|
|
76
|
+
:type => :string,
|
|
77
|
+
:transport => ['ssh'],
|
|
78
|
+
:sensitive => false,
|
|
79
|
+
:description => 'The user to run commands as on the target host. This requires that the user specified in the "user" option has permission to run commands as this user.',
|
|
80
|
+
},
|
|
81
|
+
'sudo-password' => {
|
|
82
|
+
:type => :string,
|
|
83
|
+
:transport => ['ssh'],
|
|
84
|
+
:sensitive => true,
|
|
85
|
+
:description => 'Password used for privilege escalation when using SSH.',
|
|
86
|
+
},
|
|
87
|
+
'ssl' => {
|
|
88
|
+
:type => :boolean,
|
|
89
|
+
:transport => ['winrm'],
|
|
90
|
+
:sensitive => false,
|
|
91
|
+
:description => 'Use SSL when connecting to hosts via WinRM.',
|
|
92
|
+
},
|
|
93
|
+
'ssl-verify' => {
|
|
94
|
+
:type => :boolean,
|
|
95
|
+
:transport => ['winrm'],
|
|
96
|
+
:sensitive => false,
|
|
97
|
+
:description => 'Verify remote host SSL certificate when connecting to hosts via WinRM.',
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
class << self
|
|
101
|
+
@@mutex = Mutex.new
|
|
102
|
+
|
|
103
|
+
def openbolt_options
|
|
104
|
+
OPENBOLT_OPTIONS.sort.to_h
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def executor
|
|
108
|
+
@executor ||= Proxy::OpenBolt::Executor.instance
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# /tasks or /tasks/reload
|
|
112
|
+
def tasks(reload: false)
|
|
113
|
+
# If we need to reload, only one instance of the reload
|
|
114
|
+
# should happen at once. Make others wait until it is
|
|
115
|
+
# finished.
|
|
116
|
+
@@mutex.synchronize do
|
|
117
|
+
@tasks = nil if reload
|
|
118
|
+
@tasks || reload_tasks
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def reload_tasks
|
|
123
|
+
task_data = {}
|
|
124
|
+
|
|
125
|
+
# Get a list of all tasks
|
|
126
|
+
command = "bolt task show --project #{Proxy::OpenBolt::Plugin.settings.environment_path} --format json"
|
|
127
|
+
stdout, stderr, status = openbolt(command)
|
|
128
|
+
unless status.exitstatus.zero?
|
|
129
|
+
raise Proxy::OpenBolt::CliError.new(
|
|
130
|
+
message: 'Error occurred when fetching tasks names.',
|
|
131
|
+
exitcode: status.exitstatus,
|
|
132
|
+
stdout: stdout,
|
|
133
|
+
stderr: stderr,
|
|
134
|
+
command: command,
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
task_names = []
|
|
138
|
+
begin
|
|
139
|
+
task_names = JSON.parse(stdout)['tasks'].map { |t| t[0] }
|
|
140
|
+
rescue JSON::ParserError => e
|
|
141
|
+
raise Proxy::OpenBolt::Error.new(
|
|
142
|
+
message: "Error occurred when parsing 'bolt task show' output.",
|
|
143
|
+
exception: e,
|
|
144
|
+
)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Get metadata for each task and put into @tasks
|
|
148
|
+
task_names.each do |name|
|
|
149
|
+
command = "bolt task show #{name} --project #{Proxy::OpenBolt::Plugin.settings.environment_path} --format json"
|
|
150
|
+
stdout, stderr, status = openbolt(command)
|
|
151
|
+
unless status.exitstatus.zero?
|
|
152
|
+
@tasks = nil
|
|
153
|
+
raise Proxy::OpenBolt::CliError.new(
|
|
154
|
+
message: "Error occurred when fetching task information for #{name}",
|
|
155
|
+
exitcode: status.exitstatus,
|
|
156
|
+
stdout: stdout,
|
|
157
|
+
stderr: stderr,
|
|
158
|
+
command: command,
|
|
159
|
+
)
|
|
160
|
+
end
|
|
161
|
+
metadata = {}
|
|
162
|
+
begin
|
|
163
|
+
metadata = JSON.parse(stdout)['metadata']
|
|
164
|
+
rescue Json::ParserError => e
|
|
165
|
+
@tasks = nil
|
|
166
|
+
raise Proxy::OpenBolt::Error.new(
|
|
167
|
+
message: "Error occurred when parsing 'bolt task show #{name}' output.",
|
|
168
|
+
exception: e,
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
if metadata.nil?
|
|
172
|
+
@tasks = nil
|
|
173
|
+
raise Proxy::OpenBolt::Error.new(
|
|
174
|
+
message: "Invalid metadata found for task #{name}",
|
|
175
|
+
output: output,
|
|
176
|
+
command: command,
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
task_data[name] = {
|
|
180
|
+
'description' => metadata['description'] || '',
|
|
181
|
+
'parameters' => metadata['parameters'] || {},
|
|
182
|
+
}
|
|
183
|
+
end
|
|
184
|
+
@tasks = task_data
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Normalize options and parameters, since the UI may send unspecified options as empty strings
|
|
188
|
+
def normalize_values(hash)
|
|
189
|
+
return {} unless hash.is_a?(Hash)
|
|
190
|
+
hash.transform_values do |value|
|
|
191
|
+
if value.is_a?(String)
|
|
192
|
+
value = value.strip
|
|
193
|
+
value = nil if value.empty?
|
|
194
|
+
elsif value.is_a?(Array)
|
|
195
|
+
value = value.map { |v| v.is_a?(String) ? v.strip : v }
|
|
196
|
+
value = nil if value.empty?
|
|
197
|
+
end
|
|
198
|
+
value
|
|
199
|
+
end.compact
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# /launch/task
|
|
203
|
+
def launch_task(data)
|
|
204
|
+
### Validation ###
|
|
205
|
+
unless data.is_a?(Hash)
|
|
206
|
+
raise Proxy::OpenBolt::Error.new(message: 'Data passed in to launch_task function is not a hash. This is most likely a bug in the smart_proxy_openbolt plugin. Please file an issue with the maintainers.').to_json
|
|
207
|
+
end
|
|
208
|
+
fields = ['name', 'parameters', 'targets', 'options']
|
|
209
|
+
unless fields.all? { |k| data.keys.include?(k) }
|
|
210
|
+
raise Proxy::OpenBolt::Error.new(message: "You must provide values for 'name', 'parameters', 'targets', and 'transport'.")
|
|
211
|
+
end
|
|
212
|
+
name = data['name']
|
|
213
|
+
params = data['parameters'] || {}
|
|
214
|
+
targets = data['targets']
|
|
215
|
+
options = data['options']
|
|
216
|
+
|
|
217
|
+
logger.info("Task: #{name}")
|
|
218
|
+
logger.info("Parameters: #{params.inspect}")
|
|
219
|
+
logger.info("Targets: #{targets.inspect}")
|
|
220
|
+
logger.info("Options: #{scrub(options, options.inspect.to_s)}")
|
|
221
|
+
|
|
222
|
+
# Validate name
|
|
223
|
+
raise Proxy::OpenBolt::Error.new(message: "You must provide a value for 'name'.") unless name.is_a?(String) && !name.empty?
|
|
224
|
+
raise Proxy::OpenBolt::Error.new(message: "Task #{name} not found.") unless tasks.keys.include?(name)
|
|
225
|
+
|
|
226
|
+
# Validate parameters
|
|
227
|
+
raise Proxy::OpenBolt::Error.new(message: "The 'parameters' value should be a hash.") unless params.is_a?(Hash)
|
|
228
|
+
missing = []
|
|
229
|
+
tasks[name]['parameters'].each do |k, v|
|
|
230
|
+
next if v['type'].start_with?('Optional[')
|
|
231
|
+
missing << k unless params.keys.include?(k)
|
|
232
|
+
end
|
|
233
|
+
raise Proxy::OpenBolt::Error.new(message: "Missing required parameters: #{missing}") unless missing.empty?
|
|
234
|
+
extra = params.keys - tasks[name]['parameters'].keys
|
|
235
|
+
raise Proxy::OpenBolt::Error.new(message: "Unknown parameters: #{extra}") unless extra.empty?
|
|
236
|
+
|
|
237
|
+
# Normalize parameters, ensuring blank values are not passed
|
|
238
|
+
params = normalize_values(params)
|
|
239
|
+
logger.info("Normalized parameters: #{params.inspect}")
|
|
240
|
+
|
|
241
|
+
# Validate targets
|
|
242
|
+
raise Proxy::OpenBolt::Error.new(message: "The 'targets' value should be a string or an array.'") unless targets.is_a?(String) || targets.is_a?(Array)
|
|
243
|
+
targets = targets.split(',').map { |t| t.strip }
|
|
244
|
+
raise Proxy::OpenBolt::Error.new(message: "The 'targets' value should not be empty.") if targets.empty?
|
|
245
|
+
|
|
246
|
+
options ||= {}
|
|
247
|
+
# Validate options
|
|
248
|
+
raise Proxy::OpenBolt::Error.new(message: "The 'options' value should be a hash.") unless options.is_a?(Hash)
|
|
249
|
+
extra = options.keys - OPENBOLT_OPTIONS.keys
|
|
250
|
+
raise Proxy::OpenBolt::Error.new(message: "Invalid options specified: #{extra}") unless extra.empty?
|
|
251
|
+
unknown = options.keys - OPENBOLT_OPTIONS.keys
|
|
252
|
+
raise Proxy::OpenBolt::Error.new(message: "Invalid options specified: #{unknown}") unless unknown.empty?
|
|
253
|
+
|
|
254
|
+
# Normalize options, removing blank values
|
|
255
|
+
options = normalize_values(options)
|
|
256
|
+
logger.info("Normalized options: #{scrub(options, options.inspect.to_s)}")
|
|
257
|
+
OPENBOLT_OPTIONS.each { |key, value| options[key] ||= value[:default] if value.key?(:default) }
|
|
258
|
+
logger.info("Options with required defaults: #{scrub(options, options.inspect.to_s)}")
|
|
259
|
+
|
|
260
|
+
# Validate option types
|
|
261
|
+
options = options.map do |key, value|
|
|
262
|
+
type = OPENBOLT_OPTIONS[key][:type]
|
|
263
|
+
value = value.nil? ? '' : value # Just in case
|
|
264
|
+
case type
|
|
265
|
+
when :boolean
|
|
266
|
+
if value.is_a?(String)
|
|
267
|
+
value = value.downcase.strip
|
|
268
|
+
raise Proxy::OpenBolt::Error.new(message: "Option #{key} must be a boolean 'true' or 'false'. Current value: #{value}") unless ['true', 'false'].include?(value)
|
|
269
|
+
value = value == 'true'
|
|
270
|
+
end
|
|
271
|
+
raise Proxy::OpenBolt::Error.new(message: "Option #{key} must be a boolean true for false. It appears to be #{value.class}.") unless [TrueClass, FalseClass].include?(value.class)
|
|
272
|
+
when :string
|
|
273
|
+
value = value.strip
|
|
274
|
+
raise Proxy::OpenBolt::Error.new(message: "Option #{key} must have a value when the option is specified.") if value.empty?
|
|
275
|
+
when Array
|
|
276
|
+
value = value.strip
|
|
277
|
+
raise Proxy::OpenBolt::Error.new(message: "Option #{key} must have one of the following values: #{OPENBOLT_OPTIONS[key][:type]}") unless OPENBOLT_OPTIONS[key][:type].include?(value)
|
|
278
|
+
end
|
|
279
|
+
[key, value]
|
|
280
|
+
end.to_h
|
|
281
|
+
logger.info("Final options: #{scrub(options, options.inspect.to_s)}")
|
|
282
|
+
|
|
283
|
+
### Run the task ###
|
|
284
|
+
task = TaskJob.new(name, params, options, targets)
|
|
285
|
+
id = executor.add_job(task)
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
id: id
|
|
289
|
+
}.to_json
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# /job/:id/status
|
|
293
|
+
def get_status(id)
|
|
294
|
+
return {
|
|
295
|
+
status: executor.status(id),
|
|
296
|
+
}.to_json
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# /job/:id/result
|
|
300
|
+
def get_result(id)
|
|
301
|
+
executor.result(id).to_json
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Anything that needs to run an OpenBolt CLI command should use this.
|
|
305
|
+
# At the moment, the full output is held in memory and passed back.
|
|
306
|
+
# If this becomes a problem, we can stream to disk and point to it.
|
|
307
|
+
#
|
|
308
|
+
# For task runs, the log goes to stderr and the result to stdout when
|
|
309
|
+
# --format json is specified. At some point, figure out how to make
|
|
310
|
+
# OpenBolt's logger log to a file instead without having to have a special
|
|
311
|
+
# project config file.
|
|
312
|
+
def openbolt(command)
|
|
313
|
+
env = { 'BOLT_GEM' => 'true', 'BOLT_DISABLE_ANALYTICS' => 'true' }
|
|
314
|
+
Open3.capture3(env, *command.split)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Probably needs to go in a utils class somewhere
|
|
318
|
+
# Used only for display text that may contain sensitive OpenBolt
|
|
319
|
+
# options values. Should to be used to pass anything to the CLI.
|
|
320
|
+
def scrub(options, text)
|
|
321
|
+
sensitive = options.select { |key, _| OPENBOLT_OPTIONS[key] && OPENBOLT_OPTIONS[key][:sensitive] }
|
|
322
|
+
sensitive.each { |_, value| text = text.gsub(value, '*****') }
|
|
323
|
+
text
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module Proxy::OpenBolt
|
|
4
|
+
class NotFound < RuntimeError; end
|
|
5
|
+
|
|
6
|
+
class LogPathValidator < ::Proxy::PluginValidators::Base
|
|
7
|
+
def validate!(settings)
|
|
8
|
+
logdir = settings[:log_dir]
|
|
9
|
+
unless Dir.exist?(logdir)
|
|
10
|
+
FileUtils.mkdir_p(logdir)
|
|
11
|
+
FileUtils.chown('foreman-proxy','foreman-proxy',logdir)
|
|
12
|
+
FileUtils.chmod(0750, logdir)
|
|
13
|
+
end
|
|
14
|
+
raise ::Proxy::Error::ConfigurationError("Could not create log dir at #{logdir}") unless Dir.exist?(logdir)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Plugin < ::Proxy::Plugin
|
|
19
|
+
plugin :openbolt, Proxy::OpenBolt::VERSION
|
|
20
|
+
|
|
21
|
+
expose_setting :enabled
|
|
22
|
+
|
|
23
|
+
capability :tasks
|
|
24
|
+
|
|
25
|
+
# TODO: Validate this is a valid path
|
|
26
|
+
default_settings(
|
|
27
|
+
environment_path: '/etc/puppetlabs/code/environments/production',
|
|
28
|
+
workers: 20,
|
|
29
|
+
concurrency: 100,
|
|
30
|
+
connect_timeout: 30,
|
|
31
|
+
log_dir: '/var/log/foreman-proxy/openbolt'
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
load_validators :log_path_validator => Proxy::OpenBolt::LogPathValidator
|
|
35
|
+
validate_readable :environment_path
|
|
36
|
+
validate :log_dir, :log_path_validator => true
|
|
37
|
+
|
|
38
|
+
https_rackup_path File.expand_path('http_config.ru', File.expand_path('../', __FILE__))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module Proxy::OpenBolt
|
|
4
|
+
class Result
|
|
5
|
+
|
|
6
|
+
attr_reader :command, :status, :value, :log, :message, :schema
|
|
7
|
+
|
|
8
|
+
# Result from the OpenBolt CLI with --format json looks like:
|
|
9
|
+
#
|
|
10
|
+
# { "items": [
|
|
11
|
+
# {
|
|
12
|
+
# "target": "certname1",
|
|
13
|
+
# "action": "task",
|
|
14
|
+
# "object": "task::name",
|
|
15
|
+
# "status": "success",
|
|
16
|
+
# "value": <whatever the task returns>
|
|
17
|
+
# },
|
|
18
|
+
# {
|
|
19
|
+
# "target": "certname2",
|
|
20
|
+
# ...
|
|
21
|
+
# }
|
|
22
|
+
# ],
|
|
23
|
+
# "target_count": 2,
|
|
24
|
+
# "elapsed_time": 3
|
|
25
|
+
# }
|
|
26
|
+
|
|
27
|
+
# This class will take the raw stdout, stderr, status.exitcode objects from a
|
|
28
|
+
# OpenBolt CLI invocation, and parse them accordingly. This should only be
|
|
29
|
+
# used with the --format json flag passed to the OpenBolt CLI, as that changes
|
|
30
|
+
# what data gets put on stdout and stderr.
|
|
31
|
+
#
|
|
32
|
+
# The "exception" parameter is to be able to handle an unexpected exception,
|
|
33
|
+
# and should generally not be used except where it is right now.
|
|
34
|
+
def initialize(command, stdout, stderr, exitcode)
|
|
35
|
+
@schema = 1
|
|
36
|
+
@command = command
|
|
37
|
+
if exitcode > 1
|
|
38
|
+
@message = "Command unexpectedly exited with code #{exitcode}"
|
|
39
|
+
@status = :exception
|
|
40
|
+
@value = "stderr:\n#{stderr}\nstdout:\n#{stdout}"
|
|
41
|
+
else
|
|
42
|
+
if exitcode == 1 && !stdout.start_with?('{')
|
|
43
|
+
@value = stdout
|
|
44
|
+
@status = :failure
|
|
45
|
+
@log = stderr
|
|
46
|
+
else
|
|
47
|
+
begin
|
|
48
|
+
@value = JSON.parse(stdout)
|
|
49
|
+
@status = exitcode == 0 ? :success : :failure
|
|
50
|
+
@log = stderr
|
|
51
|
+
rescue JSON::ParserError => e
|
|
52
|
+
@status = :exception
|
|
53
|
+
@message = e.message
|
|
54
|
+
@value = e.inspect
|
|
55
|
+
@log = stderr
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_json
|
|
62
|
+
{
|
|
63
|
+
'command': @command,
|
|
64
|
+
'status': @status,
|
|
65
|
+
'value': @value,
|
|
66
|
+
'log': @log,
|
|
67
|
+
'message': @message,
|
|
68
|
+
'schema': @schema,
|
|
69
|
+
}.to_json
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'smart_proxy_openbolt/error'
|
|
3
|
+
require 'smart_proxy_openbolt/job'
|
|
4
|
+
require 'smart_proxy_openbolt/main'
|
|
5
|
+
require 'smart_proxy_openbolt/result'
|
|
6
|
+
|
|
7
|
+
module Proxy::OpenBolt
|
|
8
|
+
class TaskJob < Job
|
|
9
|
+
attr_reader :targets
|
|
10
|
+
|
|
11
|
+
# NOTE: Validation of all objects initialized here should be done in
|
|
12
|
+
# main.rb BEFORE creating this object.
|
|
13
|
+
def initialize(name, parameters, options, targets)
|
|
14
|
+
super(name, parameters, options)
|
|
15
|
+
@targets = targets
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def execute
|
|
19
|
+
command = get_cmd
|
|
20
|
+
stdout, stderr, status = Proxy::OpenBolt.openbolt(command)
|
|
21
|
+
Proxy::OpenBolt::Result.new(
|
|
22
|
+
Proxy::OpenBolt.scrub(@options, command),
|
|
23
|
+
Proxy::OpenBolt.scrub(@options, stdout),
|
|
24
|
+
Proxy::OpenBolt.scrub(@options, stderr),
|
|
25
|
+
status.exitstatus
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_cmd
|
|
30
|
+
# Service config settings (not per-task)
|
|
31
|
+
concurrency = "--concurrency=#{Proxy::OpenBolt::Plugin.settings.concurrency}"
|
|
32
|
+
connect_timeout = "--connect-timeout=#{Proxy::OpenBolt::Plugin.settings.connect_timeout}"
|
|
33
|
+
"bolt task run #{@name} --targets #{@targets.join(',')} --no-save-rerun #{concurrency} #{connect_timeout} --project #{Proxy::OpenBolt::Plugin.settings.environment_path} --format json --no-color #{parse_options} #{parse_parameters}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse_parameters
|
|
37
|
+
params = []
|
|
38
|
+
@parameters.each do |key, value|
|
|
39
|
+
if value.is_a?(Array)
|
|
40
|
+
params << "#{key}='#{value}'"
|
|
41
|
+
elsif value.is_a?(Hash)
|
|
42
|
+
params << "#{key}='#{value.to_json}'"
|
|
43
|
+
else
|
|
44
|
+
params << "#{key}=#{value}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
params.join(' ')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_options
|
|
51
|
+
opt_str = ''
|
|
52
|
+
if @options
|
|
53
|
+
@options.each do |key, value|
|
|
54
|
+
# --noop doesn't have a --[no-] prefix
|
|
55
|
+
next if key == 'noop' && value.is_a?(FalseClass)
|
|
56
|
+
# For some mindboggling reason, there are both '--log-level trace'
|
|
57
|
+
# and '--trace' options. We only expose log level, so just
|
|
58
|
+
# tack on --trace if that's what we find.
|
|
59
|
+
if key == 'log-level' && value == 'trace'
|
|
60
|
+
opt_str += "--log-level=trace --trace "
|
|
61
|
+
elsif value.is_a?(TrueClass)
|
|
62
|
+
opt_str += "--#{key} "
|
|
63
|
+
elsif value.is_a?(FalseClass)
|
|
64
|
+
opt_str += "--no-#{key} "
|
|
65
|
+
else
|
|
66
|
+
opt_str += "--#{key}=#{value} "
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
opt_str
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: smart_proxy_openbolt
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Overlook InfraTech
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: concurrent-ruby
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.3'
|
|
19
|
+
- - ">="
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 1.3.5
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - "~>"
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.3'
|
|
29
|
+
- - ">="
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: 1.3.5
|
|
32
|
+
description: Uses the OpenBolt CLI tool to run tasks and plans in Foreman
|
|
33
|
+
email: contact@overlookinfratech.com
|
|
34
|
+
executables: []
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files:
|
|
37
|
+
- LICENSE
|
|
38
|
+
- README.md
|
|
39
|
+
files:
|
|
40
|
+
- LICENSE
|
|
41
|
+
- README.md
|
|
42
|
+
- bundler.d/openbolt.rb
|
|
43
|
+
- lib/smart_proxy_openbolt.rb
|
|
44
|
+
- lib/smart_proxy_openbolt/api.rb
|
|
45
|
+
- lib/smart_proxy_openbolt/error.rb
|
|
46
|
+
- lib/smart_proxy_openbolt/executor.rb
|
|
47
|
+
- lib/smart_proxy_openbolt/http_config.ru
|
|
48
|
+
- lib/smart_proxy_openbolt/job.rb
|
|
49
|
+
- lib/smart_proxy_openbolt/main.rb
|
|
50
|
+
- lib/smart_proxy_openbolt/plugin.rb
|
|
51
|
+
- lib/smart_proxy_openbolt/result.rb
|
|
52
|
+
- lib/smart_proxy_openbolt/task_job.rb
|
|
53
|
+
- lib/smart_proxy_openbolt/version.rb
|
|
54
|
+
- settings.d/openbolt.yml
|
|
55
|
+
homepage: http://github.com/overlookinfra/smart_proxy_openbolt
|
|
56
|
+
licenses:
|
|
57
|
+
- GPL-3.0-only
|
|
58
|
+
metadata: {}
|
|
59
|
+
rdoc_options: []
|
|
60
|
+
require_paths:
|
|
61
|
+
- lib
|
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '3.0'
|
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
|
+
requirements:
|
|
69
|
+
- - ">="
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '0'
|
|
72
|
+
requirements: []
|
|
73
|
+
rubygems_version: 3.6.9
|
|
74
|
+
specification_version: 4
|
|
75
|
+
summary: Smart Proxy plugin for OpenBolt integration
|
|
76
|
+
test_files: []
|