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

[WIP] Add Ansible Runner container support #23300

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
66 changes: 45 additions & 21 deletions lib/ansible/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ def available?
# @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: [], verbosity: 0, become_enabled: false)
def run_async(env_vars, extra_vars, playbook_path, hosts: ["localhost"], credentials: [], verbosity: 0, become_enabled: false, container_image: nil)
run_via_cli(hosts,
credentials,
env_vars,
extra_vars,
:ansible_runner_method => "start",
:playbook => playbook_path,
:verbosity => verbosity,
:become_enabled => become_enabled)
:become_enabled => become_enabled,
:container_image => container_image)
end

# Runs a role directly via ansible-runner, a simple playbook is then automatically created,
Expand All @@ -44,7 +45,7 @@ def run_async(env_vars, extra_vars, playbook_path, hosts: ["localhost"], credent
# @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: [], verbosity: 0, become_enabled: false)
def run_role_async(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true, hosts: ["localhost"], credentials: [], verbosity: 0, become_enabled: false, container_image: nil)
run_via_cli(hosts,
credentials,
env_vars,
Expand All @@ -54,7 +55,8 @@ def run_role_async(env_vars, extra_vars, role_name, roles_path:, role_skip_facts
:roles_path => roles_path,
:role_skip_facts => role_skip_facts,
:verbosity => verbosity,
:become_enabled => become_enabled)
:become_enabled => become_enabled,
:container_image => container_image)
end

# Runs a playbook via ansible-runner, see: https://ansible-runner.readthedocs.io/en/latest/standalone.html#running-playbooks
Expand All @@ -68,15 +70,16 @@ def run_role_async(env_vars, extra_vars, role_name, roles_path:, role_skip_facts
# @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: [], verbosity: 0, become_enabled: false)
def run(env_vars, extra_vars, playbook_path, tags: nil, hosts: ["localhost"], credentials: [], verbosity: 0, become_enabled: false, container_image: nil)
run_via_cli(hosts,
credentials,
env_vars,
extra_vars,
:tags => tags,
:playbook => playbook_path,
:verbosity => verbosity,
:become_enabled => become_enabled)
:tags => tags,
:playbook => playbook_path,
:verbosity => verbosity,
:become_enabled => become_enabled,
:container_image => container_image)
end

# Runs a role directly via ansible-runner, a simple playbook is then automatically created,
Expand All @@ -94,7 +97,7 @@ def run(env_vars, extra_vars, playbook_path, tags: nil, hosts: ["localhost"], cr
# @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: [], verbosity: 0, become_enabled: false)
def run_role(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true, tags: nil, hosts: ["localhost"], credentials: [], verbosity: 0, become_enabled: false, container_image: nil)
run_via_cli(hosts,
credentials,
env_vars,
Expand All @@ -104,7 +107,8 @@ def run_role(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true
:roles_path => roles_path,
:role_skip_facts => role_skip_facts,
:verbosity => verbosity,
:become_enabled => become_enabled)
:become_enabled => become_enabled,
:container_image => container_image)
end

# Runs "run" method via queue
Expand All @@ -119,12 +123,13 @@ def run_role(env_vars, extra_vars, role_name, roles_path:, role_skip_facts: true
# @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: [], verbosity: 0, become_enabled: false)
def run_queue(env_vars, extra_vars, playbook_path, user_id, queue_opts, hosts: ["localhost"], credentials: [], verbosity: 0, become_enabled: false, container_image: nil)
kwargs = {
:hosts => hosts,
:credentials => credentials,
:verbosity => verbosity,
:become_enabled => become_enabled
:hosts => hosts,
:credentials => credentials,
:verbosity => verbosity,
:become_enabled => become_enabled,
:container_image => container_image
}
run_in_queue("run", user_id, queue_opts, [env_vars, extra_vars, playbook_path, kwargs])
end
Expand All @@ -144,14 +149,15 @@ def run_queue(env_vars, extra_vars, playbook_path, user_id, queue_opts, hosts: [
# @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: [], verbosity: 0, become_enabled: false)
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, become_enabled: false, container_image: nil)
kwargs = {
:roles_path => roles_path,
:role_skip_facts => role_skip_facts,
:hosts => hosts,
:credentials => credentials,
:verbosity => verbosity,
:become_enabled => become_enabled
:become_enabled => become_enabled,
:container_image => container_image
}
run_in_queue("run_role", user_id, queue_opts, [env_vars, extra_vars, role_name, kwargs])
end
Expand Down Expand Up @@ -194,7 +200,11 @@ def run_in_queue(method_name, user_id, queue_opts, args)
# @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", verbosity: 0, become_enabled: false, **playbook_or_role_args)
def run_via_cli(hosts, credentials, env_vars, extra_vars, tags: nil, ansible_runner_method: "run", verbosity: 0, become_enabled: false, container_image: nil, **playbook_or_role_args)
if container_image && async?(ansible_runner_method)
raise NotImplementedError, "container_image in async mode is not yet implemented"
end

# 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 @@ -218,8 +228,9 @@ def run_via_cli(hosts, credentials, env_vars, extra_vars, tags: nil, ansible_run
create_hosts_file(base_dir, hosts)
create_extra_vars_file(base_dir, extra_vars_hash)
create_cmdline_file(base_dir, command_line_hash)
create_project_dir(base_dir, playbook_or_role_args) if container_image

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

begin
fetch_galaxy_roles(playbook_or_role_args)
Expand Down Expand Up @@ -267,7 +278,7 @@ def async?(ansible_runner_method)
ansible_runner_method == "start"
end

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

runner_args.delete(:roles_path) if runner_args[:roles_path].nil?
Expand All @@ -281,6 +292,12 @@ def runner_params(base_dir, ansible_runner_method, playbook_or_role_args, verbos
runner_args[:project_dir] = File.dirname(playbook)
end

if container_image
runner_args[:process_isolation] = nil
runner_args[:container_image] = container_image
runner_args[:project_dir] = "/runner/project" # TODO: Default from ansible-runner - try to move towards using this directory across the board
end

if verbosity.to_i > 0
v_flag = "-#{"v" * verbosity.to_i.clamp(1, 5)}"
runner_args[v_flag] = nil
Expand Down Expand Up @@ -365,6 +382,13 @@ def create_cmdline_file(dir, cmd_line)
File.write(cmd_line_file, cmd_string)
end

def create_project_dir(base_dir, playbook_or_role_args)
return unless playbook_or_role_args[:playbook] # TODO: Support roles later

playbook_path = File.dirname(playbook_or_role_args[:playbook])
FileUtils.cp_r(playbook_path, File.join(base_dir, "project"))
end

def env_dir(base_dir)
FileUtils.mkdir_p(File.join(base_dir, "env")).first
end
Expand Down
14 changes: 13 additions & 1 deletion spec/lib/ansible/runner_execution_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def expect_ansible_runner_success(response)
it "runs a playbook with variables in a vars file" do
playbook = data_directory.join("hello_world_vars_file.yml")

response = Ansible::Runner.public_send(method_under_test, env_vars, extra_vars, playbook)
response = Ansible::Runner.public_send(method_under_test, env_vars, extra_vars, playbook, verbosity: 6)
response = response.wait(5.seconds) if async

expect_ansible_runner_success(response)
Expand Down Expand Up @@ -73,6 +73,18 @@ def expect_ansible_runner_success(response)
end
end

it "runs a playbook in a container" do
pending("container_image in async mode is not yet implemented") if async

playbook = data_directory.join("hello_world.yml")

response = Ansible::Runner.public_send(method_under_test, env_vars, extra_vars, playbook, :container_image => "ghcr.io/ansible-community/community-ee-base:latest")
response = response.wait(5.seconds) if async

expect_ansible_runner_success(response)
expect(response.human_stdout).to include('"msg": "Hello World!"')
end

it "with a payload that fails before running even starts" do
playbook = data_directory.join("hello_world.yml")

Expand Down
17 changes: 17 additions & 0 deletions spec/lib/ansible/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,17 @@
expect(response.debug).to eq(true)
end

it "calls run with container_image support" 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")
end.and_return(result)

response = described_class.run(env_vars, extra_vars, playbook, container_image: "ghcr.io/ansible-community/community-ee-base:latest")
end

it "calls run with become options" do
expect(AwesomeSpawn).to receive(:run) do |command, options|
expect(command).to eq("ansible-runner")
Expand Down Expand Up @@ -197,6 +208,12 @@
runner_result = described_class.run_async(env_vars, extra_vars, playbook)
expect(runner_result).kind_of?(Ansible::Runner::ResponseAsync)
end

it "raises NotImplementedError for container_image" do
expect do
described_class.run_async(env_vars, extra_vars, playbook, container_image: "ghcr.io/ansible-community/community-ee-base:latest")
end.to raise_error(NotImplementedError)
end
end

describe ".run_queue" do
Expand Down
Loading