smart_proxy_ansible 3.3.1 → 3.4.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: 1139197a8fd9b944e4bec3a0dcf1ed18951bffb88457663927ba427ca4f1b372
4
- data.tar.gz: 0b774027ceedca7a36714f7725704261996efdbfe835f08bcc6fbe6a7beabc22
3
+ metadata.gz: fe91bd15ae06f9e9cc9e2d54fce101129acd7fd39f8f9c3d970909e5bf029545
4
+ data.tar.gz: d6e887989f068bfa795ceada359e68d911fbeba3d308d3eb0a52e35b1571c3fd
5
5
  SHA512:
6
- metadata.gz: 2e5535a4a9d2100e9d68321be99e23b331100e4bb8b2b1e4d7104ab7216064db9f9361c68ac6989fe7197217441fe574087278606c848be65a2362634001d818
7
- data.tar.gz: 2704d69243f82a099282d5289de914bb754826a9d32a92a93d1ad0c747f512ebe006ac2869554c26616126e3085233a6a166d0670130b8844d0e52e89f4182f5
6
+ metadata.gz: dfc145591dd06dad090a054332802674e30f2c5b51ef22809b1dcd3a42e24f587cb4e577c9e41740ff691290fe4621610ba092c888f35eaba4fa3fa73a9f90dc
7
+ data.tar.gz: 53dfb48a0c82802307cb05df916a48d146bce2505148d8eb67162ef4f87bbeab626c02ecfed46e151051d12c6af1efea7c757ac50f24768654ff39933fa2b485
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Smart-proxy Ansible plugin
2
2
 
