def test_containerization_settings(tmp_path, runtime, mocker): mocker.patch('os.path.isdir', return_value=True) mocker.patch('os.path.exists', return_value=True) mock_containerized = mocker.patch( 'ansible_runner.runner_config.RunnerConfig.containerized', new_callable=mocker.PropertyMock) mock_containerized.return_value = True rc = RunnerConfig(tmp_path) rc.ident = 'foo' rc.playbook = 'main.yaml' rc.command = 'ansible-playbook' rc.process_isolation = True rc.process_isolation_executable = runtime rc.container_image = 'my_container' rc.container_volume_mounts = ['/host1:/container1', '/host2:/container2'] rc.prepare() extra_container_args = [] if runtime == 'podman': extra_container_args = ['--quiet'] else: extra_container_args = [f'--user={os.getuid()}'] expected_command_start = [runtime, 'run', '--rm', '--tty', '--interactive', '--workdir', '/runner/project'] + \ ['-v', '{}/:/runner/:Z'.format(rc.private_data_dir)] + \ ['-v', '/host1/:/container1/', '-v', '/host2/:/container2/'] + \ ['--env-file', '{}/env.list'.format(rc.artifact_dir)] + \ extra_container_args + \ ['--name', 'ansible_runner_foo'] + \ ['my_container', 'ansible-playbook', '-i', '/runner/inventory/hosts', 'main.yaml'] assert expected_command_start == rc.command
def test_profiling_plugin_settings(mocker): mocker.patch('os.mkdir', return_value=True) rc = RunnerConfig('/') rc.playbook = 'main.yaml' rc.command = 'ansible-playbook' rc.resource_profiling = True rc.resource_profiling_base_cgroup = 'ansible-runner' rc.prepare() expected_command_start = [ 'cgexec', '--sticky', '-g', 'cpuacct,memory,pids:ansible-runner/{}'.format(rc.ident), 'ansible-playbook', 'main.yaml', ] assert expected_command_start == rc.command assert 'main.yaml' in rc.command assert rc.env['ANSIBLE_CALLBACK_WHITELIST'] == 'cgroup_perf_recap' assert rc.env['CGROUP_CONTROL_GROUP'] == 'ansible-runner/{}'.format( rc.ident) assert rc.env['CGROUP_OUTPUT_DIR'] == os.path.normpath( os.path.join(rc.private_data_dir, 'profiling_data')) assert rc.env['CGROUP_OUTPUT_FORMAT'] == 'json' assert rc.env['CGROUP_CPU_POLL_INTERVAL'] == '0.25' assert rc.env['CGROUP_MEMORY_POLL_INTERVAL'] == '0.25' assert rc.env['CGROUP_PID_POLL_INTERVAL'] == '0.25' assert rc.env['CGROUP_FILE_PER_TASK'] == 'True' assert rc.env['CGROUP_WRITE_FILES'] == 'True' assert rc.env['CGROUP_DISPLAY_RECAP'] == 'False'
def test_bwrap_process_isolation_defaults(mocker): mocker.patch('os.makedirs', return_value=True) rc = RunnerConfig('/') rc.artifact_dir = '/tmp/artifacts' rc.playbook = 'main.yaml' rc.command = 'ansible-playbook' rc.process_isolation = True rc.process_isolation_executable = 'bwrap' path_exists = mocker.patch('os.path.exists') path_exists.return_value = True rc.prepare() assert rc.command == [ 'bwrap', '--die-with-parent', '--unshare-pid', '--dev-bind', '/', '/', '--proc', '/proc', '--bind', '/', '/', '--chdir', '/project', 'ansible-playbook', '-i', '/inventory', 'main.yaml', ]
def test_prepare_with_ssh_key(mocker): mocker.patch('os.makedirs', return_value=True) open_fifo_write_mock = mocker.patch( 'ansible_runner.config._base.open_fifo_write') rc = RunnerConfig('/') rc.prepare_inventory = mocker.Mock() rc.prepare_command = mocker.Mock() rc.wrap_args_with_ssh_agent = mocker.Mock() rc.ssh_key_data = None rc.artifact_dir = '/' rc.env = {} rc.execution_mode = ExecutionMode.ANSIBLE_PLAYBOOK rc.playbook = 'main.yaml' rc.ssh_key_data = '01234567890' rc.command = 'ansible-playbook' mocker.patch.dict('os.environ', {'AWX_LIB_DIRECTORY': '/'}) rc.prepare() assert rc.ssh_key_path == '/ssh_key_data' assert rc.wrap_args_with_ssh_agent.called assert open_fifo_write_mock.called
def test_process_isolation_settings(mocker, tmp_path): mocker.patch('os.path.isdir', return_value=False) mocker.patch('os.path.exists', return_value=True) mocker.patch('os.makedirs', return_value=True) rc = RunnerConfig('/') rc.artifact_dir = tmp_path.joinpath('artifacts').as_posix() rc.playbook = 'main.yaml' rc.command = 'ansible-playbook' rc.process_isolation = True rc.process_isolation_executable = 'not_bwrap' rc.process_isolation_hide_paths = ['/home', '/var'] rc.process_isolation_show_paths = ['/usr'] rc.process_isolation_ro_paths = ['/venv'] rc.process_isolation_path = tmp_path.as_posix() mocker.patch('os.path.exists', return_value=True) rc.prepare() print(rc.command) assert rc.command[0:8] == [ 'not_bwrap', '--die-with-parent', '--unshare-pid', '--dev-bind', '/', '/', '--proc', '/proc', ] # hide /home assert rc.command[8] == '--bind' assert 'ansible_runner_pi' in rc.command[9] assert rc.command[10] == os.path.realpath('/home') # needed for Mac # hide /var assert rc.command[11] == '--bind' assert 'ansible_runner_pi' in rc.command[12] assert rc.command[13] == '/var' or rc.command[13] == '/private/var' # read-only bind assert rc.command[14:17] == ['--ro-bind', '/venv', '/venv'] # root bind assert rc.command[17:20] == ['--bind', '/', '/'] # show /usr assert rc.command[20:23] == ['--bind', '/usr', '/usr'] # chdir and ansible-playbook command assert rc.command[23:] == [ '--chdir', '/project', 'ansible-playbook', '-i', '/inventory', 'main.yaml' ]
def test_bwrap_process_isolation_and_directory_isolation( mocker, patch_private_data_dir, tmp_path): def mock_exists(path): if path == "/project": return False return True class MockArtifactLoader: def __init__(self, base_path): self.base_path = base_path def load_file(self, path, objtype=None, encoding='utf-8'): raise ConfigurationError def isfile(self, path): return False mocker.patch('ansible_runner.config.runner.os.makedirs', return_value=True) mocker.patch('ansible_runner.config.runner.os.chmod', return_value=True) mocker.patch('ansible_runner.config.runner.os.path.exists', mock_exists) mocker.patch('ansible_runner.config._base.ArtifactLoader', new=MockArtifactLoader) artifact_path = tmp_path / 'artifacts' artifact_path.mkdir() rc = RunnerConfig('/') rc.artifact_dir = tmp_path / 'artifacts' rc.directory_isolation_path = tmp_path / 'dirisolation' rc.playbook = 'main.yaml' rc.command = 'ansible-playbook' rc.process_isolation = True rc.process_isolation_executable = 'bwrap' rc.prepare() assert rc.command == [ 'bwrap', '--die-with-parent', '--unshare-pid', '--dev-bind', '/', '/', '--proc', '/proc', '--bind', '/', '/', '--chdir', os.path.realpath(rc.directory_isolation_path), 'ansible-playbook', '-i', '/inventory', 'main.yaml', ]
def test_profiling_plugin_settings_with_custom_intervals(mocker): mocker.patch('os.mkdir', return_value=True) rc = RunnerConfig('/') rc.playbook = 'main.yaml' rc.command = 'ansible-playbook' rc.resource_profiling = True rc.resource_profiling_base_cgroup = 'ansible-runner' rc.resource_profiling_cpu_poll_interval = '.5' rc.resource_profiling_memory_poll_interval = '.75' rc.resource_profiling_pid_poll_interval = '1.5' rc.prepare() assert rc.env['CGROUP_CPU_POLL_INTERVAL'] == '.5' assert rc.env['CGROUP_MEMORY_POLL_INTERVAL'] == '.75' assert rc.env['CGROUP_PID_POLL_INTERVAL'] == '1.5'
def test_prepare(mocker): mocker.patch.dict( 'os.environ', { 'PYTHONPATH': '/python_path_via_environ', 'AWX_LIB_DIRECTORY': '/awx_lib_directory_via_environ', }) mocker.patch('os.makedirs', return_value=True) rc = RunnerConfig('/') rc.prepare_inventory = mocker.Mock() rc.prepare_command = mocker.Mock() rc.ssh_key_data = None rc.artifact_dir = '/' rc.env = {} rc.execution_mode = ExecutionMode.ANSIBLE_PLAYBOOK rc.playbook = 'main.yaml' rc.prepare() assert rc.prepare_inventory.called assert rc.prepare_command.called assert not hasattr(rc, 'ssh_key_path') assert not hasattr(rc, 'command') assert rc.env['ANSIBLE_STDOUT_CALLBACK'] == 'awx_display' assert rc.env['ANSIBLE_RETRY_FILES_ENABLED'] == 'False' assert rc.env['ANSIBLE_HOST_KEY_CHECKING'] == 'False' assert rc.env['AWX_ISOLATED_DATA_DIR'] == '/' assert rc.env['PYTHONPATH'] == '/python_path_via_environ:/awx_lib_directory_via_environ', \ "PYTHONPATH is the union of the env PYTHONPATH and AWX_LIB_DIRECTORY" del rc.env['PYTHONPATH'] os.environ['PYTHONPATH'] = "/foo/bar/python_path_via_environ" rc.prepare() assert rc.env['PYTHONPATH'] == "/foo/bar/python_path_via_environ:/awx_lib_directory_via_environ", \ "PYTHONPATH is the union of the explicit env['PYTHONPATH'] override and AWX_LIB_DIRECTORY"