smart_proxy_ansible 3.6.0 → 3.7.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: c94b0334ddc75bab338d79841ac4cc17d813b5f40c5e06c6f739cfd531d88320
4
- data.tar.gz: bcf96f811ef107f4737bfa5f37ee9f2ac757bf50ac4a7b64b3e8c31397f60fc0
3
+ metadata.gz: 5485a4ab8fc4f53bc9cacdcabf3fb6bd9181e2389686c4da6dee9d6586b54cf2
4
+ data.tar.gz: fbd95ce8ef2496a94d92cb51d50d6c32eb2ae0dc9cd5bb5620eb0aaf93f9bb88
5
5
  SHA512:
6
- metadata.gz: f4f41d9c0a2875c5f33d245d8fec3fb8a15b457102c344bd20bd801e54911de91eabdced4816c968b3ce655ebae3e9292a822f2d75d57553c073aee93e4ac1a6
7
- data.tar.gz: 8fbbf65cb55fc9082ed7194fee478f8334a15a90a1ab3302fef22cde827e920dce29aac775b374b2ba5d651240425c233a08ec72fe6404329137b148aa453e85
6
+ metadata.gz: f13d23b788defd429202867247a12e0ee538990a77076b83d2db304e2a147a58f989b0651c787c168c098451bd4ef7db31ea4289597b2bbdb75028fd61fff5a8
7
+ data.tar.gz: d8694581e202966086f7766fbacc66cfebcaa4b038d50e66ab644db4776bab48279b80a513ce74db5530444672f6c43d48841938f7ec6f06421bd8e945466116
@@ -23,11 +23,13 @@ module Proxy::Ansible
23
23
  @rex_command = action_input[:remote_execution_command]
24
24
  @check_mode = action_input[:check_mode]
25
25
  @job_check_mode = action_input[:job_check_mode]
26
+ @diff_mode = action_input[:diff_mode]
26
27
  @tags = action_input[:tags]
27
28
  @tags_flag = action_input[:tags_flag]
28
29
  @passphrase = action_input['secrets']['key_passphrase']
29
30
  @execution_timeout_interval = action_input[:execution_timeout_interval]
30
31
  @cleanup_working_dirs = action_input.fetch(:cleanup_working_dirs, true)
32
+ prune_known_hosts_on_first_execution
31
33
  end
32
34
 
33
35
  def start
@@ -128,7 +130,7 @@ module Proxy::Ansible
128
130
 
129
131
  def handle_host_event(hostname, event)
130
132
  log_event("for host: #{hostname.inspect}", event)
131
- publish_data_for(hostname, event['stdout'] + "\n", 'stdout') if event['stdout']
133
+ publish_data_for(hostname, event['stdout'] + "\n", 'stdout', id: event['uuid'], timestamp: parse_timestamp(event['created'])) if event['stdout']
132
134
  case event['event']
133
135
  when 'runner_on_ok'
134
136
  publish_exit_status_for(hostname, 0) if @exit_statuses[hostname].nil?
