Skip to content

Commit

Permalink
Merge pull request #18989 from carbonin/add_more_options_to_ansible_s…
Browse files Browse the repository at this point in the history
…tuff

Make embedded ansible verbosity and execution_ttl work
  • Loading branch information
Fryguy authored Jul 18, 2019
2 parents a4dd59f + 40f33b0 commit ec14de1
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 35 deletions.
7 changes: 4 additions & 3 deletions app/models/manageiq/providers/ansible_playbook_workflow.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class ManageIQ::Providers::AnsiblePlaybookWorkflow < ManageIQ::Providers::AnsibleRunnerWorkflow
def self.job_options(env_vars, extra_vars, playbook_options, timeout, poll_interval, hosts, credentials)
def self.job_options(env_vars, extra_vars, playbook_options, timeout, poll_interval, hosts, credentials, verbosity)
{
:credentials => credentials,
:env_vars => env_vars,
Expand All @@ -8,6 +8,7 @@ def self.job_options(env_vars, extra_vars, playbook_options, timeout, poll_inter
:playbook_path => playbook_options[:playbook_path],
:timeout => timeout,
:poll_interval => poll_interval,
:verbosity => verbosity
}
end

Expand All @@ -17,9 +18,9 @@ def pre_playbook
end

def run_playbook
credentials, env_vars, extra_vars, hosts, playbook_path = options.values_at(:credentials, :env_vars, :extra_vars, :hosts, :playbook_path)
credentials, env_vars, extra_vars, hosts, playbook_path, verbosity = options.values_at(:credentials, :env_vars, :extra_vars, :hosts, :playbook_path, :verbosity)

response = Ansible::Runner.run_async(env_vars, extra_vars, playbook_path, :hosts => hosts, :credentials => credentials)
response = Ansible::Runner.run_async(env_vars, extra_vars, playbook_path, :hosts => hosts, :credentials => credentials, :verbosity => verbosity)
if response.nil?
queue_signal(:abort, "Failed to run ansible playbook", "error")
else
Expand Down
5 changes: 3 additions & 2 deletions app/models/manageiq/providers/ansible_role_workflow.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class ManageIQ::Providers::AnsibleRoleWorkflow < ManageIQ::Providers::AnsibleRunnerWorkflow
def self.job_options(env_vars, extra_vars, role_options, timeout, poll_interval, hosts, credentials)
def self.job_options(env_vars, extra_vars, role_options, timeout, poll_interval, hosts, credentials, verbosity)
{
:credentials => credentials,
:env_vars => env_vars,
Expand All @@ -9,7 +9,8 @@ def self.job_options(env_vars, extra_vars, role_options, timeout, poll_interval,
:roles_path => role_options[:roles_path],
:role_skip_facts => role_options[:role_skip_facts],
:timeout => timeout,
:poll_interval => poll_interval
:poll_interval => poll_interval,
:verbosity => verbosity
}
end

Expand Down
8 changes: 6 additions & 2 deletions app/models/manageiq/providers/ansible_runner_workflow.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
class ManageIQ::Providers::AnsibleRunnerWorkflow < Job
def self.create_job(env_vars, extra_vars, role_or_playbook_options, hosts = ["localhost"], credentials = [], timeout: 1.hour, poll_interval: 1.second)
super(name, job_options(env_vars, extra_vars, role_or_playbook_options, timeout, poll_interval, hosts, credentials))
def self.create_job(env_vars, extra_vars, role_or_playbook_options,
hosts = ["localhost"], credentials = [],
timeout: 1.hour, poll_interval: 1.second, verbosity: 0)
options = job_options(env_vars, extra_vars, role_or_playbook_options, timeout,
poll_interval, hosts, credentials, verbosity)
super(name, options)
end

def current_job_timeout(_timeout_adjustment = 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ def run(vars = {})
playbook_vars = { :playbook_path => parent.path }
credentials = collect_credentials(vars)

workflow.create_job({}, extra_vars, playbook_vars, vars[:hosts], credentials).tap do |job|
kwargs = {}
kwargs[:timeout] = vars[:execution_ttl].to_i.minutes if vars[:execution_ttl].present?
kwargs[:verbosity] = vars[:verbosity].to_i if vars[:verbosity].present?

workflow.create_job({}, extra_vars, playbook_vars, vars[:hosts], credentials, kwargs).tap do |job|
job.signal(:start)
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def translate_credentials!(launch_options)
limit
network_credential_id
vault_credential_id
verbosity
].freeze

def launch_ansible_tower_job
Expand Down
10 changes: 6 additions & 4 deletions app/models/service_ansible_playbook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,14 @@ def get_job_options(action)
end

CONFIG_OPTIONS_WHITELIST = %i[
hosts
extra_vars
cloud_credential_id
credential_id
vault_credential_id
execution_ttl
extra_vars
hosts
network_credential_id
cloud_credential_id
vault_credential_id
verbosity
].freeze

def config_options(action)
Expand Down
50 changes: 34 additions & 16 deletions lib/ansible/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ class << self
# @param playbook_path [String] Path to the playbook we will want to run
# @param hosts [Array] List of hostnames to target with the playbook
# @param credentials [Array] List of Authentication object ids to provide to the playbook run
# @param verbosity [Integer] ansible-runner verbosity level 0-5
# @return [Ansible::Runner::ResponseAsync] Response object that we can query for .running?, providing us the
# Ansible::Runner::Response object, when the job is finished.
def run_async(env_vars, extra_vars, playbook_path, hosts: ["localhost"], credentials: [])
def run_async(env_vars, extra_vars, playbook_path, hosts: ["localhost"], credentials: [], verbosity: 0)
run_via_cli(hosts,
credentials,
env_vars,
extra_vars,
:ansible_runner_method => "start",
:playbook => playbook_path)
:playbook => playbook_path,
:verbosity => verbosity)
end

# Runs a role directly via ansible-runner, a simple playbook is then automatically created,
Expand All @@ -32,17 +34,19 @@ def run_async(env_vars, extra_vars, playbook_path, hosts: ["localhost"], credent
# playbook. True by default.
# @param hosts [Array] List of hostnames to target with the role
# @param credentials [Array] List of Authentication object ids to provide to the role run
# @param verbosity [Integer] ansible-runner verbosity level 0-5
# @return [Ansible::Runner::ResponseAsync] Response object that we can query for .running?, providing us the
# Ansible::Runner::Response object, when the job is finished.
def run_role_async(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true, hosts: ["localhost"], credentials: [])
def run_role_async(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true, hosts: ["localhost"], credentials: [], verbosity: 0)
run_via_cli(hosts,
credentials,
env_vars,
extra_vars,
:ansible_runner_method => "start",
:role => role_name,
:roles_path => roles_path,
:role_skip_facts => role_skip_facts)
:role_skip_facts => role_skip_facts,
:verbosity => verbosity)
end

# Runs a playbook via ansible-runner, see: https://ansible-runner.readthedocs.io/en/latest/standalone.html#running-playbooks
Expand All @@ -54,14 +58,16 @@ def run_role_async(env_vars, extra_vars, role_name, roles_path:, role_skip_facts
# @param tags [Hash] Hash with key/values pairs that will be passed as tags to the ansible-runner run
# @param hosts [Array] List of hostnames to target with the playbook
# @param credentials [Array] List of Authentication object ids to provide to the playbook run
# @param verbosity [Integer] ansible-runner verbosity level 0-5
# @return [Ansible::Runner::Response] Response object with all details about the ansible run
def run(env_vars, extra_vars, playbook_path, tags: nil, hosts: ["localhost"], credentials: [])
def run(env_vars, extra_vars, playbook_path, tags: nil, hosts: ["localhost"], credentials: [], verbosity: 0)
run_via_cli(hosts,
credentials,
env_vars,
extra_vars,
:tags => tags,
:playbook => playbook_path)
:tags => tags,
:playbook => playbook_path,
:verbosity => verbosity)
end

# Runs a role directly via ansible-runner, a simple playbook is then automatically created,
Expand All @@ -77,16 +83,18 @@ def run(env_vars, extra_vars, playbook_path, tags: nil, hosts: ["localhost"], cr
# @param tags [Hash] Hash with key/values pairs that will be passed as tags to the ansible-runner run
# @param hosts [Array] List of hostnames to target with the role
# @param credentials [Array] List of Authentication object ids to provide to the role run
# @param verbosity [Integer] ansible-runner verbosity level 0-5
# @return [Ansible::Runner::Response] Response object with all details about the ansible run
def run_role(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true, tags: nil, hosts: ["localhost"], credentials: [])
def run_role(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true, tags: nil, hosts: ["localhost"], credentials: [], verbosity: 0)
run_via_cli(hosts,
credentials,
env_vars,
extra_vars,
:tags => tags,
:role => role_name,
:roles_path => roles_path,
:role_skip_facts => role_skip_facts)
:role_skip_facts => role_skip_facts,
:verbosity => verbosity)
end

# Runs "run" method via queue
Expand All @@ -99,11 +107,13 @@ def run_role(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true
# @param queue_opts [Hash] Additional options that will be passed to MiqQueue record creation
# @param hosts [Array] List of hostnames to target with the playbook
# @param credentials [Array] List of Authentication object ids to provide to the playbook run
# @param verbosity [Integer] ansible-runner verbosity level 0-5
# @return [BigInt] ID of MiqTask record wrapping the task
def run_queue(env_vars, extra_vars, playbook_path, user_id, queue_opts, hosts: ["localhost"], credentials: [])
def run_queue(env_vars, extra_vars, playbook_path, user_id, queue_opts, hosts: ["localhost"], credentials: [], verbosity: 0)
kwargs = {
:hosts => hosts,
:credentials => credentials
:credentials => credentials,
:verbosity => verbosity
}
run_in_queue("run", user_id, queue_opts, [env_vars, extra_vars, playbook_path, kwargs])
end
Expand All @@ -121,13 +131,15 @@ def run_queue(env_vars, extra_vars, playbook_path, user_id, queue_opts, hosts: [
# playbook. True by default.
# @param hosts [Array] List of hostnames to target with the role
# @param credentials [Array] List of Authentication object ids to provide to the role run
# @param verbosity [Integer] ansible-runner verbosity level 0-5
# @return [BigInt] ID of MiqTask record wrapping the task
def run_role_queue(env_vars, extra_vars, role_name, user_id, queue_opts, roles_path:, role_skip_facts: true, hosts: ["localhost"], credentials: [])
def run_role_queue(env_vars, extra_vars, role_name, user_id, queue_opts, roles_path:, role_skip_facts: true, hosts: ["localhost"], credentials: [], verbosity: 0)
kwargs = {
:roles_path => roles_path,
:role_skip_facts => role_skip_facts,
:hosts => hosts,
:credentials => credentials
:credentials => credentials,
:verbosity => verbosity
}
run_in_queue("run_role", user_id, queue_opts, [env_vars, extra_vars, role_name, kwargs])
end
Expand Down Expand Up @@ -167,9 +179,10 @@ def run_in_queue(method_name, user_id, queue_opts, args)
# @param tags [Hash] Hash with key/values pairs that will be passed as tags to the ansible-runner run
# @param ansible_runner_method [String] Optional method we will use to run the ansible-runner. It can be either
# "run", which is sync call, or "start" which is async call. Default is "run"
# @param verbosity [Integer] ansible-runner verbosity level 0-5
# @param playbook_or_role_args [Hash] Hash that includes the :playbook key or :role keys
# @return [Ansible::Runner::Response] Response object with all details about the ansible run
def run_via_cli(hosts, credentials, env_vars, extra_vars, tags: nil, ansible_runner_method: "run", **playbook_or_role_args)
def run_via_cli(hosts, credentials, env_vars, extra_vars, tags: nil, ansible_runner_method: "run", verbosity: 0, **playbook_or_role_args)
# If we are running against only localhost and no other value is set for ansible_connection
# then assume we don't want to ssh locally
extra_vars["ansible_connection"] ||= "local" if hosts == ["localhost"]
Expand All @@ -184,7 +197,7 @@ def run_via_cli(hosts, credentials, env_vars, extra_vars, tags: nil, ansible_run
create_extra_vars_file(base_dir, extra_vars.merge(cred_extra_vars))
create_cmdline_file(base_dir, {:tags => tags}.delete_blanks.merge(cred_command_line))

params = runner_params(base_dir, ansible_runner_method, playbook_or_role_args)
params = runner_params(base_dir, ansible_runner_method, playbook_or_role_args, verbosity)

begin
result = AwesomeSpawn.run("ansible-runner", :env => env_vars.merge(cred_env_vars), :params => params)
Expand Down Expand Up @@ -217,14 +230,19 @@ def async?(ansible_runner_method)
ansible_runner_method == "start"
end

def runner_params(base_dir, ansible_runner_method, playbook_or_role_args)
def runner_params(base_dir, ansible_runner_method, playbook_or_role_args, verbosity)
runner_args = playbook_or_role_args.dup

runner_args.delete(:roles_path) if runner_args[:roles_path].nil?

runner_args[:role_skip_facts] = nil if runner_args.delete(:role_skip_facts)
runner_args[:ident] = "result"

if verbosity.to_i > 0
v_flag = "-#{"v" * verbosity.to_i.clamp(1, 5)}"
runner_args[v_flag] = nil
end

[ansible_runner_method, base_dir, :json, runner_args]
end

Expand Down
11 changes: 11 additions & 0 deletions spec/lib/ansible/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@
described_class.run(env_vars, extra_vars, playbook, :tags => tags)
end

it "calls run with the correct verbosity" do
expect(AwesomeSpawn).to receive(:run) do |command, options|
expect(command).to eq("ansible-runner")

_method, _dir, _json, args = options[:params]
expect(args).to eq(:ident => "result", :playbook => playbook, "-vvvvv" => nil)
end.and_return(result)

described_class.run(env_vars, extra_vars, playbook, :verbosity => 6)
end

context "with special characters" do
let(:env_vars) { {"ENV1" => "pa$%w0rd!'"} }
let(:extra_vars) { {"name" => "john's server"} }
Expand Down
16 changes: 13 additions & 3 deletions spec/models/manageiq/providers/ansible_playbook_workflow_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
describe ManageIQ::Providers::AnsiblePlaybookWorkflow do
let(:job) { described_class.create_job(*options).tap { |job| job.state = state } }
let(:options) { [{"ENV" => "VAR"}, %w[arg1 arg2], {:playbook_path => "/path/to/playbook"}, %w[192.0.2.0 192.0.2.1]] }
let(:options) { [{"ENV" => "VAR"}, %w[arg1 arg2], {:playbook_path => "/path/to/playbook"}, %w[192.0.2.0 192.0.2.1], {:verbosity => 3}] }
let(:state) { "waiting_to_start" }

context ".create_job" do
Expand Down Expand Up @@ -90,8 +90,18 @@

it "ansible-runner succeeds" do
response_async = Ansible::Runner::ResponseAsync.new(:base_dir => "/path/to/results")

expect(Ansible::Runner).to receive(:run_async).and_return(response_async)
runner_options = [
{"ENV" => "VAR"},
%w[arg1 arg2],
"/path/to/playbook",
{
:hosts => %w[192.0.2.0 192.0.2.1],
:credentials => [],
:verbosity => 3
}
]

expect(Ansible::Runner).to receive(:run_async).with(*runner_options).and_return(response_async)
expect(job).to receive(:queue_signal).with(:poll_runner)

job.signal(:run_playbook)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
describe ManageIQ::Providers::AnsibleRoleWorkflow do
let(:job) { described_class.create_job(*options).tap { |job| job.state = state } }
let(:role_options) { {:role_name => 'role_name', :roles_path => 'path/role', :role_skip_facts => true } }
let(:options) { [{"ENV" => "VAR"}, %w(arg1 arg2), role_options] }
let(:options) { [{"ENV" => "VAR"}, %w[arg1 arg2], role_options, {:verbosity => 4}] }
let(:state) { "waiting_to_start" }

context ".create_job" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
expect(job.options[:env_vars]).to eq({})
expect(job.options[:extra_vars]).to eq(:instance_ids => ["i-3434"])
expect(job.options[:playbook_path]).to eq(playbook.path)
expect(job.options[:timeout]).to eq(1.hour)
expect(job.options[:verbosity]).to eq(0)
end

it "accepts different variables to launch a job template against" do
Expand All @@ -54,6 +56,20 @@
expect(job.options[:extra_vars]).to eq(:instance_ids => ["i-3434"], :some_key => :some_value)
expect(job.options[:playbook_path]).to eq(playbook.path)
end

it "passes execution_ttl to the job as its timeout" do
job = manager.configuration_scripts.first.run(:execution_ttl => "5")

expect(job).to be_a ManageIQ::Providers::AnsiblePlaybookWorkflow
expect(job.options[:timeout]).to eq(5.minutes)
end

it "passes verbosity to the job when specified" do
job = manager.configuration_scripts.first.run(:verbosity => "5")

expect(job).to be_a ManageIQ::Providers::AnsiblePlaybookWorkflow
expect(job.options[:verbosity]).to eq(5)
end
end

context "#merge_extra_vars" do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@
end

context 'with launch options' do
let(:options) { {:job_template_ref => 'jt1', :extra_vars => {:thing => "stuff"}} }
let(:options) { {:job_template_ref => 'jt1', :extra_vars => {:thing => "stuff"}, :verbosity => "4"} }
it 'passes them to the job' do
expect(ManageIQ::Providers::EmbeddedAnsible::AutomationManager::Job).to receive(:create_job)
.with(an_instance_of(ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ConfigurationScript), :hosts => ["localhost"], :extra_vars => {:thing => "stuff"})
.with(an_instance_of(ManageIQ::Providers::EmbeddedAnsible::AutomationManager::ConfigurationScript), :hosts => ["localhost"], :extra_vars => {:thing => "stuff"}, :verbosity => "4")
.and_return(double(:id => 'jb1'))
expect(subject).to receive(:queue_signal)
subject.launch_ansible_tower_job
Expand Down
10 changes: 9 additions & 1 deletion spec/models/service_ansible_playbook_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
:credential_id => credential_0.id,
:vault_credential_id => credential_3.id,
:playbook_id => 10,
:execution_ttl => "5",
:verbosity => "3",
:extra_vars => {
"var1" => {:default => "default_val1"},
:var2 => {:default => "default_val2"},
Expand Down Expand Up @@ -122,7 +124,9 @@
expect(basic_service.options[:provision_job_options]).to include(
:hosts => "default_host1,default_host2",
:credential => credential_0.native_ref,
:vault_credential => credential_3.native_ref
:vault_credential => credential_3.native_ref,
:execution_ttl => "5",
:verbosity => "3"
)
end
end
Expand All @@ -135,6 +139,8 @@
:hosts => "host1,host2",
:credential => credential_1.native_ref,
:vault_credential => credential_3.native_ref,
:execution_ttl => "5",
:verbosity => "3",
:extra_vars => {
'var1' => 'value1',
'var2' => 'value2',
Expand All @@ -160,6 +166,8 @@
:hosts => "default_host1,default_host2",
:credential => credential_0.native_ref,
:vault_credential => credential_3.native_ref,
:execution_ttl => "5",
:verbosity => "3",
:extra_vars => {
'var1' => 'default_val1',
'var2' => 'default_val2',
Expand Down

0 comments on commit ec14de1

Please sign in to comment.