3
- Proxy plugin to make [foreman_ansible](https://github.com/theforeman/foreman_ansible) actions run in the proxy
3
+ Proxy plugin to make [foreman_ansible](https://github.com/theforeman/foreman_ansible) actions run on the proxy.
4
4
 
5
5
  ## Compatibility
6
6
 
@@ -11,8 +11,8 @@ This plugin requires at least Foreman Proxy 2.3.
11
11
  ### Prerequisites
12
12
 
13
13
  We expect your proxy to also have
14
- [smart_proxy_dynflow](https://github.com/theforeman/smart_proxy_dynflow) 0.1.5
15
- at least, and [foreman-tasks-core](https://github.com/theforeman/foreman-tasks) as
14
+ [smart_proxy_dynflow](https://github.com/theforeman/smart_proxy_dynflow) 0.5
15
+ at least, and [smart_proxy_remote_execution_ssh](https://github.com/theforeman/smart_proxy_remote_execution_ssh) 0.4 as
16
16
  a gem requirement.
17
17
 
18
18
  ### Get the code
@@ -4,17 +4,10 @@ module Proxy::Ansible
4
4
  require 'smart_proxy_dynflow'
5
5
  require 'smart_proxy_dynflow/continuous_output'
6
6
  require 'smart_proxy_ansible/task_launcher/ansible_runner'
7
- require 'smart_proxy_ansible/task_launcher/playbook'
8
- require 'smart_proxy_ansible/actions'
9
- require 'smart_proxy_ansible/remote_execution_core/ansible_runner'
10
7
  require 'smart_proxy_ansible/runner/ansible_runner'
11
- require 'smart_proxy_ansible/runner/command_creator'
12
- require 'smart_proxy_ansible/runner/playbook'
13
8
 
14
9
  Proxy::Dynflow::TaskLauncherRegistry.register('ansible-runner',
15
10
  TaskLauncher::AnsibleRunner)
16
- Proxy::Dynflow::TaskLauncherRegistry.register('ansible-playbook',
17
- TaskLauncher::Playbook)
18
11
  end
19
12
  end
20
13
  end
@@ -24,7 +24,7 @@ module Proxy
24
24
  name = ReaderHelper.playbook_or_role_full_name(path)
25
25
  {
26
26
  name: name,
27
- playbooks_content: File.readlines(path)
27
+ playbooks_content: File.read(path)
28
28
  } if playbooks_to_import.nil? || playbooks_to_import.include?(name)
29
29
  end.compact
30
30
  rescue Errno::ENOENT, Errno::EACCES => e
@@ -32,7 +32,7 @@ module Proxy
32
32
 
33
33
  def playbook_or_role_full_name(path)
34
34
  parts = path.split('/')
35
- playbook = parts.pop
35
+ playbook = parts.pop.sub(/\.ya?ml/, '')
36
36
  parts.pop
37
37
  collection = parts.pop
38
38
  author = parts.pop
@@ -1,17 +1,17 @@
1
1
  require 'shellwords'
2
2
  require 'yaml'
3
3
 
4
- require 'smart_proxy_dynflow/runner/command'
4
+ require 'smart_proxy_dynflow/runner/process_manager_command'
5
5
  require 'smart_proxy_dynflow/runner/base'
6
6
  require 'smart_proxy_dynflow/runner/parent'
7
7
  module Proxy::Ansible
8
8
  module Runner
9
9
  class AnsibleRunner < ::Proxy::Dynflow::Runner::Parent
10
- include ::Proxy::Dynflow::Runner::Command
11
- attr_reader :execution_timeout_interval, :command_pid
10
+ include ::Proxy::Dynflow::Runner::ProcessManagerCommand
11
+ attr_reader :execution_timeout_interval
12
12
 
13
- def initialize(input, suspended_action:)
14
- super input, :suspended_action => suspended_action
13
+ def initialize(input, suspended_action:, id: nil)
14
+ super input, :suspended_action => suspended_action, :id => id
15
15
  @inventory = rebuild_secrets(rebuild_inventory(input), input)
16
16
  action_input = input.values.first[:input][:action_input]
17
17
  @playbook = action_input[:script]
@@ -34,35 +34,10 @@ module Proxy::Ansible
34
34
  start_ansible_runner
35
35
  end
36
36
 
37
- def initialize_command(*command)
38
- r, w = IO.pipe
39
-
40
- @command_pid = spawn(*command, :out => w, :err => w, :in => '/dev/null')
41
- @command_out = r
42
- w.close
43
- rescue Errno::ENOENT => e
44
- publish_exception("Error running command '#{command.join(' ')}'", e)
45
- end
46
-
47
- def refresh
48
- super
49
- @uuid ||= if (f = Dir["#{@root}/artifacts/*"].first)
50
- File.basename(f)
51
- end
52
- return unless @uuid
53
- @counter ||= 1
54
- job_event_dir = File.join(@root, 'artifacts', @uuid, 'job_events')
55
- loop do
56
- files = Dir["#{job_event_dir}/*.json"].map do |file|
57
- num = File.basename(file)[/\A\d+/].to_i unless file.include?('partial')
58
- [file, num]
59
- end
60
- files_with_nums = files.select { |(_, num)| num && num >= @counter }.sort_by(&:last)
61
- break if files_with_nums.empty?
62
- logger.debug("[foreman_ansible] - processing event files: #{files_with_nums.map(&:first).inspect}}")
63
- files_with_nums.map(&:first).each { |event_file| handle_event_file(event_file) }
64
- @counter = files_with_nums.last.last + 1
65
- end
37
+ def run_refresh_output
38
+ logger.debug('refreshing runner on demand')
39
+ process_artifacts
40
+ generate_updates
66
41
  end
67
42
 
68
43
  def timeout
@@ -75,7 +50,7 @@ module Proxy::Ansible
75
50
  end
76
51
 
77
52
  def kill
78
- ::Process.kill('SIGTERM', @command_pid)
53
+ ::Process.kill('SIGTERM', @process_manager.pid)
79
54
  publish_exit_status(2)
80
55
  @inventory['all']['hosts'].each { |hostname| @exit_statuses[hostname] = 2 }
81
56
  broadcast_data('Timeout for execution passed, stopping the job', 'stderr')
@@ -87,8 +62,38 @@ module Proxy::Ansible
87
62
  FileUtils.remove_entry(@root) if @tmp_working_dir && Dir.exist?(@root) && @cleanup_working_dirs
88
63
  end
89
64
 
65
+ def publish_exit_status(status)
66
+ process_artifacts
67
+ super
68
+ end
69
+
70
+ def initialize_command(*command)
71
+ super
72
+ @process_manager.stdin.close unless @process_manager.done?
73
+ end
74
+
90
75
  private
91
76
 
77
+ def process_artifacts
78
+ @counter ||= 1
79
+ @uuid ||= if (f = Dir["#{@root}/artifacts/*"].first)
80
+ File.basename(f)
81
+ end
82
+ return unless @uuid
83
+ job_event_dir = File.join(@root, 'artifacts', @uuid, 'job_events')
84
+ loop do
85
+ files = Dir["#{job_event_dir}/*.json"].map do |file|
86
+ num = File.basename(file)[/\A\d+/].to_i unless file.include?('partial')
87
+ [file, num]
88
+ end
89
+ files_with_nums = files.select { |(_, num)| num && num >= @counter }.sort_by(&:last)
90
+ break if files_with_nums.empty?
91
+ logger.debug("[foreman_ansible] - processing event files: #{files_with_nums.map(&:first).inspect}}")
92
+ files_with_nums.map(&:first).each { |event_file| handle_event_file(event_file) }
93
+ @counter = files_with_nums.last.last + 1
94
+ end
95
+ end
96
+
92
97
  def handle_event_file(event_file)
93
98
  logger.debug("[foreman_ansible] - parsing event file #{event_file}")
94
99
  begin
@@ -25,7 +25,7 @@ module Proxy::Ansible
25
25
  # apart when debugging
26
26
  def transform_input(input)
27
27
  action_input = super['action_input']
28
- { 'action_input' => { 'name' => action_input['name'], :task_id => action_input[:task_id] } }
28
+ { 'action_input' => { 'name' => action_input['name'], :task_id => action_input[:task_id], :runner_id => action_input[:runner_id] } }
29
29
  end
30
30
 
31
31
  # def self.input_format
@@ -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.3.1'
5
+ VERSION = '3.4.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_proxy_ansible
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.1
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-02-08 00:00:00.000000000 Z
12
+ date: 1980-01-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -115,14 +115,14 @@ dependencies:
115
115
  requirements:
116
116
  - - "~>"
117
117
  - !ruby/object:Gem::Version
118
- version: '0.5'
118
+ version: '0.8'
119
119
  type: :runtime
120
120
  prerelease: false
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
122
  requirements:
123
123
  - - "~>"
124
124
  - !ruby/object:Gem::Version
125
- version: '0.5'
125
+ version: '0.8'
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: smart_proxy_remote_execution_ssh
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -152,7 +152,6 @@ files:
152
152
  - bin/json_inventory.sh
153
153
  - bundler.d/ansible.rb
154
154
  - lib/smart_proxy_ansible.rb
155
- - lib/smart_proxy_ansible/actions.rb
156
155
  - lib/smart_proxy_ansible/api.rb
157
156
  - lib/smart_proxy_ansible/configuration_loader.rb
158
157
  - lib/smart_proxy_ansible/exception.rb
@@ -160,13 +159,9 @@ files:
160
159
  - lib/smart_proxy_ansible/playbooks_reader.rb
161
160
  - lib/smart_proxy_ansible/plugin.rb
162
161
  - lib/smart_proxy_ansible/reader_helper.rb
163
- - lib/smart_proxy_ansible/remote_execution_core/ansible_runner.rb
164
162
  - lib/smart_proxy_ansible/roles_reader.rb
165
163
  - lib/smart_proxy_ansible/runner/ansible_runner.rb
166
- - lib/smart_proxy_ansible/runner/command_creator.rb
167
- - lib/smart_proxy_ansible/runner/playbook.rb
168
164
  - lib/smart_proxy_ansible/task_launcher/ansible_runner.rb
169
- - lib/smart_proxy_ansible/task_launcher/playbook.rb
170
165
  - lib/smart_proxy_ansible/validate_settings.rb
171
166
  - lib/smart_proxy_ansible/variables_extractor.rb
172
167
  - lib/smart_proxy_ansible/version.rb
@@ -190,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
190
185
  - !ruby/object:Gem::Version
191
186
  version: '0'
192
187
  requirements: []
193
- rubygems_version: 3.3.4
188
+ rubygems_version: 3.2.26
194
189
  signing_key:
195
190
  specification_version: 4
196
191
  summary: Smart-Proxy Ansible plugin
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'smart_proxy_dynflow/action/runner'
4
-
5
- module Proxy::Ansible
6
- module Actions
7
- # Action that can be run both on Foreman or Foreman proxy side
8
- # to execute the playbook run
9
- class RunPlaybook < Proxy::Dynflow::Action::Runner
10
- def initiate_runner
11
- Proxy::Ansible::Runner::Playbook.new(
12
- input[:inventory],
13
- input[:playbook],
14
- input[:options]
15
- )
16
- end
17
- end
18
- end
19
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'smart_proxy_dynflow/runner/command_runner'
4
-
5
- module Proxy::Ansible
6
- module RemoteExecutionCore
7
- # Takes an inventory and runs it through REXCore CommandRunner
8
- class AnsibleRunner < ::Proxy::Dynflow::Runner::CommandRunner
9
- DEFAULT_REFRESH_INTERVAL = 1
10
- CONNECTION_PROMPT = 'Are you sure you want to continue connecting (yes/no)? '
11
-
12
- def initialize(options, suspended_action:)
13
- super(options, :suspended_action => suspended_action)
14
- @playbook_runner = Proxy::Ansible::Runner::Playbook.new(
15
- options['ansible_inventory'],
16
- options['script'],
17
- options,
18
- :suspended_action => suspended_action
19
- )
20
- end
21
-
22
- def start
23
- @playbook_runner.logger = logger
24
- @playbook_runner.start
25
- rescue StandardError => e
26
- logger.error(
27
- 'error while initalizing command'\
28
- " #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}"
29
- )
30
- publish_exception('Error initializing command', e)
31
- end
32
-
33
- def fill_continuous_output(continuous_output)
34
- delegated_output.fetch('result', []).each do |raw_output|
35
- continuous_output.add_raw_output(raw_output)
36
- end
37
- rescue StandardError => e
38
- continuous_output.add_exception(_('Error loading data from proxy'), e)
39
- end
40
-
41
- def refresh
42
- @command_out = @playbook_runner.command_out
43
- @command_in = @playbook_runner.command_in
44
- @command_pid = @playbook_runner.command_pid
45
- super
46
- kill if unknown_host_key_fingerprint?
47
- end
48
-
49
- def kill
50
- publish_exit_status(1)
51
- ::Process.kill('SIGTERM', @command_pid)
52
- close
53
- end
54
-
55
- private
56
-
57
- def unknown_host_key_fingerprint?
58
- last_output = @continuous_output.raw_outputs.last
59
- return if last_output.nil?
60
- last_output['output']&.lines&.last == CONNECTION_PROMPT
61
- end
62
- end
63
- end
64
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Proxy::Ansible
4
- # Creates the actual command to be passed to smart_proxy_dynflow to run
5
- class CommandCreator
6
- def initialize(inventory_file, playbook_file, options = {})
7
- @options = options
8
- @playbook_file = playbook_file
9
- @inventory_file = inventory_file
10
- command
11
- end
12
-
13
- def command
14
- parts = [environment_variables]
15
- parts << 'ansible-playbook'
16
- parts.concat(command_options)
17
- parts << @playbook_file
18
- parts
19
- end
20
-
21
- private
22
-
23
- def environment_variables
24
- defaults = { 'JSON_INVENTORY_FILE' => @inventory_file }
25
- defaults['ANSIBLE_CALLBACK_WHITELIST'] = '' if rex_command?
26
- defaults
27
- end
28
-
29
- def command_options
30
- opts = ['-i', json_inventory_script]
31
- opts.concat([setup_verbosity]) if verbose?
32
- opts.concat(['-T', @options[:timeout]]) unless @options[:timeout].nil?
33
- opts
34
- end
35
-
36
- def json_inventory_script
37
- File.expand_path('../../../bin/json_inventory.sh', File.dirname(__FILE__))
38
- end
39
-
40
- def setup_verbosity
41
- verbosity_level = @options[:verbosity_level].to_i
42
- verbosity = '-'
43
- verbosity_level.times do
44
- verbosity += 'v'
45
- end
46
- verbosity
47
- end
48
-
49
- def verbose?
50
- verbosity_level = @options[:verbosity_level]
51
- # rubocop:disable Rails/Present
52
- !verbosity_level.nil? && !verbosity_level.empty? &&
53
- verbosity_level.to_i.positive?
54
- # rubocop:enable Rails/Present
55
- end
56
-
57
- def rex_command?
58
- @options[:remote_execution_command]
59
- end
60
- end
61
- end
@@ -1,138 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'smart_proxy_dynflow/continuous_output'
4
- require 'smart_proxy_dynflow/runner/command_runner'
5
- require_relative 'command_creator'
6
- require 'tmpdir'
7
- require 'net/ssh'
8
-
9
- module Proxy::Ansible
10
- module Runner
11
- # Implements Proxy::Dynflow::Runner::Base interface for running
12
- # Ansible playbooks, used by the Foreman Ansible plugin and Ansible proxy
13
- class Playbook < Proxy::Dynflow::Runner::CommandRunner
14
- attr_reader :command_out, :command_in, :command_pid
15
-
16
- def initialize(inventory, playbook, options = {}, suspended_action:)
17
- super :suspended_action => suspended_action
18
- @inventory = rebuild_secrets(inventory, options[:secrets])
19
- unknown_hosts.each do |host|
20
- add_to_known_hosts(host)
21
- end
22
- @playbook = playbook
23
- @options = options
24
- initialize_dirs
25
- end
26
-
27
- def start
28
- write_inventory
29
- write_playbook
30
- command = CommandCreator.new(inventory_file,
31
- playbook_file,
32
- @options).command
33
- logger.debug('[foreman_ansible] - Initializing Ansible Runner')
34
- Dir.chdir(@ansible_dir) do
35
- initialize_command(*command)
36
- logger.debug("[foreman_ansible] - Running command '#{command.join(' ')}'")
37
- end
38
- end
39
-
40
- def kill
41
- publish_data('== TASK ABORTED BY USER ==', 'stdout')
42
- publish_exit_status(1)
43
- ::Process.kill('SIGTERM', @command_pid)
44
- close
45
- end
46
-
47
- def close
48
- super
49
- FileUtils.remove_entry(@working_dir) if @tmp_working_dir
50
- end
51
-
52
- private
53
-
54
- def write_inventory
55
- ensure_directory(File.dirname(inventory_file))
56
- File.write(inventory_file, JSON.dump(@inventory))
57
- end
58
-
59
- def write_playbook
60
- ensure_directory(File.dirname(playbook_file))
61
- File.write(playbook_file, @playbook)
62
- end
63
-
64
- def inventory_file
65
- File.join(@working_dir, 'foreman-inventories', id)
66
- end
67
-
68
- def playbook_file
69
- File.join(@working_dir, "foreman-playbook-#{id}.yml")
70
- end
71
-
72
- def events_dir
73
- File.join(@working_dir, 'events', id.to_s)
74
- end
75
-
76
- def ensure_directory(path)
77
- if File.exist?(path)
78
- raise "#{path} expected to be a directory" unless File.directory?(path)
79
- else
80
- FileUtils.mkdir_p(path)
81
- end
82
- path
83
- end
84
-
85
- def initialize_dirs
86
- settings = Proxy::Ansible::Plugin.settings
87
- initialize_working_dir(settings[:working_dir])
88
- initialize_ansible_dir(settings[:ansible_dir])
89
- end
90
-
91
- def initialize_working_dir(working_dir)
92
- if working_dir.nil?
93
- @working_dir = Dir.mktmpdir
94
- @tmp_working_dir = true
95
- else
96
- @working_dir = File.expand_path(working_dir)
97
- end
98
- end
99
-
100
- def initialize_ansible_dir(ansible_dir)
101
- raise "Ansible dir #{ansible_dir} does not exist" unless
102
- !ansible_dir.nil? && File.exist?(ansible_dir)
103
- @ansible_dir = ansible_dir
104
- end
105
-
106
- def unknown_hosts
107
- @inventory['all']['hosts'].select do |host|
108
- Net::SSH::KnownHosts.search_for(host).empty?
109
- end
110
- end
111
-
112
- def add_to_known_hosts(host)
113
- logger.warn("[foreman_ansible] - Host #{host} not found in known_hosts")
114
- Net::SSH::Transport::Session.new(host).host_keys.each do |host_key|
115
- Net::SSH::KnownHosts.add(host, host_key)
116
- end
117
- logger.warn("[foreman_ansible] - Added host key #{host} to known_hosts")
118
- rescue StandardError => e
119
- logger.error('[foreman_ansible] - Failed to save host key for '\
120
- "#{host}: #{e}")
121
- end
122
-
123
- def rebuild_secrets(inventory, secrets)
124
- inventory['all']['hosts'].each do |name|
125
- per_host = secrets['per-host'][name]
126
-
127
- new_secrets = {
128
- 'ansible_password' => inventory['ssh_password'] || per_host['ansible_password'],
129
- 'ansible_become_password' => inventory['effective_user_password'] || per_host['ansible_become_password']
130
- }
131
- inventory['_meta']['hostvars'][name].update(new_secrets)
132
- end
133
-
134
- inventory
135
- end
136
- end
137
- end
138
- end
@@ -1,25 +0,0 @@
1
- require 'smart_proxy_dynflow/action/runner'
2
-
3
- module Proxy::Ansible
4
- module TaskLauncher
5
- class Playbook < Proxy::Dynflow::TaskLauncher::Batch
6
- class PlaybookRunnerAction < Proxy::Dynflow::Action::Runner
7
- def initiate_runner
8
- additional_options = {
9
- :step_id => run_step_id,
10
- :uuid => execution_plan_id
11
- }
12
- ::Proxy::Ansible::RemoteExecutionCore::AnsibleRunner.new(
13
- input.merge(additional_options),
14
- :suspended_action => suspended_action
15
- )
16
- end
17
- end
18
-
19
- def child_launcher(parent)
20
- ::Proxy::Dynflow::TaskLauncher::Single.new(world, callback, :parent => parent,
21
- :action_class_override => PlaybookRunnerAction)
22
- end
23
- end
24
- end
25
- end