@@ -154,11 +156,11 @@ module Proxy::Ansible
154
156
  host = inventory_hosts.find { |host| row =~ /#{host}/ }
155
157
  line = row + "\n"
156
158
  unless host
157
- broadcast_data(line, 'stdout')
159
+ broadcast_data(line, 'stdout', id: event['uuid'], timestamp: parse_timestamp(event['created']))
158
160
  next
159
161
  end
160
162
 
161
- publish_data_for(host, line, 'stdout')
163
+ publish_data_for(host, line, 'stdout', id: event['uuid'], timestamp: parse_timestamp(event['created']))
162
164
 
163
165
  # If the task has been rescued, it won't consider a failure
164
166
  if @exit_statuses[host].to_i != 0 && failures[host].to_i <= 0 && unreachable[host].to_i <= 0 && rescued[host].to_i > 0
@@ -166,7 +168,7 @@ module Proxy::Ansible
166
168
  end
167
169
  end
168
170
  else
169
- broadcast_data(event['stdout'] + "\n", 'stdout')
171
+ broadcast_data(event['stdout'] + "\n", 'stdout', id: event['uuid'], timestamp: parse_timestamp(event['created']))
170
172
  end
171
173
 
172
174
  # If the run ends early due to an error - fail all other tasks
@@ -214,7 +216,7 @@ module Proxy::Ansible
214
216
  end
215
217
 
216
218
  def cmdline
217
- cmd_args = [tags_cmd, check_cmd].reject(&:empty?)
219
+ cmd_args = [tags_cmd, check_cmd, diff_cmd].reject(&:empty?)
218
220
  return nil unless cmd_args.any?
219
221
  cmd_args.join(' ')
220
222
  end
@@ -232,6 +234,10 @@ module Proxy::Ansible
232
234
  end
233
235
  end
234
236
 
237
+ def diff_cmd
238
+ diff_mode? ? '"--diff"' : ''
239
+ end
240
+
235
241
  def verbosity
236
242
  '-' + 'v' * @verbosity_level.to_i
237
243
  end
@@ -248,6 +254,10 @@ module Proxy::Ansible
248
254
  @job_check_mode == true
249
255
  end
250
256
 
257
+ def diff_mode?
258
+ @diff_mode == true
259
+ end
260
+
251
261
  def prepare_directory_structure
252
262
  inner = %w[inventory project env].map { |part| File.join(@root, part) }
253
263
  ([@root] + inner).each do |path|
@@ -261,21 +271,40 @@ module Proxy::Ansible
261
271
  logger.debug("[foreman_ansible] - handling event #{description}: #{JSON.pretty_generate(event)}") if logger.level <= ::Logger::DEBUG
262
272
  end
263
273
 
264
- # Each per-host task has inventory only for itself, we must
265
- # collect all the partial inventories into one large inventory
266
- # containing all the hosts.
274
+ # Rebuilds a unified Ansible inventory from multiple per-host inventories.
275
+ # @param input [Hash] The input hash mapping hostnames to inventory data.
276
+ # @return [Hash] The merged inventory.
267
277
  def rebuild_inventory(input)
268
- action_inputs = input.values.map { |hash| hash[:input][:action_input] }
269
- inventories = action_inputs.map { |hash| hash[:ansible_inventory] }
270
- host_vars = inventories.map { |i| i['_meta']['hostvars'] }.reduce({}) do |acc, hosts|
271
- hosts.reduce(acc) do |inner_acc, (hostname, vars)|
278
+ action_inputs = input.values.map { |entry| entry['input']['action_input'] }
279
+ inventories = action_inputs.map { |action_input| action_input['ansible_inventory'] }
280
+ first_execution_by_host = action_inputs.to_h { |action_input| [action_input['name'], action_input['first_execution']] }
281
+
282
+ host_vars = merge_hostvars_from_inventories(inventories)
283
+
284
+ # Use the first inventory's group vars as a base, fallback to empty hash if missing
285
+ group_vars = inventories.first.dig('all', 'vars') || {}
286
+
287
+ inventory = {
288
+ 'all' => {
289
+ 'hosts' => host_vars,
290
+ 'vars' => group_vars
291
+ }
292
+ }
293
+
294
+ update_first_execution_flags(inventory['all']['hosts'], first_execution_by_host)
295
+
296
+ inventory
297
+ end
298
+
299
+ # Helper: Merges hostvars from a list of inventories, ensuring ssh key is set.
300
+ def merge_hostvars_from_inventories(inventories)
301
+ inventories.each_with_object({}) do |inventory, acc|
302
+ inventory.dig('_meta', 'hostvars')&.each do |hostname, vars|
303
+ # Ensure the ssh key is set for each host
272
304
  vars[:ansible_ssh_private_key_file] ||= Proxy::RemoteExecution::Ssh::Plugin.settings[:ssh_identity_key_file]
273
- inner_acc.merge(hostname => vars)
305
+ acc[hostname] = vars
274
306
  end
275
307
  end
276
-
277
- { 'all' => { 'hosts' => host_vars,
278
- 'vars' => inventories.first['all']['vars'] } }
279
308
  end
280
309
 
281
310
  def working_dir
@@ -303,6 +332,64 @@ module Proxy::Ansible
303
332
 
304
333
  inventory
305
334
  end
335
+
336
+ # Removes known hosts entries for hosts marked as 'first_execution' in the inventory.
337
+ # This ensures SSH host key checking does not fail on first connection.
338
+ # @return [void]
339
+ def prune_known_hosts_on_first_execution
340
+ @inventory.dig('all', 'hosts')&.each_value do |host_data|
341
+ next unless host_data.dig("foreman", "first_execution")
342
+
343
+ interface = host_data.dig("foreman", "foreman_interfaces", 0)
344
+ next unless interface
345
+
346
+ extract_host_identifiers(interface, host_data).each do |host|
347
+ extract_ports(host_data).each do |port|
348
+ Proxy::RemoteExecution::Utils.prune_known_hosts!(host, port, logger)
349
+ end
350
+ end
351
+ end
352
+ end
353
+
354
+ private
355
+
356
+ # Updates the 'first_execution' flag in the foreman data for each host in the inventory.
357
+ # @param hosts [Hash] hostname => host data hash
358
+ # @param execution_flags [Hash] hostname => boolean (first_execution)
359
+ # @return [void]
360
+ def update_first_execution_flags(hosts, execution_flags)
361
+ hosts.each do |hostname, vars|
362
+ foreman = vars['foreman']
363
+ next unless foreman
364
+
365
+ if execution_flags.key?(hostname)
366
+ foreman['first_execution'] = execution_flags[hostname]
367
+ end
368
+ end
369
+ end
370
+
371
+ def extract_host_identifiers(interface, host_data)
372
+ [
373
+ interface["ip"],
374
+ interface["ip6"],
375
+ host_data["ansible_host"],
376
+ interface["name"]
377
+ ].compact.uniq
378
+ end
379
+
380
+ def extract_ports(host_data)
381
+ [
382
+ host_data["ansible_ssh_port"],
383
+ host_data["ansible_port"]
384
+ ].compact.uniq
385
+ end
386
+
387
+ def parse_timestamp(ts)
388
+ Time.iso8601(ts)
389
+ rescue ArgumentError
390
+ logger.warn("Failed to parse '#{ts}' (#{ts.class}) as iso8601 timestamp, defaulting to current timestamp")
391
+ Time.now.getlocal
392
+ end
306
393
  end
307
394
  end
308
395
  end
@@ -2,6 +2,6 @@ module Proxy
2
2
  # Version, this allows the proxy and other plugins know
3
3
  # what version of the Ansible plugin is running
4
4
  module Ansible
5
- VERSION = '3.6.0'
5
+ VERSION = '3.7.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_ansible
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  - Daniel Lobato
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2025-05-08 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rake
@@ -29,30 +28,42 @@ dependencies:
29
28
  name: smart_proxy_dynflow
30
29
  requirement: !ruby/object:Gem::Requirement
31
30
  requirements:
32
- - - "~>"
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.4
34
+ - - "<"
33
35
  - !ruby/object:Gem::Version
34
- version: '0.8'
36
+ version: 1.0.0
35
37
  type: :runtime
36
38
  prerelease: false
37
39
  version_requirements: !ruby/object:Gem::Requirement
38
40
  requirements:
39
- - - "~>"
41
+ - - ">="
40
42
  - !ruby/object:Gem::Version
41
- version: '0.8'
43
+ version: 0.9.4
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: 1.0.0
42
47
  - !ruby/object:Gem::Dependency
43
48
  name: smart_proxy_remote_execution_ssh
44
49
  requirement: !ruby/object:Gem::Requirement
45
50
  requirements:
46
- - - "~>"
51
+ - - ">="
47
52
  - !ruby/object:Gem::Version
48
- version: '0.4'
53
+ version: 0.5.0
54
+ - - "<"
55
+ - !ruby/object:Gem::Version
56
+ version: 2.0.0
49
57
  type: :runtime
50
58
  prerelease: false
51
59
  version_requirements: !ruby/object:Gem::Requirement
52
60
  requirements:
53
- - - "~>"
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 0.5.0
64
+ - - "<"
54
65
  - !ruby/object:Gem::Version
55
- version: '0.4'
66
+ version: 2.0.0
56
67
  description: " Smart-Proxy ansible plugin\n"
57
68
  email:
58
69
  - inecas@redhat.com
@@ -60,8 +71,8 @@ email:
60
71
  executables: []
61
72
  extensions: []
62
73
  extra_rdoc_files:
63
- - README.md
64
74
  - LICENSE
75
+ - README.md
65
76
  files:
66
77
  - LICENSE
67
78
  - README.md
@@ -87,7 +98,6 @@ homepage: https://github.com/theforeman/smart_proxy_ansible
87
98
  licenses:
88
99
  - GPL-3.0-only
89
100
  metadata: {}
90
- post_install_message:
91
101
  rdoc_options: []
92
102
  require_paths:
93
103
  - lib
@@ -95,15 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
105
  requirements:
96
106
  - - ">="
97
107
  - !ruby/object:Gem::Version
98
- version: '2.7'
108
+ version: '3.0'
99
109
  required_rubygems_version: !ruby/object:Gem::Requirement
100
110
  requirements:
101
111
  - - ">="
102
112
  - !ruby/object:Gem::Version
103
113
  version: '0'
104
114
  requirements: []
105
- rubygems_version: 3.5.23
106
- signing_key:
115
+ rubygems_version: 3.6.9
107
116
  specification_version: 4
108
117
  summary: Smart-Proxy Ansible plugin
109
118
  test_files: []