Skip to content

Commit

Permalink
Fixes #13812 - Remote execution provider
Browse files Browse the repository at this point in the history
Ansible can be used as a provider for remote execution. The job
templates can be Ansible playbooks that use host parameters, properties
and ERB.
Job templates, however, should have a 'hosts' section that just contains
<%= @host.name %>, because a new inventory is generated per host with
all the required variables.
In any case, the way it should work is:

Foreman sends request to proxy - including the 'hosts' it's supposed to
run on.
Proxy generates an inventory with the hosts and variables required
Proxy runs ansible and reports to Foreman

The reason why ansible_foreman_inventory cannot be used in this case is
because it's less flexible than the 'search' field of REX, where one can
use the 'scoped_search' syntax to figure out what hosts to run the
playbook on. If we used ansible_foreman_inventory for that, we would be
forced to run our playbooks on a set of hosts, hostgroups, organiztions
or locations.
  • Loading branch information
dLobatog committed Jan 15, 2018
1 parent c92d821 commit a5e0827
Show file tree
Hide file tree
Showing 15 changed files with 155 additions and 11 deletions.
4 changes: 0 additions & 4 deletions app/lib/actions/foreman_ansible/helpers/host_common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ def humanized_output
continuous_output.humanize
end

def continuous_output_providers
super << self
end

def fill_continuous_output(continuous_output)
delegated_output.fetch('result', []).each do |raw_output|
continuous_output.add_raw_output(raw_output)
Expand Down
24 changes: 24 additions & 0 deletions app/models/foreman_ansible/ansible_provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
if defined? ForemanRemoteExecution
module ForemanAnsible
# Provider for RemoteExecution that allows to run Ansible playbooks.
# Read the source of other RemoteExecution providers for more.
class AnsibleProvider < RemoteExecutionProvider
class << self
def humanized_name
'Ansible'
end

def host_setting(host, setting)
host.params[setting.to_s] || Setting[setting]
end

def proxy_command_options(template_invocation, host)
super(template_invocation, host).merge(
'ansible_inventory' =>
::ForemanAnsible::InventoryCreator.new([host]).to_hash.to_json
)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/foreman_ansible/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'fast_gettext'
require 'gettext_i18n_rails'
require 'foreman_ansible_core'
require 'foreman_ansible/remote_execution'

module ForemanAnsible
# This engine connects ForemanAnsible with Foreman core
Expand Down
11 changes: 11 additions & 0 deletions lib/foreman_ansible/remote_execution.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
if defined? ForemanRemoteExecution
module ForemanAnsible
# Dependencies related with the remote execution plugin
class Engine < ::Rails::Engine
config.to_prepare do
RemoteExecutionProvider.register(:Ansible,
ForemanAnsible::AnsibleProvider)
end
end
end
end
10 changes: 10 additions & 0 deletions lib/foreman_ansible_core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ module ForemanAnsibleCore
require 'foreman_ansible_core/actions'
end

if defined? ForemanRemoteExecutionCore
require 'foreman_remote_execution_core/actions'
require 'foreman_ansible_core/remote_execution_core/ansible_runner'
require 'foreman_ansible_core/remote_execution_core/settings_override'
ForemanRemoteExecutionCore::Actions::RunScript.send(
:prepend,
ForemanAnsibleCore::RemoteExecutionCore::SettingsOverride
)
end

require 'foreman_ansible_core/roles_reader'
require 'foreman_ansible_core/version'
end
2 changes: 2 additions & 0 deletions lib/foreman_ansible_core/playbook_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module ForemanAnsibleCore
# Implements ForemanTasksCore::Runner::Base interface for running
# Ansible playbooks, used by the Foreman Ansible plugin and Ansible proxy
class PlaybookRunner < ForemanTasksCore::Runner::CommandRunner
attr_reader :command_out, :command_in, :command_pid

def initialize(inventory, playbook, options = {})
super
@inventory = inventory
Expand Down
40 changes: 40 additions & 0 deletions lib/foreman_ansible_core/remote_execution_core/ansible_runner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
if defined? ::ForemanRemoteExecutionCore
module ForemanAnsibleCore
module RemoteExecutionCore
class AnsibleRunner < ::ForemanTasksCore::Runner::CommandRunner
DEFAULT_REFRESH_INTERVAL = 1

