diff --git a/lib/smart_proxy_ansible/runner/ansible_runner.rb b/lib/smart_proxy_ansible/runner/ansible_runner.rb index f56d1dd..13374a5 100644 --- a/lib/smart_proxy_ansible/runner/ansible_runner.rb +++ b/lib/smart_proxy_ansible/runner/ansible_runner.rb @@ -28,6 +28,7 @@ def initialize(input, suspended_action:, id: nil) @passphrase = action_input['secrets']['key_passphrase'] @execution_timeout_interval = action_input[:execution_timeout_interval] @cleanup_working_dirs = action_input.fetch(:cleanup_working_dirs, true) + prune_known_hosts_on_first_execution end def start @@ -261,21 +262,40 @@ def log_event(description, event) logger.debug("[foreman_ansible] - handling event #{description}: #{JSON.pretty_generate(event)}") if logger.level <= ::Logger::DEBUG end - # Each per-host task has inventory only for itself, we must - # collect all the partial inventories into one large inventory - # containing all the hosts. + # Rebuilds a unified Ansible inventory from multiple per-host inventories. + # @param input [Hash] The input hash mapping hostnames to inventory data. + # @return [Hash] The merged inventory. def rebuild_inventory(input) - action_inputs = input.values.map { |hash| hash[:input][:action_input] } - inventories = action_inputs.map { |hash| hash[:ansible_inventory] } - host_vars = inventories.map { |i| i['_meta']['hostvars'] }.reduce({}) do |acc, hosts| - hosts.reduce(acc) do |inner_acc, (hostname, vars)| + action_inputs = input.values.map { |entry| entry['input']['action_input'] } + inventories = action_inputs.map { |action_input| action_input['ansible_inventory'] } + first_execution_by_host = action_inputs.to_h { |action_input| [action_input['name'], action_input['first_execution']] } + + host_vars = merge_hostvars_from_inventories(inventories) + + # Use the first inventory's group vars as a base, fallback to empty hash if missing + group_vars = inventories.first.dig('all', 'vars') || {} + + inventory = { + 'all' => { + 'hosts' => host_vars, + 'vars' => group_vars + } + } + + update_first_execution_flags(inventory['all']['hosts'], first_execution_by_host) + + inventory + end + + # Helper: Merges hostvars from a list of inventories, ensuring ssh key is set. + def merge_hostvars_from_inventories(inventories) + inventories.each_with_object({}) do |inventory, acc| + inventory.dig('_meta', 'hostvars')&.each do |hostname, vars| + # Ensure the ssh key is set for each host vars[:ansible_ssh_private_key_file] ||= Proxy::RemoteExecution::Ssh::Plugin.settings[:ssh_identity_key_file] - inner_acc.merge(hostname => vars) + acc[hostname] = vars end end - - { 'all' => { 'hosts' => host_vars, - 'vars' => inventories.first['all']['vars'] } } end def working_dir @@ -303,6 +323,57 @@ def rebuild_secrets(inventory, input) inventory end + + # Removes known hosts entries for hosts marked as 'first_execution' in the inventory. + # This ensures SSH host key checking does not fail on first connection. + # @return [void] + def prune_known_hosts_on_first_execution + @inventory.dig('all', 'hosts')&.each_value do |host_data| + next unless host_data.dig("foreman", "first_execution") + + interface = host_data.dig("foreman", "foreman_interfaces", 0) + next unless interface + + extract_host_identifiers(interface, host_data).each do |host| + extract_ports(host_data).each do |port| + Proxy::RemoteExecution::Utils.prune_known_hosts!(host, port, logger) + end + end + end + end + + private + + # Updates the 'first_execution' flag in the foreman data for each host in the inventory. + # @param hosts [Hash] hostname => host data hash + # @param execution_flags [Hash] hostname => boolean (first_execution) + # @return [void] + def update_first_execution_flags(hosts, execution_flags) + hosts.each do |hostname, vars| + foreman = vars['foreman'] + next unless foreman + + if execution_flags.key?(hostname) + foreman['first_execution'] = execution_flags[hostname] + end + end + end + + def extract_host_identifiers(interface, host_data) + [ + interface["ip"], + interface["ip6"], + host_data["ansible_host"], + interface["name"] + ].compact.uniq + end + + def extract_ports(host_data) + [ + host_data["ansible_ssh_port"], + host_data["ansible_port"] + ].compact.uniq + end end end end diff --git a/smart_proxy_ansible.gemspec b/smart_proxy_ansible.gemspec index 5644042..185ccb9 100644 --- a/smart_proxy_ansible.gemspec +++ b/smart_proxy_ansible.gemspec @@ -29,5 +29,5 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rake', '~> 13.0' gem.add_runtime_dependency('smart_proxy_dynflow', '~> 0.8') - gem.add_runtime_dependency('smart_proxy_remote_execution_ssh', '~> 0.4') + gem.add_runtime_dependency('smart_proxy_remote_execution_ssh', '~> 0.5') end diff --git a/test/ansible_runner_test.rb b/test/ansible_runner_test.rb index 19d3a6a..aefd254 100644 --- a/test/ansible_runner_test.rb +++ b/test/ansible_runner_test.rb @@ -3,10 +3,18 @@ require 'test_helper' require 'smart_proxy_ansible' require 'smart_proxy_ansible/runner/ansible_runner' +require 'json' +require 'smart_proxy_remote_execution_ssh' +require 'smart_proxy_remote_execution_ssh/utils' module Proxy::Ansible module Runner class AnsibleRunnerTest < Minitest::Test + def setup + # Setup remote execution plugin settings + Proxy::RemoteExecution::Ssh::Plugin.load_test_settings({}) + end + describe Proxy::Ansible::Runner::AnsibleRunner do it 'parses files without event data' do content = <<~JSON @@ -53,6 +61,280 @@ class AnsibleRunnerTest < Minitest::Test assert_equal 'mypass', host_vars['ansible_become_password'] end end + + describe '#rebuild_inventory' do + let(:runner) { ::Proxy::Ansible::Runner::AnsibleRunner.allocate } + let(:ssh_key_path) { '/tmp/dummy_id_rsa' } + + before do + ssh_settings = { ssh_identity_key_file: ssh_key_path } + Proxy::RemoteExecution::Ssh::Plugin.stubs(:settings).returns(ssh_settings) + end + + test 'merges hostvars from multiple inventories' do + input = { + 'host1' => { 'input' => { 'action_input' => { + 'name' => 'host1', + 'ansible_inventory' => { + '_meta' => { 'hostvars' => { 'host1' => { 'foo' => 'bar' } } }, + 'all' => { 'vars' => { 'group_var' => 1 } } + } } } }, + 'host2' => { 'input' => { 'action_input' => { + 'name' => 'host2', + 'ansible_inventory' => { + '_meta' => { 'hostvars' => { 'host2' => { 'baz' => 'qux' } } }, + 'all' => { 'vars' => { 'group_var' => 2 } } + } } } } + } + inventory = runner.send(:rebuild_inventory, input) + assert inventory['all']['hosts'].key?('host1') + assert inventory['all']['hosts'].key?('host2') + assert_equal 'bar', inventory['all']['hosts']['host1']['foo'] + assert_equal 'qux', inventory['all']['hosts']['host2']['baz'] + end + + test 'sets first_execution flag per host' do + input = { + 'host1' => { 'input' => { 'action_input' => { + 'name' => 'host1', + 'ansible_inventory' => { + '_meta' => { 'hostvars' => { 'host1' => { 'foreman' => {} } } }, + 'all' => { 'vars' => {} } + }, + 'first_execution' => true + } } }, + 'host2' => { 'input' => { 'action_input' => { + 'name' => 'host2', + 'ansible_inventory' => { + '_meta' => { 'hostvars' => { 'host2' => { 'foreman' => {} } } }, + 'all' => { 'vars' => {} } + }, + 'first_execution' => false + } } } + } + inventory = runner.send(:rebuild_inventory, input) + assert_equal true, inventory['all']['hosts']['host1']['foreman']['first_execution'] + assert_equal false, inventory['all']['hosts']['host2']['foreman']['first_execution'] + end + + test 'handles missing group vars gracefully' do + input = { + 'host1' => { 'input' => { 'action_input' => { + 'name' => 'host1', + 'ansible_inventory' => { + '_meta' => { 'hostvars' => { 'host1' => {} } }, + 'all' => {} # no vars + }, + 'first_execution' => true + } } } + } + inventory = runner.send(:rebuild_inventory, input) + assert_equal({}, inventory['all']['vars']) + end + + test 'ensures ssh key is set for each host' do + input = { + 'host1' => { 'input' => { 'action_input' => { + 'name' => 'host1', + 'ansible_inventory' => { + '_meta' => { 'hostvars' => { 'host1' => {} } }, + 'all' => { 'vars' => {} } + }, + 'first_execution' => true + } } } + } + inventory = runner.send(:rebuild_inventory, input) + assert_equal ssh_key_path, inventory['all']['hosts']['host1'][:ansible_ssh_private_key_file] + end + + test 'handles empty input gracefully' do + input = {} + assert_raises(NoMethodError) do + runner.send(:rebuild_inventory, input) + end + end + + test 'rebuild_inventory works with real input.json fixture' do + input = JSON.parse(File.read(File.join(__dir__, 'fixtures/input.json'))) + inventory = runner.send(:rebuild_inventory, input) + + # Basic structure checks + assert inventory.key?('all'), 'Inventory should have an all group' + assert inventory['all'].key?('hosts'), 'All group should have hosts' + assert_kind_of Hash, inventory['all']['hosts'] + + # Check that at least one host from input is present + input.keys.each do |host| + assert inventory['all']['hosts'].key?(host), "Host #{host} should be present in rebuilt inventory" + end + + # Check that first_execution flag is set correctly + input.each do |host, data| + expected_flag = data['input']['action_input']['first_execution'] + actual_flag = inventory['all']['hosts'][host]['foreman']['first_execution'] + assert_equal expected_flag, actual_flag, "first_execution flag for #{host} should match input" + end + end + end + + describe '#prune_known_hosts_on_first_execution' do + let(:runner) { ::Proxy::Ansible::Runner::AnsibleRunner.allocate } + let(:logger_stub) { stub(:debug => nil, :warn => nil, :error => nil, :level => 1) } + + before do + runner.stubs(:logger).returns(logger_stub) + Proxy::RemoteExecution::Utils.stubs(:prune_known_hosts!) + end + + test 'skips when inventory has no hosts' do + runner.instance_variable_set(:@inventory, { 'all' => {} }) + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).never + runner.send(:prune_known_hosts_on_first_execution) + end + + test 'skips hosts without first_execution flag' do + inventory = { + 'all' => { + 'hosts' => { + 'host1.example.com' => { + 'foreman' => { 'first_execution' => false }, + 'ansible_host' => '192.168.1.1', + 'ansible_port' => 22 + } + } + } + } + runner.instance_variable_set(:@inventory, inventory) + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).never + runner.send(:prune_known_hosts_on_first_execution) + end + + test 'skips hosts without foreman interfaces' do + inventory = { + 'all' => { + 'hosts' => { + 'host1.example.com' => { + 'foreman' => { 'first_execution' => true }, + 'ansible_host' => '192.168.1.1', + 'ansible_port' => 22 + } + } + } + } + runner.instance_variable_set(:@inventory, inventory) + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).never + runner.send(:prune_known_hosts_on_first_execution) + end + + test 'processes all identifiers and ports for host with first execution' do + inventory = { + 'all' => { + 'hosts' => { + 'host1.example.com' => { + 'foreman' => { + 'first_execution' => true, + 'foreman_interfaces' => [{ 'ip' => '192.168.1.1', + 'ip6' => '2001:db8::1', + 'name' => 'host1.example.com' }] + }, + 'ansible_host' => '192.168.1.2', + 'ansible_port' => 22, + 'ansible_ssh_port' => 2222 + } + } + } + } + runner.instance_variable_set(:@inventory, inventory) + + expected_identifiers = ['192.168.1.1', '2001:db8::1', '192.168.1.2', 'host1.example.com'] + expected_ports = [22, 2222] + + expected_identifiers.product(expected_ports).each do |host, port| + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).with(host, port, logger_stub) + end + + runner.send(:prune_known_hosts_on_first_execution) + end + + test 'handles hosts with minimal interface information' do + inventory = { + 'all' => { + 'hosts' => { + 'host1.example.com' => { + 'foreman' => { + 'first_execution' => true, + 'foreman_interfaces' => [{ 'ip' => '192.168.1.1' }] + }, + 'ansible_port' => 22 + } + } + } + } + runner.instance_variable_set(:@inventory, inventory) + + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).with('192.168.1.1', 22, logger_stub) + runner.send(:prune_known_hosts_on_first_execution) + end + + test 'processes multiple hosts correctly' do + inventory = { + 'all' => { + 'hosts' => { + 'host1.example.com' => { + 'foreman' => { + 'first_execution' => true, + 'foreman_interfaces' => [{ 'ip' => '192.168.1.1' }] + }, + 'ansible_port' => 22 + }, + 'host2.example.com' => { + 'foreman' => { + 'first_execution' => false, + 'foreman_interfaces' => [{ 'ip' => '192.168.1.2' }] + }, + 'ansible_port' => 22 + }, + 'host3.example.com' => { + 'foreman' => { + 'first_execution' => true, + 'foreman_interfaces' => [{ 'ip' => '192.168.1.3' }] + }, + 'ansible_port' => 22 + } + } + } + } + runner.instance_variable_set(:@inventory, inventory) + + # Should only process host1 and host3 + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).with('192.168.1.1', 22, logger_stub) + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).with('192.168.1.3', 22, logger_stub) + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).with('192.168.1.2', anything, anything).never + + runner.send(:prune_known_hosts_on_first_execution) + end + + test 'prune_known_hosts_on_first_execution works with real inventory.json fixture' do + inventory = JSON.parse(File.read(File.join(__dir__, 'fixtures/inventory.json'))) + runner.instance_variable_set(:@inventory, inventory) + + # Find all hosts with first_execution true + hosts = inventory['all']['hosts'].select do |_, vars| + vars.dig('foreman', 'first_execution') + end + + hosts.each do |_, vars| + interface = vars.dig('foreman', 'foreman_interfaces', 0) + next unless interface + identifiers = [interface['ip'], interface['ip6'], vars['ansible_host'], interface['name']].compact.uniq + ports = [vars['ansible_ssh_port'], vars['ansible_port']].compact.uniq + identifiers.product(ports).each do |host, port| + Proxy::RemoteExecution::Utils.expects(:prune_known_hosts!).with(host, port, logger_stub) + end + end + runner.send(:prune_known_hosts_on_first_execution) + end + end end end end diff --git a/test/fixtures/input.json b/test/fixtures/input.json new file mode 100644 index 0000000..b83c3dd --- /dev/null +++ b/test/fixtures/input.json @@ -0,0 +1,348 @@ +{ + "host2.example.com.local.test": { + "execution_plan_id": "60b7cc85-f23a-46d5-b88b-8582cdf78504", + "run_step_id": 2, + "input": { + "action_input": { + "proxy_operation_name": "ansible-runner", + "time_to_pickup": 86400, + "tags": "", + "tags_flag": "include", + "ansible_inventory": { + "all": { + "hosts": [ + "host2.example.com.local.test" + ], + "vars": {} + }, + "_meta": { + "hostvars": { + "host2.example.com.local.test": { + "foreman": { + "foreman_hostname": "host2.example.com", + "foreman_fqdn": "host2.example.com.local.test", + "root_pw": "$6$dummy$notarealpasswordhash", + "foreman_subnets": [ + { + "name": "ipv6_subnet", + "network": "2001:db8:1::1", + "mask": "ffff:ff00::", + "gateway": "", + "dns_primary": "", + "dns_secondary": "", + "from": "", + "to": "", + "boot_mode": "DHCP", + "ipam": "None", + "vlanid": null, + "mtu": 1500, + "nic_delay": null, + "network_type": "IPv6", + "description": "" + } + ], + "foreman_interfaces": [ + { + "ip": "156.168.121.32", + "ip6": "2001:db7:1:1234:abcd:5678:90ab:cde9", + "mac": "52:54:00:bc:c1:59", + "name": "host2.example.com.local.test", + "attrs": {}, + "virtual": false, + "link": true, + "identifier": "", + "managed": true, + "primary": true, + "provision": true, + "subnet": null, + "subnet6": { + "name": "ipv6_subnet", + "network": "2001:db8:1::1", + "mask": "ffff:ff00::", + "gateway": "", + "dns_primary": "", + "dns_secondary": "", + "from": "", + "to": "", + "boot_mode": "DHCP", + "ipam": "None", + "vlanid": null, + "mtu": 1500, + "nic_delay": null, + "network_type": "IPv6", + "description": "" + }, + "tag": null, + "attached_to": null, + "type": "Interface" + } + ], + "location": "Default Location", + "location_title": "Default Location", + "organization": "Default Organization", + "organization_title": "Default Organization", + "domainname": "local.test", + "foreman_domain_description": "", + "owner_name": "Admin User", + "owner_email": "root@localdomain.net", + "ssh_authorized_keys": [], + "foreman_users": { + "admin": { + "firstname": "Admin", + "lastname": "User", + "mail": "root@localdomain.net", + "description": null, + "fullname": "Admin User", + "name": "admin", + "ssh_authorized_keys": [] + } + }, + "server_ca": null, + "ssl_ca": "-----BEGIN CERTIFICATE-----\nHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSQhKfSG1u/h/nutC3s7HiguYH2uzAK\nBggqhkjOPQQDAgNIADBFAiEAj/8ml5IqZmxb1Q93vPmAVafo6DnhN20jPc3kzdcj\nYg0CIH29gvAmLzxFkpHOFDOCdS+4hszszYrGXnXtbASf0HZS\n-----END CERTIFICATE-----\n", + "first_execution": true + }, + "foreman_ansible_roles": [], + "ansible_connection": "ssh", + "ansible_ssh_private_key_file": "~/.ssh/id_rsa_foreman_proxy", + "ansible_winrm_server_cert_validation": "validate", + "ansible_user": "root", + "ansible_become_method": "sudo", + "ansible_port": 22, + "ansible_host": "156.168.121.32", + "ansible_ssh_port": 22, + "ansible_ssh_user": "root", + "ansible_roles_check_mode": false, + "remote_execution_ssh_user": "root", + "remote_execution_effective_user_method": "sudo", + "remote_execution_connect_by_ip": true, + "host_packages": "", + "host_registration_insights": false, + "host_registration_remote_execution": true, + "remote_execution_ssh_keys": [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDU/== email@gmail.com" + ], + "ansible_password": null, + "ansible_become_password": "" + } + } + } + }, + "verbosity_level": "4", + "remote_execution_command": false, + "name": "host2.example.com.local.test", + "check_mode": false, + "job_check_mode": true, + "cleanup_working_dirs": true, + "hostname": "156.168.121.32", + "script": "---\n- hosts: all\n pre_tasks:\n - name: Display all parameters known for the Foreman host\n debug:\n var: foreman\n tags:\n - always\n tasks:\n - name: Apply roles\n include_role:\n name: \"{{ role }}\"\n tags:\n - always\n loop: \"{{ foreman_ansible_roles }}\"\n loop_control:\n loop_var: role", + "execution_timeout_interval": null, + "secrets": { + "ssh_password": null, + "key_passphrase": null, + "effective_user_password": null, + "per-host": { + "host2.example.com.local.test": { + "ansible_password": null, + "ansible_become_password": "" + } + } + }, + "use_batch_triggering": true, + "first_execution": true, + "alternative_names": { + "fqdn": "host2.example.com.local.test" + }, + "connection_options": { + "retry_interval": 15, + "retry_count": 4, + "proxy_batch_triggering": true + }, + "proxy_url": "http://localhost:8000", + "proxy_action_name": "Proxy::Ansible::TaskLauncher::Playbook::PlaybookRunnerAction", + "current_request_id": "7ed8b66b-4b21-4979-8068-f44399c9e86f", + "current_timezone": "Asia/Jerusalem", + "current_organization_id": null, + "current_location_id": null, + "current_user_id": 4, + "callback": { + "task_id": "d5e82140-4439-4fe4-9fe2-fcdfdfb7bbda", + "step_id": 3 + } + }, + "action_class": "Proxy::Ansible::TaskLauncher::Playbook::PlaybookRunnerAction" + } + }, + "host1.example.com.local.test": { + "execution_plan_id": "ae547530-20ad-4ee2-9c3e-050c74294104", + "run_step_id": 2, + "input": { + "action_input": { + "proxy_operation_name": "ansible-runner", + "time_to_pickup": 86400, + "tags": "", + "tags_flag": "include", + "ansible_inventory": { + "all": { + "hosts": [ + "host1.example.com.local.test" + ], + "vars": {} + }, + "_meta": { + "hostvars": { + "host1.example.com.local.test": { + "foreman": { + "foreman_hostname": "host1.example.com", + "foreman_fqdn": "host1.example.com.local.test", + "root_pw": "$6$dummy$notarealpasswordhash", + "foreman_subnets": [ + { + "name": "ipv6_subnet", + "network": "2001:db8:1::1", + "mask": "ffff:ff00::", + "gateway": "", + "dns_primary": "", + "dns_secondary": "", + "from": "", + "to": "", + "boot_mode": "DHCP", + "ipam": "None", + "vlanid": null, + "mtu": 1500, + "nic_delay": null, + "network_type": "IPv6", + "description": "" + } + ], + "foreman_interfaces": [ + { + "ip": "154.168.121.32", + "ip6": "2001:db7:1:1234:abcd:5678:90ab:cde8", + "mac": "52:54:00:bc:c1:49", + "name": "host1.example.com.local.test", + "attrs": {}, + "virtual": false, + "link": true, + "identifier": "", + "managed": true, + "primary": true, + "provision": true, + "subnet": null, + "subnet6": { + "name": "ipv6_subnet", + "network": "2001:db8:1::1", + "mask": "ffff:ff00::", + "gateway": "", + "dns_primary": "", + "dns_secondary": "", + "from": "", + "to": "", + "boot_mode": "DHCP", + "ipam": "None", + "vlanid": null, + "mtu": 1500, + "nic_delay": null, + "network_type": "IPv6", + "description": "" + }, + "tag": null, + "attached_to": null, + "type": "Interface" + } + ], + "location": "Default Location", + "location_title": "Default Location", + "organization": "Default Organization", + "organization_title": "Default Organization", + "domainname": "local.test", + "foreman_domain_description": "", + "owner_name": "Admin User", + "owner_email": "root@localdomain.net", + "ssh_authorized_keys": [], + "foreman_users": { + "admin": { + "firstname": "Admin", + "lastname": "User", + "mail": "root@localdomain.net", + "description": null, + "fullname": "Admin User", + "name": "admin", + "ssh_authorized_keys": [] + } + }, + "server_ca": null, + "ssl_ca": "-----BEGIN CERTIFICATE-----\nHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSQhKfSG1u/h/nutC3s7HiguYH2uzAK\nBggqhkjOPQQDAgNIADBFAiEAj/8ml5IqZmxb1Q93vPmAVafo6DnhN20jPc3kzdcj\nYg0CIH29gvAmLzxFkpHOFDOCdS+4hszszYrGXnXtbASf0HZS\n-----END CERTIFICATE-----\n", + "first_execution": false + }, + "foreman_ansible_roles": [], + "ansible_connection": "ssh", + "ansible_ssh_private_key_file": "~/.ssh/id_rsa_foreman_proxy", + "ansible_winrm_server_cert_validation": "validate", + "ansible_user": "root", + "ansible_become_method": "sudo", + "ansible_port": 22, + "ansible_host": "clone-ansible-host-test", + "ansible_ssh_port": 22, + "ansible_ssh_user": "root", + "ansible_roles_check_mode": false, + "remote_execution_ssh_user": "root", + "remote_execution_effective_user_method": "sudo", + "remote_execution_connect_by_ip": true, + "host_packages": "", + "host_registration_insights": false, + "host_registration_remote_execution": true, + "remote_execution_ssh_keys": [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDU/== email@gmail.com" + ], + "ansible_password": null, + "ansible_become_password": "" + } + } + } + }, + "verbosity_level": "4", + "remote_execution_command": false, + "name": "host1.example.com.local.test", + "check_mode": false, + "job_check_mode": true, + "cleanup_working_dirs": true, + "hostname": "154.168.121.32", + "script": "---\n- hosts: all\n pre_tasks:\n - name: Display all parameters known for the Foreman host\n debug:\n var: foreman\n tags:\n - always\n tasks:\n - name: Apply roles\n include_role:\n name: \"{{ role }}\"\n tags:\n - always\n loop: \"{{ foreman_ansible_roles }}\"\n loop_control:\n loop_var: role", + "execution_timeout_interval": null, + "secrets": { + "ssh_password": null, + "key_passphrase": null, + "effective_user_password": null, + "per-host": { + "host1.example.com.local.test": { + "ansible_password": null, + "ansible_become_password": "" + } + } + }, + "use_batch_triggering": true, + "first_execution": false, + "alternative_names": { + "fqdn": "host1.example.com.local.test" + }, + "connection_options": { + "retry_interval": 15, + "retry_count": 4, + "proxy_batch_triggering": true + }, + "proxy_url": "http://localhost:8000", + "proxy_action_name": "Proxy::Ansible::TaskLauncher::Playbook::PlaybookRunnerAction", + "current_request_id": "7ed8b66b-4b21-4979-8068-f44399c9e86f", + "current_timezone": "Asia/Jerusalem", + "current_organization_id": null, + "current_location_id": null, + "current_user_id": 4, + "callback": { + "task_id": "9c0dae04-3ac6-4290-86d2-4d05c215a3e3", + "step_id": 3 + } + }, + "action_class": "Proxy::Ansible::TaskLauncher::Playbook::PlaybookRunnerAction" + } + } +} diff --git a/test/fixtures/inventory.json b/test/fixtures/inventory.json new file mode 100644 index 0000000..23316fc --- /dev/null +++ b/test/fixtures/inventory.json @@ -0,0 +1,221 @@ +{ + "all": { + "hosts": { + "hostkeys-clone.local.test": { + "foreman": { + "foreman_hostname": "hostkeys-clone", + "foreman_fqdn": "hostkeys-clone.local.test", + "root_pw": "$6$dummy$notarealpasswordhash", + "foreman_subnets": [ + { + "name": "ipv6_subnet", + "network": "2001:db8:1::1", + "mask": "ffff:ff00::", + "gateway": "", + "dns_primary": "", + "dns_secondary": "", + "from": "", + "to": "", + "boot_mode": "DHCP", + "ipam": "None", + "vlanid": null, + "mtu": 1500, + "nic_delay": null, + "network_type": "IPv6", + "description": "" + } + ], + "foreman_interfaces": [ + { + "ip": "156.168.121.32", + "ip6": "2001:db7:1:1234:abcd:5678:90ab:cde9", + "mac": "52:54:00:bc:c1:59", + "name": "hostkeys-clone.local.test", + "attrs": {}, + "virtual": false, + "link": true, + "identifier": "", + "managed": true, + "primary": true, + "provision": true, + "subnet": null, + "subnet6": { + "name": "ipv6_subnet", + "network": "2001:db8:1::1", + "mask": "ffff:ff00::", + "gateway": "", + "dns_primary": "", + "dns_secondary": "", + "from": "", + "to": "", + "boot_mode": "DHCP", + "ipam": "None", + "vlanid": null, + "mtu": 1500, + "nic_delay": null, + "network_type": "IPv6", + "description": "" + }, + "tag": null, + "attached_to": null, + "type": "Interface" + } + ], + "location": "Default Location", + "location_title": "Default Location", + "organization": "Default Organization", + "organization_title": "Default Organization", + "domainname": "local.test", + "foreman_domain_description": "", + "owner_name": "Admin User", + "owner_email": "root@localdomain.net", + "ssh_authorized_keys": [], + "foreman_users": { + "admin": { + "firstname": "Admin", + "lastname": "User", + "mail": "root@localdomain.net", + "description": null, + "fullname": "Admin User", + "name": "admin", + "ssh_authorized_keys": [] + } + }, + "server_ca": null, + "ssl_ca": "-----BEGIN CERTIFICATE-----\nHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSQhKfSG1u/h/nutC3s7HiguYH2uzAK\nBggqhkjOPQQDAgNIADBFAiEAj/8ml5IqZmxb1Q93vPmAVafo6DnhN20jPc3kzdcj\nYg0CIH29gvAmLzxFkpHOFDOCdS+4hszszYrGXnXtbASf0HZS\n-----END CERTIFICATE-----\n", + "first_execution": true + }, + "foreman_ansible_roles": [], + "ansible_connection": "ssh", + "ansible_ssh_private_key_file": "~/.ssh/id_rsa_foreman_proxy", + "ansible_winrm_server_cert_validation": "validate", + "ansible_user": "root", + "ansible_become_method": "sudo", + "ansible_port": 22, + "ansible_host": "156.168.121.32", + "ansible_ssh_port": 22, + "ansible_ssh_user": "root", + "ansible_roles_check_mode": false, + "remote_execution_ssh_user": "root", + "remote_execution_effective_user_method": "sudo", + "remote_execution_connect_by_ip": true, + "host_packages": "", + "host_registration_insights": false, + "host_registration_remote_execution": true, + "remote_execution_ssh_keys": [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDU/== email@gmail.com" + ], + "ansible_password": null, + "ansible_become_password": "" + }, + "hostkeys-keys.local.test": { + "foreman": { + "foreman_hostname": "hostkeys-keys", + "foreman_fqdn": "hostkeys-keys.local.test", + "root_pw": "root_pw", + "foreman_subnets": [ + { + "name": "ipv6_subnet", + "network": "2001:db8:1::1", + "mask": "ffff:ff00::", + "gateway": "", + "dns_primary": "", + "dns_secondary": "", + "from": "", + "to": "", + "boot_mode": "DHCP", + "ipam": "None", + "vlanid": null, + "mtu": 1500, + "nic_delay": null, + "network_type": "IPv6", + "description": "" + } + ], + "foreman_interfaces": [ + { + "ip": "154.168.121.32", + "ip6": "2001:db7:1:1234:abcd:5678:90ab:cde8", + "mac": "52:54:00:bc:c1:49", + "name": "hostkeys-keys.local.test", + "attrs": {}, + "virtual": false, + "link": true, + "identifier": "", + "managed": true, + "primary": true, + "provision": true, + "subnet": null, + "subnet6": { + "name": "ipv6_subnet", + "network": "2001:db8:1::1", + "mask": "ffff:ff00::", + "gateway": "", + "dns_primary": "", + "dns_secondary": "", + "from": "", + "to": "", + "boot_mode": "DHCP", + "ipam": "None", + "vlanid": null, + "mtu": 1500, + "nic_delay": null, + "network_type": "IPv6", + "description": "" + }, + "tag": null, + "attached_to": null, + "type": "Interface" + } + ], + "location": "Default Location", + "location_title": "Default Location", + "organization": "Default Organization", + "organization_title": "Default Organization", + "domainname": "local.test", + "foreman_domain_description": "", + "owner_name": "Admin User", + "owner_email": "root@localdomain.net", + "ssh_authorized_keys": [], + "foreman_users": { + "admin": { + "firstname": "Admin", + "lastname": "User", + "mail": "root@localdomain.net", + "description": null, + "fullname": "Admin User", + "name": "admin", + "ssh_authorized_keys": [] + } + }, + "server_ca": null, + "ssl_ca": "-----BEGIN CERTIFICATE-----\nHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBSQhKfSG1u/h/nutC3s7HiguYH2uzAK\nBggqhkjOPQQDAgNIADBFAiEAj/8ml5IqZmxb1Q93vPmAVafo6DnhN20jPc3kzdcj\nYg0CIH29gvAmLzxFkpHOFDOCdS+4hszszYrGXnXtbASf0HZS\n-----END CERTIFICATE-----\n", + "first_execution": false + }, + "foreman_ansible_roles": [], + "ansible_connection": "ssh", + "ansible_ssh_private_key_file": "~/.ssh/id_rsa_foreman_proxy", + "ansible_winrm_server_cert_validation": "validate", + "ansible_user": "root", + "ansible_become_method": "sudo", + "ansible_port": 22, + "ansible_host": "clone-ansible-host-test", + "ansible_ssh_port": 22, + "ansible_ssh_user": "root", + "ansible_roles_check_mode": false, + "remote_execution_ssh_user": "root", + "remote_execution_effective_user_method": "sudo", + "remote_execution_connect_by_ip": true, + "host_packages": "", + "host_registration_insights": false, + "host_registration_remote_execution": true, + "remote_execution_ssh_keys": [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDU/== email@gmail.com" + ], + "ansible_password": null, + "ansible_become_password": "" + } + }, + "vars": {} + } +}