foreman_ansible_core 2.1.1 → 3.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 +5 -5
- data/lib/foreman_ansible_core.rb +22 -27
- data/lib/foreman_ansible_core/actions.rb +3 -1
- data/lib/foreman_ansible_core/exception.rb +2 -0
- data/lib/foreman_ansible_core/remote_execution_core/ansible_runner.rb +24 -4
- data/lib/foreman_ansible_core/runner/ansible_runner.rb +143 -0
- data/lib/foreman_ansible_core/{command_creator.rb → runner/command_creator.rb} +4 -2
- data/lib/foreman_ansible_core/runner/playbook.rb +123 -0
- data/lib/foreman_ansible_core/task_launcher/ansible_runner.rb +38 -0
- data/lib/foreman_ansible_core/task_launcher/playbook.rb +23 -0
- data/lib/foreman_ansible_core/version.rb +3 -1
- metadata +14 -14
- data/lib/foreman_ansible_core/playbook_runner.rb +0 -119
- data/lib/foreman_ansible_core/remote_execution_core/settings_override.rb +0 -18
- data/lib/foreman_ansible_core/roles_reader.rb +0 -61
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3a2a8033b0b49e4265b56fa56a0376ca02cde2f293a57c618039c60087014381
|
|
4
|
+
data.tar.gz: 98166355302fab2ab9d5624699cd6cfe6d880d6161b304b689f6410a1006c0f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4ec94ef02708b8dd9403e630abf2f3f82a65503124dbcef17ee4f3ca9e78e663d9dd8b2ce89ea43d37641ec13c5b81cd2c96d25dbf7bf3b13b8fbad5d189631e
|
|
7
|
+
data.tar.gz: 199d21b8a88c256ece9fccc356f580f46d2334fbd87b7310f39043ebdce7560e37d10338ef73b34f4c7af24ed8940b556314faeba74c5206e8fa453a0ce7542c
|
data/lib/foreman_ansible_core.rb
CHANGED
|
@@ -1,39 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
rescue LoadError
|
|
6
|
-
# These gems are not available in a proxy SCLed context
|
|
7
|
-
# puts 'Running Foreman Ansible Core in non-SCL context'
|
|
8
|
-
end
|
|
9
|
-
# rubocop:enable Lint/HandleExceptions
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'foreman_tasks_core'
|
|
4
|
+
require 'foreman_remote_execution_core'
|
|
10
5
|
|
|
11
6
|
# Core actions for Foreman Ansible, used by both Foreman and Foreman proxy
|
|
12
7
|
# This comprises running playbooks for the moment
|
|
13
8
|
module ForemanAnsibleCore
|
|
14
9
|
require 'foreman_ansible_core/exception'
|
|
15
|
-
require 'foreman_ansible_core/roles_reader'
|
|
16
10
|
require 'foreman_ansible_core/version'
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
:working_dir => nil)
|
|
12
|
+
extend ForemanTasksCore::SettingsLoader
|
|
13
|
+
register_settings(:ansible, :ansible_dir => Dir.home,
|
|
14
|
+
:working_dir => nil)
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
if ForemanTasksCore.dynflow_present?
|
|
17
|
+
require 'foreman_tasks_core/runner'
|
|
18
|
+
require 'foreman_ansible_core/runner/playbook'
|
|
19
|
+
require 'foreman_ansible_core/runner/ansible_runner'
|
|
20
|
+
require 'foreman_ansible_core/actions'
|
|
28
21
|
end
|
|
29
22
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
require 'foreman_remote_execution_core/actions'
|
|
24
|
+
require 'foreman_ansible_core/remote_execution_core/ansible_runner'
|
|
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)
|
|
38
33
|
end
|
|
39
34
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'foreman_tasks_core/shareable_action'
|
|
2
4
|
|
|
3
5
|
module ForemanAnsibleCore
|
|
@@ -6,7 +8,7 @@ module ForemanAnsibleCore
|
|
|
6
8
|
# to execute the playbook run
|
|
7
9
|
class RunPlaybook < ForemanTasksCore::Runner::Action
|
|
8
10
|
def initiate_runner
|
|
9
|
-
ForemanAnsibleCore::
|
|
11
|
+
ForemanAnsibleCore::Runner::Playbook.new(
|
|
10
12
|
input[:inventory],
|
|
11
13
|
input[:playbook],
|
|
12
14
|
input[:options]
|
|
@@ -1,19 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ForemanAnsibleCore
|
|
2
4
|
module RemoteExecutionCore
|
|
3
5
|
# Takes an inventory and runs it through REXCore CommandRunner
|
|
4
6
|
class AnsibleRunner < ::ForemanTasksCore::Runner::CommandRunner
|
|
5
7
|
DEFAULT_REFRESH_INTERVAL = 1
|
|
8
|
+
CONNECTION_PROMPT = 'Are you sure you want to continue connecting (yes/no)? '
|
|
6
9
|
|
|
7
|
-
def initialize(options)
|
|
8
|
-
super(options)
|
|
9
|
-
@playbook_runner = ForemanAnsibleCore::
|
|
10
|
+
def initialize(options, suspended_action:)
|
|
11
|
+
super(options, :suspended_action => suspended_action)
|
|
12
|
+
@playbook_runner = ForemanAnsibleCore::Runner::Playbook.new(
|
|
10
13
|
options['ansible_inventory'],
|
|
11
14
|
options['script'],
|
|
12
|
-
options
|
|
15
|
+
options,
|
|
16
|
+
:suspended_action => suspended_action
|
|
13
17
|
)
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def start
|
|
21
|
+
@playbook_runner.logger = logger
|
|
17
22
|
@playbook_runner.start
|
|
18
23
|
rescue StandardError => e
|
|
19
24
|
logger.error(
|
|
@@ -36,6 +41,21 @@ module ForemanAnsibleCore
|
|
|
36
41
|
@command_in = @playbook_runner.command_in
|
|
37
42
|
@command_pid = @playbook_runner.command_pid
|
|
38
43
|
super
|
|
44
|
+
kill if unknown_host_key_fingerprint?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def kill
|
|
48
|
+
publish_exit_status(1)
|
|
49
|
+
::Process.kill('SIGTERM', @command_pid)
|
|
50
|
+
close
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def unknown_host_key_fingerprint?
|
|
56
|
+
last_output = @continuous_output.raw_outputs.last
|
|
57
|
+
return if last_output.nil?
|
|
58
|
+
last_output['output']&.lines&.last == CONNECTION_PROMPT
|
|
39
59
|
end
|
|
40
60
|
end
|
|
41
61
|
end
|
|
@@ -0,0 +1,143 @@
|
|
|
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_ok'
|
|
61
|
+
publish_exit_status_for(hostname, 0) if @exit_statuses[hostname].nil?
|
|
62
|
+
when 'runner_on_unreachable'
|
|
63
|
+
publish_exit_status_for(hostname, 1)
|
|
64
|
+
when 'runner_on_failed'
|
|
65
|
+
publish_exit_status_for(hostname, 2) if event['ignore_errors'].nil?
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def handle_broadcast_data(event)
|
|
70
|
+
log_event("broadcast", event)
|
|
71
|
+
if event['event'] == 'playbook_on_stats'
|
|
72
|
+
header, *rows = event['stdout'].strip.lines.map(&:chomp)
|
|
73
|
+
@outputs.keys.select { |key| key.is_a? String }.each do |host|
|
|
74
|
+
line = rows.find { |row| row =~ /#{host}/ }
|
|
75
|
+
publish_data_for(host, [header, line].join("\n"), 'stdout')
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
broadcast_data(event['stdout'] + "\n", 'stdout')
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def write_inventory
|
|
83
|
+
inventory_script = <<~INVENTORY_SCRIPT
|
|
84
|
+
#!/bin/sh
|
|
85
|
+
cat <<-EOS
|
|
86
|
+
#{JSON.dump(@inventory)}
|
|
87
|
+
EOS
|
|
88
|
+
INVENTORY_SCRIPT
|
|
89
|
+
path = File.join(@root, 'inventory', 'hosts')
|
|
90
|
+
File.write(path, inventory_script)
|
|
91
|
+
File.chmod(0o0755, path)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def write_playbook
|
|
95
|
+
File.write(File.join(@root, 'project', 'playbook.yml'), @playbook)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def start_ansible_runner
|
|
99
|
+
command = ['ansible-runner', 'run', @root, '-p', 'playbook.yml']
|
|
100
|
+
initialize_command(*command)
|
|
101
|
+
logger.debug("[foreman_ansible] - Running command '#{command.join(' ')}'")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def prepare_directory_structure
|
|
105
|
+
inner = %w[inventory project].map { |part| File.join(@root, part) }
|
|
106
|
+
([@root] + inner).each do |path|
|
|
107
|
+
FileUtils.mkdir_p path
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def log_event(description, event)
|
|
112
|
+
# TODO: replace this ugly code with block variant once https://github.com/Dynflow/dynflow/pull/323
|
|
113
|
+
# arrives in production
|
|
114
|
+
logger.debug("[foreman_ansible] - handling event #{description}: #{JSON.pretty_generate(event)}") if logger.level <= ::Logger::DEBUG
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Each per-host task has inventory only for itself, we must
|
|
118
|
+
# collect all the partial inventories into one large inventory
|
|
119
|
+
# containing all the hosts.
|
|
120
|
+
def rebuild_inventory(input)
|
|
121
|
+
action_inputs = input.values.map { |hash| hash[:input][:action_input] }
|
|
122
|
+
hostnames = action_inputs.map { |hash| hash[:name] }
|
|
123
|
+
inventories = action_inputs.map { |hash| JSON.parse(hash[:ansible_inventory]) }
|
|
124
|
+
host_vars = inventories.map { |i| i['_meta']['hostvars'] }.reduce(&:merge)
|
|
125
|
+
|
|
126
|
+
{ '_meta' => { 'hostvars' => host_vars },
|
|
127
|
+
'all' => { 'hosts' => hostnames,
|
|
128
|
+
'vars' => inventories.first['all']['vars'] } }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def working_dir
|
|
132
|
+
return @root if @root
|
|
133
|
+
dir = ForemanAnsibleCore.settings[:working_dir]
|
|
134
|
+
@tmp_working_dir = true
|
|
135
|
+
if dir.nil?
|
|
136
|
+
Dir.mktmpdir
|
|
137
|
+
else
|
|
138
|
+
Dir.mktmpdir(nil, File.expand_path(dir))
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ForemanAnsibleCore
|
|
2
4
|
# Creates the actual command to be passed to foreman_tasks_core to run
|
|
3
5
|
class CommandCreator
|
|
@@ -32,7 +34,7 @@ module ForemanAnsibleCore
|
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
def json_inventory_script
|
|
35
|
-
File.expand_path('
|
|
37
|
+
File.expand_path('../../../bin/json_inventory.sh', File.dirname(__FILE__))
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def setup_verbosity
|
|
@@ -48,7 +50,7 @@ module ForemanAnsibleCore
|
|
|
48
50
|
verbosity_level = @options[:verbosity_level]
|
|
49
51
|
# rubocop:disable Rails/Present
|
|
50
52
|
!verbosity_level.nil? && !verbosity_level.empty? &&
|
|
51
|
-
verbosity_level.to_i
|
|
53
|
+
verbosity_level.to_i.positive?
|
|
52
54
|
# rubocop:enable Rails/Present
|
|
53
55
|
end
|
|
54
56
|
|
|
@@ -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
|
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:
|
|
4
|
+
version: 3.0.1
|
|
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:
|
|
11
|
+
date: 2019-09-06 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:
|
|
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:
|
|
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
|
|
@@ -56,16 +56,16 @@ dependencies:
|
|
|
56
56
|
name: net-ssh
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
58
58
|
requirements:
|
|
59
|
-
- - "
|
|
59
|
+
- - ">="
|
|
60
60
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
61
|
+
version: '0'
|
|
62
62
|
type: :runtime
|
|
63
63
|
prerelease: false
|
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
65
|
requirements:
|
|
66
|
-
- - "
|
|
66
|
+
- - ">="
|
|
67
67
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
68
|
+
version: '0'
|
|
69
69
|
description: |2
|
|
70
70
|
Ansible integration with Foreman - core parts for dealing with Ansible concepts,
|
|
71
71
|
usable by foreman_ansible or smart_proxy_ansible to delegate the execution.
|
|
@@ -79,12 +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/
|
|
87
|
-
- lib/foreman_ansible_core/
|
|
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
|
|
88
89
|
- lib/foreman_ansible_core/version.rb
|
|
89
90
|
homepage: https://github.com/theforeman/foreman_ansible
|
|
90
91
|
licenses:
|
|
@@ -105,8 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
105
106
|
- !ruby/object:Gem::Version
|
|
106
107
|
version: '0'
|
|
107
108
|
requirements: []
|
|
108
|
-
|
|
109
|
-
rubygems_version: 2.6.8
|
|
109
|
+
rubygems_version: 3.0.3
|
|
110
110
|
signing_key:
|
|
111
111
|
specification_version: 4
|
|
112
112
|
summary: 'Ansible integration with Foreman (theforeman.org): core bits'
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
require 'foreman_tasks_core/runner/command_runner'
|
|
2
|
-
require_relative 'command_creator'
|
|
3
|
-
require 'tmpdir'
|
|
4
|
-
require 'net/ssh'
|
|
5
|
-
|
|
6
|
-
module ForemanAnsibleCore
|
|
7
|
-
# Implements ForemanTasksCore::Runner::Base interface for running
|
|
8
|
-
# Ansible playbooks, used by the Foreman Ansible plugin and Ansible proxy
|
|
9
|
-
class PlaybookRunner < ForemanTasksCore::Runner::CommandRunner
|
|
10
|
-
attr_reader :command_out, :command_in, :command_pid
|
|
11
|
-
|
|
12
|
-
def initialize(inventory, playbook, options = {})
|
|
13
|
-
super
|
|
14
|
-
@inventory = inventory
|
|
15
|
-
unknown_hosts.each do |host|
|
|
16
|
-
add_to_known_hosts(host)
|
|
17
|
-
end
|
|
18
|
-
@playbook = playbook
|
|
19
|
-
@options = options
|
|
20
|
-
initialize_dirs
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def start
|
|
24
|
-
write_inventory
|
|
25
|
-
write_playbook
|
|
26
|
-
command = CommandCreator.new(inventory_file,
|
|
27
|
-
playbook_file,
|
|
28
|
-
@options).command
|
|
29
|
-
logger.debug('[foreman_ansible] - Initializing Ansible Runner')
|
|
30
|
-
Dir.chdir(@ansible_dir) do
|
|
31
|
-
initialize_command(*command)
|
|
32
|
-
logger.debug("[foreman_ansible] - Running command #{command}")
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def kill
|
|
37
|
-
publish_data('== TASK ABORTED BY USER ==', 'stdout')
|
|
38
|
-
publish_exit_status(1)
|
|
39
|
-
::Process.kill('SIGTERM', @command_pid)
|
|
40
|
-
close
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def close
|
|
44
|
-
super
|
|
45
|
-
FileUtils.remove_entry(@working_dir) if @tmp_working_dir
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def write_inventory
|
|
51
|
-
ensure_directory(File.dirname(inventory_file))
|
|
52
|
-
File.write(inventory_file, @inventory)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def write_playbook
|
|
56
|
-
ensure_directory(File.dirname(playbook_file))
|
|
57
|
-
File.write(playbook_file, @playbook)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def inventory_file
|
|
61
|
-
File.join(@working_dir, 'foreman-inventories', id)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def playbook_file
|
|
65
|
-
File.join(@working_dir, "foreman-playbook-#{id}.yml")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def events_dir
|
|
69
|
-
File.join(@working_dir, 'events', id.to_s)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def ensure_directory(path)
|
|
73
|
-
if File.exist?(path)
|
|
74
|
-
raise "#{path} expected to be a directory" unless File.directory?(path)
|
|
75
|
-
else
|
|
76
|
-
FileUtils.mkdir_p(path)
|
|
77
|
-
end
|
|
78
|
-
path
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def initialize_dirs
|
|
82
|
-
settings = ForemanAnsibleCore.settings
|
|
83
|
-
initialize_working_dir(settings[:working_dir])
|
|
84
|
-
initialize_ansible_dir(settings[:ansible_dir])
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def initialize_working_dir(working_dir)
|
|
88
|
-
if working_dir.nil?
|
|
89
|
-
@working_dir = Dir.mktmpdir
|
|
90
|
-
@tmp_working_dir = true
|
|
91
|
-
else
|
|
92
|
-
@working_dir = File.expand_path(working_dir)
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
def initialize_ansible_dir(ansible_dir)
|
|
97
|
-
raise "Ansible dir #{ansible_dir} does not exist" unless
|
|
98
|
-
!ansible_dir.nil? && File.exist?(ansible_dir)
|
|
99
|
-
@ansible_dir = ansible_dir
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def unknown_hosts
|
|
103
|
-
JSON.parse(@inventory)['all']['hosts'].select do |host|
|
|
104
|
-
Net::SSH::KnownHosts.search_for(host).empty?
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def add_to_known_hosts(host)
|
|
109
|
-
logger.warn("[foreman_ansible] - Host #{host} not found in known_hosts")
|
|
110
|
-
Net::SSH::Transport::Session.new(host).host_keys.each do |host_key|
|
|
111
|
-
Net::SSH::KnownHosts.add(host, host_key)
|
|
112
|
-
end
|
|
113
|
-
logger.warn("[foreman_ansible] - Added host key #{host} to known_hosts")
|
|
114
|
-
rescue StandardError => e
|
|
115
|
-
logger.error('[foreman_ansible] - Failed to save host key for '\
|
|
116
|
-
"#{host}: #{e}")
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
module ForemanAnsibleCore
|
|
2
|
-
module RemoteExecutionCore
|
|
3
|
-
# Ensure the Ansible provider is used whenever a JobTemplate using this
|
|
4
|
-
# provider is called.
|
|
5
|
-
module SettingsOverride
|
|
6
|
-
def initiate_runner
|
|
7
|
-
return super unless input['ansible_inventory']
|
|
8
|
-
additional_options = {
|
|
9
|
-
:step_id => run_step_id,
|
|
10
|
-
:uuid => execution_plan_id
|
|
11
|
-
}
|
|
12
|
-
::ForemanAnsibleCore::RemoteExecutionCore::AnsibleRunner.new(
|
|
13
|
-
input.merge(additional_options)
|
|
14
|
-
)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
module ForemanAnsibleCore
|
|
2
|
-
# Implements the logic needed to read the roles and associated information
|
|
3
|
-
class RolesReader
|
|
4
|
-
class << self
|
|
5
|
-
DEFAULT_CONFIG_FILE = '/etc/ansible/ansible.cfg'.freeze
|
|
6
|
-
DEFAULT_ROLES_PATH = '/etc/ansible/roles'.freeze
|
|
7
|
-
|
|
8
|
-
def list_roles
|
|
9
|
-
roles_path.split(':').map { |path| read_roles(path) }.flatten
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def roles_path(roles_line = roles_path_from_config)
|
|
13
|
-
# Default to /etc/ansible/roles if none found
|
|
14
|
-
return DEFAULT_ROLES_PATH if roles_line.empty?
|
|
15
|
-
roles_path_key = roles_line.first.split('=').first.strip
|
|
16
|
-
# In case of commented roles_path key "#roles_path", return default
|
|
17
|
-
return DEFAULT_ROLES_PATH unless roles_path_key == 'roles_path'
|
|
18
|
-
roles_line.first.split('=').last.strip
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def logger
|
|
22
|
-
# Return a different logger depending on where ForemanAnsibleCore is
|
|
23
|
-
# running from
|
|
24
|
-
if defined?(::Foreman::Logging)
|
|
25
|
-
::Foreman::Logging.logger('foreman_ansible')
|
|
26
|
-
else
|
|
27
|
-
::Proxy::LogBuffer::Decorator.instance
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def read_roles(roles_path)
|
|
34
|
-
rescue_and_raise_file_exception ReadRolesException,
|
|
35
|
-
roles_path, 'roles' do
|
|
36
|
-
Dir.glob("#{roles_path}/*").map do |path|
|
|
37
|
-
path.split('/').last
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def roles_path_from_config
|
|
43
|
-
rescue_and_raise_file_exception ReadConfigFileException,
|
|
44
|
-
DEFAULT_CONFIG_FILE, 'config file' do
|
|
45
|
-
File.readlines(DEFAULT_CONFIG_FILE).select do |line|
|
|
46
|
-
line =~ /roles_path/
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def rescue_and_raise_file_exception(exception, path, type)
|
|
52
|
-
yield
|
|
53
|
-
rescue Errno::ENOENT, Errno::EACCES => e
|
|
54
|
-
logger.debug(e.backtrace)
|
|
55
|
-
exception_message = "Could not read Ansible #{type} "\
|
|
56
|
-
"#{path} - #{e.message}"
|
|
57
|
-
raise exception.new(exception_message)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|