foreman_ansible_core 2.2.1 → 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
  SHA1:
3
- metadata.gz: ec7cac54f7db525000849608439276fd87cb5c18
4
- data.tar.gz: fb7033e1fbfc751b6ba4b0a54f0c7da9047302e1
3
+ metadata.gz: d7ce49a1cf3b78d977912a6f0887b3c831b34cb2
4
+ data.tar.gz: 2ec5f24efd9da511e2670fcd1706b9606e004360
5
5
  SHA512:
6
- metadata.gz: 19ad3f4950508999a31d3b8cb969bf6e60246763ef15a2a401b31c2adddfe0aae8900e2f9ef303a17fbf4e1b387c2a38187d34ffbdac89c03bddba669c5c4269
7
- data.tar.gz: 3f5a68bc74b91eb32fdc146a4191daf9d40d35fbca1cbc582ecd56dc9cf4d898254cfa888e6872383cc007d409f6c13cfdab1527dc684156a899b5119b2d6da5
6
+ metadata.gz: 3b9b755f323be085fd78f46dfad781c77e57c34629bb45238024bb9125c5d0cbced2010dc41cc51060f9f78486f11946dc18a049d49a397ea6672d126c04547a
7
+ data.tar.gz: '02930ac4dc9f53b2e41dbd34225988ca9ff1a9d1693fe6bb77aa8758be610c7df4e07d61b86e2f74862ef8f3653389882514df6cf9123f6036ce00871484e4aa'
@@ -15,15 +15,20 @@ module ForemanAnsibleCore
15
15
 
16
16
  if ForemanTasksCore.dynflow_present?
17
17
  require 'foreman_tasks_core/runner'
18
- require 'foreman_ansible_core/playbook_runner'
18
+ require 'foreman_ansible_core/runner/playbook'
19
+ require 'foreman_ansible_core/runner/ansible_runner'
19
20
  require 'foreman_ansible_core/actions'
20
21
  end
21
22
 
22
23
  require 'foreman_remote_execution_core/actions'
23
24
  require 'foreman_ansible_core/remote_execution_core/ansible_runner'
24
- require 'foreman_ansible_core/remote_execution_core/settings_override'
25
- ForemanRemoteExecutionCore::Actions::RunScript.send(
26
- :prepend,
27
- ForemanAnsibleCore::RemoteExecutionCore::SettingsOverride
28
- )
25
+ require 'foreman_ansible_core/task_launcher/playbook'
26
+ require 'foreman_ansible_core/task_launcher/ansible_runner'
27
+
28
+ if defined?(SmartProxyDynflowCore)
29
+ SmartProxyDynflowCore::TaskLauncherRegistry.register('ansible-runner',
30
+ ForemanAnsibleCore::TaskLauncher::AnsibleRunner)
31
+ SmartProxyDynflowCore::TaskLauncherRegistry.register('ansible-playbook',
32
+ ForemanAnsibleCore::TaskLauncher::Playbook)
33
+ end
29
34
  end
@@ -8,7 +8,7 @@ module ForemanAnsibleCore
8
8
  # to execute the playbook run
9
9
  class RunPlaybook < ForemanTasksCore::Runner::Action
10
10
  def initiate_runner