def initialize(options)
super(options)
@playbook_runner = ForemanAnsibleCore::PlaybookRunner.new(
options['ansible_inventory'],
options['script'],
options
)
end

def start
@playbook_runner.start
rescue => e
logger.error("error while initalizing command #{e.class} #{e.message}:\n #{e.backtrace.join("\n")}")
publish_exception('Error initializing command', e)
end

def fill_continuous_output(continuous_output)
delegated_output.fetch('result', []).each do |raw_output|
continuous_output.add_raw_output(raw_output)
end
rescue StandardError => e
continuous_output.add_exception(_('Error loading data from proxy'), e)
end

def refresh
@command_out = @playbook_runner.command_out
@command_in = @playbook_runner.command_in
@command_pid = @playbook_runner.command_pid
super
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module ForemanAnsibleCore
module RemoteExecutionCore
module SettingsOverride
def initiate_runner
return super unless input['ansible_inventory']
additional_options = {
:step_id => run_step_id,
:uuid => execution_plan_id
}
::ForemanAnsibleCore::RemoteExecutionCore::AnsibleRunner.new(
input.merge(additional_options)
)
end
end
end
end
2 changes: 1 addition & 1 deletion lib/tasks/foreman_ansible_tasks.rake
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Tests
namespace :test do
desc 'Test ForemanAnsible'
desc 'Foreman Ansible plugin tests'
Rake::TestTask.new(:foreman_ansible) do |t|
test_dir = File.join(File.dirname(__FILE__), '../..', 'test')
t.libs << ['test', test_dir]
Expand Down
Empty file.
Empty file.
50 changes: 50 additions & 0 deletions test/unit/services/ansible_template_renderer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module ForemanAnsible
class AnsibleTemplateRendererTest < ActiveSupport::TestCase
test 'builds an inventory using the host name' do
host = FactoryGirl.build_stubbed(:host)
renderer = AnsibleTemplateRenderer.new(nil, host)
renderer.expects(:target_hosts).returns([host]).twice
assert_equal renderer.inventory, "#{host.name} \n"
end

test 'builds an inventory using the host parameters' do
host = FactoryGirl.build_stubbed(:host)
stubbed_parameter = FactoryGirl.build_stubbed(:host_parameter)
host.expects(:parameters).returns([stubbed_parameter]).at_least_once
renderer = AnsibleTemplateRenderer.new(nil, host)
renderer.expects(:target_hosts).returns([host]).at_least_once
assert_equal(renderer.inventory,
"#{host.name} "\
"#{host.parameters.first.name}="\
"#{host.parameters.first.value}\n")
end

context 'with hostgroup' do
setup do
@host = FactoryGirl.build_stubbed(:host, :with_hostgroup)
end

test 'builds an inventory using the host hostgroup' do
renderer = AnsibleTemplateRenderer.new(nil, @host)
renderer.expects(:target_hosts).returns([@host]).times(3)
assert_equal(renderer.inventory,
"#{@host.name} \n"\
"[#{@host.hostgroup.title}]\n"\
"#{@host.name}\n"\
"[#{@host.hostgroup.title}:vars]\n\n")
end

test 'builds an inventory using the host hostgroup params' do
@host.hostgroup.expects(:parameters).returns('a' => 'b').at_least_once
renderer = AnsibleTemplateRenderer.new(nil, @host)
renderer.expects(:target_hosts).returns([@host]).at_least_once
assert_equal(renderer.inventory,
"#{@host.name} \n"\
"[#{@host.hostgroup.title}]\n"\
"#{@host.name}\n"\
"[#{@host.hostgroup.title}:vars]\n"\
"a=b\n\n")
end
end
end
end
2 changes: 0 additions & 2 deletions test/unit/services/fact_importer_test.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'test_plugin_helper'

module ForemanAnsible
# Test for the facts importer - only verify that given
# a set of facts it's able to import them
Expand Down
2 changes: 0 additions & 2 deletions test/unit/services/fact_parser_test.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'test_plugin_helper'

module ForemanAnsible
# Checks sample Ansible facts to see if it can assign them to
# Host properties
Expand Down
2 changes: 0 additions & 2 deletions test/unit/services/fact_sparser_test.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
require 'test_plugin_helper'

module ForemanAnsible
# Tests for checking if FactSparser can sparse a hash and unsparse it
class FactSparserTest < ActiveSupport::TestCase
Expand Down

0 comments on commit a5e0827

Please # to comment.