Skip to content

Commit

Permalink
Cleanup Ansible::Runner
Browse files Browse the repository at this point in the history
Additionally fixes a bug with the :cmdline substitution when an
extra_var has a "'" in it.
  • Loading branch information
Fryguy committed Aug 6, 2018
1 parent 97b4735 commit bcf0405
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 114 deletions.
162 changes: 50 additions & 112 deletions lib/ansible/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class << self
def run_async(env_vars, extra_vars, playbook_path)
run_via_cli(env_vars,
extra_vars,
:playbook_path => playbook_path,
:ansible_runner_method => "start")
:ansible_runner_method => "start",
:playbook => playbook_path)
end

# Runs a role directly via ansible-runner, a simple playbook is then automatically created,
Expand All @@ -31,10 +31,10 @@ def run_async(env_vars, extra_vars, playbook_path)
def run_role_async(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true)
run_via_cli(env_vars,
extra_vars,
:role_name => role_name,
:ansible_runner_method => "start",
:role => role_name,
:roles_path => roles_path,
:role_skip_facts => role_skip_facts,
:ansible_runner_method => "start")
:role_skip_facts => role_skip_facts)
end

# Runs a playbook via ansible-runner, see: https://ansible-runner.readthedocs.io/en/latest/standalone.html#running-playbooks
Expand All @@ -45,7 +45,9 @@ def run_role_async(env_vars, extra_vars, role_name, roles_path:, role_skip_facts
# @param playbook_path [String] Path to the playbook we will want to run
# @return [Ansible::Runner::Response] Response object with all details about the ansible run
def run(env_vars, extra_vars, playbook_path)
run_via_cli(env_vars, extra_vars, :playbook_path => playbook_path)
run_via_cli(env_vars,
extra_vars,
:playbook => playbook_path)
end

# Runs a role directly via ansible-runner, a simple playbook is then automatically created,
Expand All @@ -60,7 +62,11 @@ def run(env_vars, extra_vars, playbook_path)
# playbook. True by default.
# @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)
run_via_cli(env_vars, extra_vars, :role_name => role_name, :roles_path => roles_path, :role_skip_facts => role_skip_facts)
run_via_cli(env_vars,
extra_vars,
:role => role_name,
:roles_path => roles_path,
:role_skip_facts => role_skip_facts)
end

# Runs "run" method via queue
Expand Down Expand Up @@ -89,9 +95,7 @@ def run_queue(env_vars, extra_vars, playbook_path, user_id, queue_opts)
# playbook. True by default.
# @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)
run_in_queue("run_role",
user_id,
queue_opts,
run_in_queue("run_role", user_id, queue_opts,
[env_vars, extra_vars, role_name, {:roles_path => roles_path, :role_skip_facts => role_skip_facts}])
end

Expand Down Expand Up @@ -125,31 +129,23 @@ def run_in_queue(method_name, user_id, queue_opts, args)
# @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the
# ansible-runner run
# @param extra_vars [Hash] Hash with key/value pairs that will be passed as extra_vars to the ansible-runner run
# @param playbook_path [String] Path to the playbook we will want to run
# @param role_name [String] Ansible role name
# @param roles_path [String] Path to the directory with roles
# @param role_skip_facts [Boolean] Whether we should skip facts gathering, equals to 'gather_facts: False' in a
# playbook. True by default.
# @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 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(env_vars, extra_vars, playbook_path: nil, role_name: nil, roles_path: nil, role_skip_facts: true,
ansible_runner_method: "run")
validate_params!(env_vars, extra_vars, playbook_path, roles_path)
def run_via_cli(env_vars, extra_vars, ansible_runner_method: "run", **playbook_or_role_args)
validate_params!(env_vars, extra_vars, ansible_runner_method, playbook_or_role_args)

base_dir = Dir.mktmpdir("ansible-runner")
begin
result = AwesomeSpawn.run(ansible_command(base_dir, ansible_runner_method),
:env => env_vars,
:params => params(:extra_vars => extra_vars,
:playbook_path => playbook_path,
:role_name => role_name,
:roles_path => roles_path,
:role_skip_facts => role_skip_facts))
params = runner_params(base_dir, ansible_runner_method, extra_vars, playbook_or_role_args)

begin
result = AwesomeSpawn.run("ansible-runner", :env => env_vars, :params => params)
res = response(base_dir, ansible_runner_method, result)
ensure
# Clean up the tmp dir for the sync method, for async we will clean it up after the job is finished and we've
# read the output, that will be written into this directory.
res.cleanup_filesystem! unless async?(ansible_runner_method)
res&.cleanup_filesystem! unless async?(ansible_runner_method)
end
end

Expand All @@ -169,106 +165,48 @@ def response(base_dir, ansible_runner_method, result)
end
end

# @return [Bollean] True if ansible-runner will run on background
# @return [Boolean] True if ansible-runner will run on background
def async?(ansible_runner_method)
ansible_runner_method == "start"
end

# Generate correct params, depending if we've passed :playbook_path or a :role_name
#
# @param extra_vars [Hash] Hash with key/value pairs that will be passed as extra_vars to the ansible-runner run
# @param playbook_path [String] Path to the playbook we will want to run
# @param role_name [String] Ansible role name
# @param roles_path [String] Path to the directory with roles
# @param role_skip_facts [Boolean] Whether we should skip facts gathering, equals to 'gather_facts: False' in a
# playbook.
# @return [Array] Arguments for the ansible-runner run
def params(extra_vars:, playbook_path:, role_name:, roles_path:, role_skip_facts:)
role_or_playbook_params = if playbook_path
playbook_params(:playbook_path => playbook_path)
elsif role_name
role_params(:role_name => role_name,
:roles_path => roles_path,
:role_skip_facts => role_skip_facts)
end
def runner_params(base_dir, ansible_runner_method, extra_vars, playbook_or_role_args)
runner_args = playbook_or_role_args.dup

[shared_params(:extra_vars => extra_vars).merge(role_or_playbook_params)]
end
runner_args.delete(:roles_path) if runner_args[:roles_path].nil?
skip_facts = runner_args.delete(:role_skip_facts)
runner_args[:role_skip_facts] = nil if skip_facts

# Generate correct params if using :playbook_path
#
# @param playbook_path [String] Path to the playbook we will want to run
# @return [Hash] Partial arguments for the ansible-runner run
def playbook_params(playbook_path:)
{:playbook => playbook_path}
end
runner_args.merge!(
:ident => "result",
:hosts => "localhost"
)
runner_args[:cmdline] = AwesomeSpawn.build_command_line(nil, [{:extra_vars => extra_vars.to_json}]).lstrip if extra_vars.any?

# Generate correct params if using :role_name
#
# @param role_name [String] Ansible role name
# @param roles_path [String] Path to the directory with roles
# @param role_skip_facts [Boolean] Whether we should skip facts gathering, equals to 'gather_facts: False' in a
# playbook.
# @return [Hash] Partial arguments for the ansible-runner run
def role_params(role_name:, roles_path:, role_skip_facts:)
role_params = {:role => role_name}

role_params[:"roles-path"] = roles_path if roles_path
role_params[:"role-skip-facts"] = nil if role_skip_facts

role_params
end

# Generate correct params shared by :playbook_path or a :role_name paths
#
# @param extra_vars [Hash] Hash with key/value pairs that will be passed as extra_vars to the ansible-runner run
# @return [Hash] Partial arguments for the ansible-runner run
def shared_params(extra_vars:)
{:cmdline => "--extra-vars '#{JSON.dump(extra_vars)}'"}
[ansible_runner_method, base_dir, :json, runner_args]
end

# Asserts passed parameters are correct, if not throws an exception.
#
# @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the
# ansible-runner run
# @param extra_vars [Hash] Hash with key/value pairs that will be passed as extra_vars to the ansible-runner run
# @param playbook_path [String] Path to the playbook we will want to run
# @param roles_path [String] Path to the directory with roles
def validate_params!(env_vars, extra_vars, playbook_path, roles_path)
assert_hash!(env_vars)
assert_hash!(extra_vars)
assert_path!(playbook_path)
assert_path!(roles_path)
end
def validate_params!(env_vars, extra_vars, ansible_runner_method, playbook_or_role_args)
errors = []

# Asserts that passed argument is a hash
#
# @param hash [Hash] Passed argument we test for being Hash
def assert_hash!(hash)
unless hash.kind_of?(Hash)
raise "Passed parameter must be of type Hash, got: #{hash}"
end
end
errors << "env_vars must be a Hash, got: #{hash.class}" unless env_vars.kind_of?(Hash)
errors << "extra_vars must be a Hash, got: #{hash.class}" unless extra_vars.kind_of?(Hash)

# Asserts that passed path exists
#
# @param path [Hash, NilClass] Passed path we test for existence
def assert_path!(path)
return unless path
unless %w(run start).include?(ansible_runner_method.to_s)
errors << "ansible_runner_method must be 'run' or 'start'"
end

unless File.exist?(path)
raise "File doesn't exist: #{path}"
unless playbook_or_role_args.keys == [:playbook] || playbook_or_role_args.keys.sort == [:role, :role_skip_facts, :roles_path]
errors << "Unexpected playbook/role args: #{args}"
end
end

# Returns ansible-runner executable command
#
# @param base_dir [String] ansible-runner private_data_dir parameter
# @param ansible_runner_method [String] Method we will use to run the ansible-runner. It can be either "run",
# which is sync call, or "start" which is async call
# @return [String] ansible-runner executable command
def ansible_command(base_dir, ansible_runner_method)
"ansible-runner #{ansible_runner_method} #{base_dir} --json -i result --hosts localhost"
playbook = playbook_or_role_args[:playbook]
errors << "playbook path doesn't exist: #{playbook}" if playbook && !File.exist?(playbook)
role = playbook_or_role_args[:role]
errors << "role path doesn't exist: #{role}" if role && !File.exist?(role)

raise ArgumentError, errors.join("; ") if errors.any?
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/ansible/runner/response_async.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ def initialize(base_dir:, ident: "result")

# @return [Boolean] true if the ansible job is still running, false when it's finished
def running?
AwesomeSpawn.run("ansible-runner is-alive #{base_dir} --json -i result").exit_status.zero?
AwesomeSpawn.run("ansible-runner", :params => ["is-alive", base_dir, :json, {:ident => "result"}]).success?
end

# Stops the running Ansible job
def stop
AwesomeSpawn.run("ansible-runner stop #{base_dir} --json -i result")
AwesomeSpawn.run("ansible-runner", :params => ["stop", base_dir, :json, {:ident => "result"}])
end

# @return [Ansible::Runner::Response, NilClass] Response object with all details about the Ansible run, or nil
Expand Down

0 comments on commit bcf0405

Please sign in to comment.