Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make embedded ansible verbosity and execution_ttl work #18989

Merged
merged 5 commits into from
Jul 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to get slightly tighter code here with something like

env_vars, extra_vars, playbook_path = options.values_at(:env_vars, :extra_vars, :playbook_path)
kw_args = options.slice(:credentials, :hosts, :verbosity)

response = Ansible::Runner.run_async(env_vars, extra_vars, playbook_path, kw_args)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alphabet Dance! 🕺

].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
Fryguy marked this conversation as resolved.
Show resolved Hide resolved
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
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