11
- ForemanAnsibleCore::PlaybookRunner.new(
11
+ ForemanAnsibleCore::Runner::Playbook.new(
12
12
  input[:inventory],
13
13
  input[:playbook],
14
14
  input[:options]
@@ -7,12 +7,13 @@ module ForemanAnsibleCore
7
7
  DEFAULT_REFRESH_INTERVAL = 1
8
8
  CONNECTION_PROMPT = 'Are you sure you want to continue connecting (yes/no)? '
9
9
 
10
- def initialize(options)
11
- super(options)
12
- @playbook_runner = ForemanAnsibleCore::PlaybookRunner.new(
10
+ def initialize(options, suspended_action:)
11
+ super(options, :suspended_action => suspended_action)
12
+ @playbook_runner = ForemanAnsibleCore::Runner::Playbook.new(
13
13
  options['ansible_inventory'],
14
14
  options['script'],
15
- options
15
+ options,
16
+ :suspended_action => suspended_action
16
17
  )
17
18
  end
18
19
 
@@ -0,0 +1,141 @@
1
+ module ForemanAnsibleCore
2
+ module Runner
3
+ class AnsibleRunner < ForemanTasksCore::Runner::Parent
4
+ include ForemanTasksCore::Runner::Command
5
+
6
+ def initialize(input, suspended_action:)
7
+ super input, :suspended_action => suspended_action
8
+ @inventory = rebuild_inventory(input)
9
+ @playbook = input.values.first[:input][:action_input][:script]
10
+ @root = working_dir
11
+ end
12
+
13
+ def start
14
+ prepare_directory_structure
15
+ write_inventory
16
+ write_playbook
17
+ start_ansible_runner
18
+ end
19
+
20
+ def refresh
21
+ return unless super
22
+ @counter ||= 1
23
+ @uuid ||= File.basename(Dir["#{@root}/artifacts/*"].first)
24
+ job_event_dir = File.join(@root, 'artifacts', @uuid, 'job_events')
25
+ loop do
26
+ files = Dir["#{job_event_dir}/*.json"].map do |file|
27
+ num = File.basename(file)[/\A\d+/].to_i unless file.include?('partial')
28
+ [file, num]
29
+ end
30
+ files_with_nums = files.select { |(_, num)| num && num >= @counter }.sort_by(&:last)
31
+ break if files_with_nums.empty?
32
+ logger.debug("[foreman_ansible] - processing event files: #{files_with_nums.map(&:first).inspect}}")
33
+ files_with_nums.map(&:first).each { |event_file| handle_event_file(event_file) }
34
+ @counter = files_with_nums.last.last + 1
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def handle_event_file(event_file)
41
+ logger.debug("[foreman_ansible] - parsing event file #{event_file}")
42
+ begin
43
+ event = JSON.parse(File.read(event_file))
44
+ if (hostname = event['event_data']['host'])
45
+ handle_host_event(hostname, event)
46
+ else
47
+ handle_broadcast_data(event)
48
+ end
49
+ true
50
+ rescue JSON::ParserError => e
51
+ logger.error("[foreman_ansible] - Error parsing runner event at #{event_file}: #{e.class}: #{e.message}")
52
+ logger.debug(e.backtrace.join("\n"))
53
+ end
54
+ end
55
+
56
+ def handle_host_event(hostname, event)
57
+ log_event("for host: #{hostname.inspect}", event)
58
+ publish_data_for(hostname, event['stdout'] + "\n", 'stdout') if event['stdout']
59
+ case event['event']
60
+ when 'runner_on_unreachable'
61
+ publish_exit_status_for(hostname, 1)
62
+ when 'runner_on_failed'
63
+ publish_exit_status_for(hostname, 2) if event['ignore_errors'].nil?
64
+ end
65
+ end
66
+
67
+ def handle_broadcast_data(event)
68
+ log_event("broadcast", event)
69
+ if event['event'] == 'playbook_on_stats'
70
+ header, *rows = event['stdout'].strip.lines.map(&:chomp)
71
+ @outputs.keys.select { |key| key.is_a? String }.each do |host|
72
+ line = rows.find { |row| row =~ /#{host}/ }
73
+ publish_data_for(host, [header, line].join("\n"), 'stdout')
74
+ end
75
+ else
76
+ broadcast_data(event['stdout'] + "\n", 'stdout')
77
+ end
78
+ end
79
+
80
+ def write_inventory
81
+ inventory_script = <<~INVENTORY_SCRIPT
82
+ #!/bin/sh
83
+ cat <<-EOS
84
+ #{JSON.dump(@inventory)}
85
+ EOS
86
+ INVENTORY_SCRIPT
87
+ path = File.join(@root, 'inventory', 'hosts')
88
+ File.write(path, inventory_script)
89
+ File.chmod(0o0755, path)
90
+ end
91
+
92
+ def write_playbook
93
+ File.write(File.join(@root, 'project', 'playbook.yml'), @playbook)
94
+ end
95
+
96
+ def start_ansible_runner
97
+ command = ['ansible-runner', 'run', @root, '-p', 'playbook.yml']
98
+ initialize_command(*command)
99
+ logger.debug("[foreman_ansible] - Running command '#{command.join(' ')}'")
100
+ end
101
+
102
+ def prepare_directory_structure
103
+ inner = %w[inventory project].map { |part| File.join(@root, part) }
104
+ ([@root] + inner).each do |path|
105
+ FileUtils.mkdir_p path
106
+ end
107
+ end
108
+
109
+ def log_event(description, event)
110
+ # TODO: replace this ugly code with block variant once https://github.com/Dynflow/dynflow/pull/323
111
+ # arrives in production
112
+ logger.debug("[foreman_ansible] - handling event #{description}: #{JSON.pretty_generate(event)}") if logger.level <= ::Logger::DEBUG
113
+ end
114
+
115
+ # Each per-host task has inventory only for itself, we must
116
+ # collect all the partial inventories into one large inventory
117
+ # containing all the hosts.
118
+ def rebuild_inventory(input)
119
+ action_inputs = input.values.map { |hash| hash[:input][:action_input] }
120
+ hostnames = action_inputs.map { |hash| hash[:name] }
121
+ inventories = action_inputs.map { |hash| JSON.parse(hash[:ansible_inventory]) }
122
+ host_vars = inventories.map { |i| i['_meta']['hostvars'] }.reduce(&:merge)
123
+
124
+ { '_meta' => { 'hostvars' => host_vars },
125
+ 'all' => { 'hosts' => hostnames,
126
+ 'vars' => inventories.first['all']['vars'] } }
127
+ end
128
+
129
+ def working_dir
130
+ return @root if @root
131
+ dir = ForemanAnsibleCore.settings[:working_dir]
132
+ @tmp_working_dir = true
133
+ if dir.nil?
134
+ Dir.mktmpdir
135
+ else
136
+ Dir.mktmpdir(nil, File.expand_path(dir))
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -34,7 +34,7 @@ module ForemanAnsibleCore
34
34
  end
35
35
 
36
36
  def json_inventory_script
37
- File.expand_path('../../bin/json_inventory.sh', File.dirname(__FILE__))
37
+ File.expand_path('../../../bin/json_inventory.sh', File.dirname(__FILE__))
38
38
  end
39
39
 
40
40
  def setup_verbosity
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'foreman_tasks_core/runner/command_runner'
4
+ require_relative 'command_creator'
5
+ require 'tmpdir'
6
+ require 'net/ssh'
7
+
8
+ module ForemanAnsibleCore
9
+ module Runner
10
+ # Implements ForemanTasksCore::Runner::Base interface for running
11
+ # Ansible playbooks, used by the Foreman Ansible plugin and Ansible proxy
12
+ class Playbook < ForemanTasksCore::Runner::CommandRunner
13
+ attr_reader :command_out, :command_in, :command_pid
14
+
15
+ def initialize(inventory, playbook, options = {}, suspended_action:)
16
+ super :suspended_action => suspended_action
17
+ @inventory = inventory
18
+ unknown_hosts.each do |host|
19
+ add_to_known_hosts(host)
20
+ end
21
+ @playbook = playbook
22
+ @options = options
23
+ initialize_dirs
24
+ end
25
+
26
+ def start
27
+ write_inventory
28
+ write_playbook
29
+ command = CommandCreator.new(inventory_file,
30
+ playbook_file,
31
+ @options).command
32
+ logger.debug('[foreman_ansible] - Initializing Ansible Runner')
33
+ Dir.chdir(@ansible_dir) do
34
+ initialize_command(*command)
35
+ logger.debug("[foreman_ansible] - Running command '#{command.join(' ')}'")
36
+ end
37
+ end
38
+
39
+ def kill
40
+ publish_data('== TASK ABORTED BY USER ==', 'stdout')
41
+ publish_exit_status(1)
42
+ ::Process.kill('SIGTERM', @command_pid)
43
+ close
44
+ end
45
+
46
+ def close
47
+ super
48
+ FileUtils.remove_entry(@working_dir) if @tmp_working_dir
49
+ end
50
+
51
+ private
52
+
53
+ def write_inventory
54
+ ensure_directory(File.dirname(inventory_file))
55
+ File.write(inventory_file, @inventory)
56
+ end
57
+
58
+ def write_playbook
59
+ ensure_directory(File.dirname(playbook_file))
60
+ File.write(playbook_file, @playbook)
61
+ end
62
+
63
+ def inventory_file
64
+ File.join(@working_dir, 'foreman-inventories', id)
65
+ end
66
+
67
+ def playbook_file
68
+ File.join(@working_dir, "foreman-playbook-#{id}.yml")
69
+ end
70
+
71
+ def events_dir
72
+ File.join(@working_dir, 'events', id.to_s)
73
+ end
74
+
75
+ def ensure_directory(path)
76
+ if File.exist?(path)
77
+ raise "#{path} expected to be a directory" unless File.directory?(path)
78
+ else
79
+ FileUtils.mkdir_p(path)
80
+ end
81
+ path
82
+ end
83
+
84
+ def initialize_dirs
85
+ settings = ForemanAnsibleCore.settings
86
+ initialize_working_dir(settings[:working_dir])
87
+ initialize_ansible_dir(settings[:ansible_dir])
88
+ end
89
+
90
+ def initialize_working_dir(working_dir)
91
+ if working_dir.nil?
92
+ @working_dir = Dir.mktmpdir
93
+ @tmp_working_dir = true
94
+ else
95
+ @working_dir = File.expand_path(working_dir)
96
+ end
97
+ end
98
+
99
+ def initialize_ansible_dir(ansible_dir)
100
+ raise "Ansible dir #{ansible_dir} does not exist" unless
101
+ !ansible_dir.nil? && File.exist?(ansible_dir)
102
+ @ansible_dir = ansible_dir
103
+ end
104
+
105
+ def unknown_hosts
106
+ JSON.parse(@inventory)['all']['hosts'].select do |host|
107
+ Net::SSH::KnownHosts.search_for(host).empty?
108
+ end
109
+ end
110
+
111
+ def add_to_known_hosts(host)
112
+ logger.warn("[foreman_ansible] - Host #{host} not found in known_hosts")
113
+ Net::SSH::Transport::Session.new(host).host_keys.each do |host_key|
114
+ Net::SSH::KnownHosts.add(host, host_key)
115
+ end
116
+ logger.warn("[foreman_ansible] - Added host key #{host} to known_hosts")
117
+ rescue StandardError => e
118
+ logger.error('[foreman_ansible] - Failed to save host key for '\
119
+ "#{host}: #{e}")
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,38 @@
1
+ require 'fileutils'
2
+
3
+ module ForemanAnsibleCore
4
+ module TaskLauncher
5
+ class AnsibleRunner < ForemanTasksCore::TaskLauncher::AbstractGroup
6
+ def runner_input(input)
7
+ super(input).reduce({}) do |acc, (_id, data)|
8
+ acc.merge(data[:input]['action_input']['name'] => data)
9
+ end
10
+ end
11
+
12
+ def operation
13
+ 'ansible-runner'
14
+ end
15
+
16
+ def self.runner_class
17
+ Runner::AnsibleRunner
18
+ end
19
+
20
+ # def self.input_format
21
+ # {
22
+ # $UUID => {
23
+ # :execution_plan_id => $EXECUTION_PLAN_UUID,
24
+ # :run_step_id => Integer,
25
+ # :input => {
26
+ # :action_class => Class,
27
+ # :action_input => {
28
+ # "ansible_inventory"=> String,
29
+ # "hostname"=>"127.0.0.1",
30
+ # "script"=>"---\n- hosts: all\n tasks:\n - shell: |\n true\n register: out\n - debug: var=out"
31
+ # }
32
+ # }
33
+ # }
34
+ # }
35
+ # end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ module ForemanAnsibleCore
2
+ module TaskLauncher
3
+ class Playbook < ForemanTasksCore::TaskLauncher::Batch
4
+ class PlaybookRunnerAction < ForemanTasksCore::Runner::Action
5
+ def initiate_runner
6
+ additional_options = {
7
+ :step_id => run_step_id,
8
+ :uuid => execution_plan_id
9
+ }
10
+ ::ForemanAnsibleCore::RemoteExecutionCore::AnsibleRunner.new(
11
+ input.merge(additional_options),
12
+ :suspended_action => suspended_action
13
+ )
14
+ end
15
+ end
16
+
17
+ def child_launcher(parent)
18
+ ForemanTasksCore::TaskLauncher::Single.new(world, callback, :parent => parent,
19
+ :action_class_override => PlaybookRunnerAction)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ForemanAnsibleCore
4
- VERSION = '2.2.1'
4
+ VERSION = '3.0.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_ansible_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Lobato Garcia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-25 00:00:00.000000000 Z
11
+ date: 2019-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.1'
33
+ version: 0.3.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.1'
40
+ version: 0.3.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: foreman_remote_execution_core
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -79,11 +79,13 @@ files:
79
79
  - bin/json_inventory.sh
80
80
  - lib/foreman_ansible_core.rb
81
81
  - lib/foreman_ansible_core/actions.rb
82
- - lib/foreman_ansible_core/command_creator.rb
83
82
  - lib/foreman_ansible_core/exception.rb
84
- - lib/foreman_ansible_core/playbook_runner.rb
85
83
  - lib/foreman_ansible_core/remote_execution_core/ansible_runner.rb
86
- - lib/foreman_ansible_core/remote_execution_core/settings_override.rb
84
+ - lib/foreman_ansible_core/runner/ansible_runner.rb
85
+ - lib/foreman_ansible_core/runner/command_creator.rb
86
+ - lib/foreman_ansible_core/runner/playbook.rb
87
+ - lib/foreman_ansible_core/task_launcher/ansible_runner.rb
88
+ - lib/foreman_ansible_core/task_launcher/playbook.rb
87
89
  - lib/foreman_ansible_core/version.rb
88
90
  homepage: https://github.com/theforeman/foreman_ansible
89
91
  licenses:
@@ -1,121 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'foreman_tasks_core/runner/command_runner'
4
- require_relative 'command_creator'
5
- require 'tmpdir'
6
- require 'net/ssh'
7
-
8
- module ForemanAnsibleCore
9
- # Implements ForemanTasksCore::Runner::Base interface for running
10
- # Ansible playbooks, used by the Foreman Ansible plugin and Ansible proxy
11
- class PlaybookRunner < ForemanTasksCore::Runner::CommandRunner
12
- attr_reader :command_out, :command_in, :command_pid
13
-
14
- def initialize(inventory, playbook, options = {})
15
- super
16
- @inventory = inventory
17
- unknown_hosts.each do |host|
18
- add_to_known_hosts(host)
19
- end
20
- @playbook = playbook
21
- @options = options
22
- initialize_dirs
23
- end
24
-
25
- def start
26
- write_inventory
27
- write_playbook
28
- command = CommandCreator.new(inventory_file,
29
- playbook_file,
30
- @options).command
31
- logger.debug('[foreman_ansible] - Initializing Ansible Runner')
32
- Dir.chdir(@ansible_dir) do
33
- initialize_command(*command)
34
- logger.debug("[foreman_ansible] - Running command #{command}")
35
- end
36
- end
37
-
38
- def kill
39
- publish_data('== TASK ABORTED BY USER ==', 'stdout')
40
- publish_exit_status(1)
41
- ::Process.kill('SIGTERM', @command_pid)
42
- close
43
- end
44
-
45
- def close
46
- super
47
- FileUtils.remove_entry(@working_dir) if @tmp_working_dir
48
- end
49
-
50
- private
51
-
52
- def write_inventory
53
- ensure_directory(File.dirname(inventory_file))
54
- File.write(inventory_file, @inventory)
55
- end
56
-
57
- def write_playbook
58
- ensure_directory(File.dirname(playbook_file))
59
- File.write(playbook_file, @playbook)
60
- end
61
-
62
- def inventory_file
63
- File.join(@working_dir, 'foreman-inventories', id)
64
- end
65
-
66
- def playbook_file
67
- File.join(@working_dir, "foreman-playbook-#{id}.yml")
68
- end
69
-
70
- def events_dir
71
- File.join(@working_dir, 'events', id.to_s)
72
- end
73
-
74
- def ensure_directory(path)
75
- if File.exist?(path)
76
- raise "#{path} expected to be a directory" unless File.directory?(path)
77
- else
78
- FileUtils.mkdir_p(path)
79
- end
80
- path
81
- end
82
-
83
- def initialize_dirs
84
- settings = ForemanAnsibleCore.settings
85
- initialize_working_dir(settings[:working_dir])
86
- initialize_ansible_dir(settings[:ansible_dir])
87
- end
88
-
89
- def initialize_working_dir(working_dir)
90
- if working_dir.nil?
91
- @working_dir = Dir.mktmpdir
92
- @tmp_working_dir = true
93
- else
94
- @working_dir = File.expand_path(working_dir)
95
- end
96
- end
97
-
98
- def initialize_ansible_dir(ansible_dir)
99
- raise "Ansible dir #{ansible_dir} does not exist" unless
100
- !ansible_dir.nil? && File.exist?(ansible_dir)
101
- @ansible_dir = ansible_dir
102
- end
103
-
104
- def unknown_hosts
105
- JSON.parse(@inventory)['all']['hosts'].select do |host|
106
- Net::SSH::KnownHosts.search_for(host).empty?
107
- end
108
- end
109
-
110
- def add_to_known_hosts(host)
111
- logger.warn("[foreman_ansible] - Host #{host} not found in known_hosts")
112
- Net::SSH::Transport::Session.new(host).host_keys.each do |host_key|
113
- Net::SSH::KnownHosts.add(host, host_key)
114
- end
115
- logger.warn("[foreman_ansible] - Added host key #{host} to known_hosts")
116
- rescue StandardError => e
117
- logger.error('[foreman_ansible] - Failed to save host key for '\
118
- "#{host}: #{e}")
119
- end
120
- end
121
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ForemanAnsibleCore
4
- module RemoteExecutionCore
5
- # Ensure the Ansible provider is used whenever a JobTemplate using this
6
- # provider is called.
7
- module SettingsOverride
8
- def initiate_runner
9
- return super unless input['ansible_inventory']
10
- additional_options = {
11
- :step_id => run_step_id,
12
- :uuid => execution_plan_id
13
- }
14
- ::ForemanAnsibleCore::RemoteExecutionCore::AnsibleRunner.new(
15
- input.merge(additional_options)
16
- )
17
- end
18
- end
19
- end
20
- end