smart_proxy_ansible 3.3.1 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
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