def test_prepare_env_ansible_vars(mocker, tmp_path): mocker.patch.dict( 'os.environ', { 'PYTHONPATH': '/python_path_via_environ', 'AWX_LIB_DIRECTORY': '/awx_lib_directory_via_environ', }) artifact_dir = tmp_path.joinpath('some_artifacts') rc = BaseConfig(artifact_dir=artifact_dir.as_posix()) rc.ssh_key_data = None rc.env = {} rc.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS rc._prepare_env() 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'] == artifact_dir.joinpath( rc.ident).as_posix() 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_env() 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"
def test_src_dst_all_files(path, labels, mocker): """Ensure file paths are tranformed correctly into dir paths""" src_str = os.path.join(resolve_path(path.path), "") dst_str = src_str expected = generate_volmount_args(src_str=src_str, dst_str=dst_str, labels=labels) result = [] src_file = os.path.join(path.path, "", "file.txt") dest_file = src_file base_config = BaseConfig() mocker.patch("os.path.exists", return_value=True) mocker.patch("os.path.isdir", return_value=False) base_config._update_volume_mount_paths(args_list=result, src_mount_path=src_file, dst_mount_path=dest_file, labels=labels) explanation = ( f"provided: {src_file}:{dest_file}", f"got: {result}", f"expected {expected}", ) assert result == expected, explanation assert all(part.endswith('/') for part in result[1].split(':')[0:1]), explanation
def test_container_volume_mounting_with_Z(tmp_path, mocker): mocker.patch('os.path.isdir', return_value=False) mocker.patch('os.path.exists', return_value=True) mocker.patch('os.makedirs', return_value=True) rc = BaseConfig(private_data_dir=str(tmp_path)) os.path.isdir = mocker.Mock() rc.container_volume_mounts = ['project_path:project_path:Z'] rc.container_name = 'foo' rc.runner_mode = 'pexpect' rc.env = {} rc.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS rc.command = ['ansible-playbook', 'foo.yml'] rc.container_image = 'network-ee' rc.cmdline_args = ['foo.yml'] new_args = rc.wrap_args_for_containerization(rc.command, rc.execution_mode, rc.cmdline_args) assert new_args[0] == 'podman' for i, entry in enumerate(new_args): if entry == '-v': mount = new_args[i + 1] if mount.endswith('project_path/:Z'): break else: raise Exception( 'Could not find expected mount, args: {}'.format(new_args))
def test_base_config_init_with_ident(tmp_path): rc = BaseConfig(private_data_dir=tmp_path.as_posix(), ident='test') assert rc.private_data_dir == tmp_path.as_posix() assert rc.ident == 'test' assert rc.artifact_dir == tmp_path.joinpath('artifacts').joinpath( 'test').as_posix() assert isinstance(rc.loader, ArtifactLoader)
def test_containerization_unsafe_write_setting(tmp_path, runtime, mocker): mock_containerized = mocker.patch( 'ansible_runner.config._base.BaseConfig.containerized', new_callable=mocker.PropertyMock) rc = BaseConfig(private_data_dir=tmp_path) rc.ident = 'foo' rc.cmdline_args = ['main.yaml', '-i', '/tmp/inventory'] rc.command = ['ansible-playbook'] + rc.cmdline_args rc.process_isolation = True rc.runner_mode = 'pexpect' rc.process_isolation_executable = runtime rc.container_image = 'my_container' rc.container_volume_mounts = ['/host1:/container1', 'host2:/container2'] mock_containerized.return_value = True rc.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS rc._prepare_env() rc._handle_command_wrap(rc.execution_mode, rc.cmdline_args) expected = { 'docker': None, 'podman': '1', } assert rc.env.get('ANSIBLE_UNSAFE_WRITES') == expected[runtime]
def test_src_dst_all_relative_dirs(src, dst, labels, relative, mocker): mocker.patch("os.path.exists", return_value=True) mocker.patch("os.path.isdir", return_value=True) """Ensure src is resolved and dest mapped to workdir when relative""" relative_src = f"{relative}{src.path}" relative_dst = f"{relative}{dst.path}" workdir = "/workdir" src_str = os.path.join(resolve_path(relative_src), "") dst_str = os.path.join(resolve_path(os.path.join(workdir, relative_dst)), "") expected = generate_volmount_args(src_str=src_str, dst_str=dst_str, labels=labels) result = [] BaseConfig(container_workdir=workdir)._update_volume_mount_paths( args_list=result, src_mount_path=relative_src, dst_mount_path=relative_dst, labels=labels) explanation = ( f"provided: {relative_src}:{relative_dst}", f"got: {result}", f"expected {expected}", ) assert result == expected, explanation assert all(part.endswith('/') for part in result[1].split(':')[0:1]), explanation
def test_wrap_args_with_ssh_agent_defaults(tmp_path): rc = BaseConfig(private_data_dir=str(tmp_path)) res = rc.wrap_args_with_ssh_agent(['ansible-playbook', 'main.yaml'], f'{tmp_path}/sshkey') assert res == [ 'ssh-agent', 'sh', '-c', f"trap 'rm -f {tmp_path}/sshkey' EXIT && ssh-add {tmp_path}/sshkey && rm -f {tmp_path}/sshkey && ansible-playbook main.yaml" ]
def test_check_not_safe_to_mount_dir(not_safe, mocker): """Ensure unsafe directories are not mounted""" with pytest.raises(ConfigurationError): bc = BaseConfig() mocker.patch("os.path.exists", return_value=True) bc._update_volume_mount_paths(args_list=[], src_mount_path=not_safe, dst_mount_path=None)
def test_wrap_args_with_ssh_agent_with_auth(): rc = BaseConfig(private_data_dir='/tmp') res = rc.wrap_args_with_ssh_agent(['ansible-playbook', 'main.yaml'], '/tmp/sshkey', '/tmp/sshauth') assert res == [ 'ssh-agent', '-a', '/tmp/sshauth', 'sh', '-c', "trap 'rm -f /tmp/sshkey' EXIT && ssh-add /tmp/sshkey && rm -f /tmp/sshkey && ansible-playbook main.yaml" ]
def test_prepare_env_defaults(): rc = BaseConfig(host_cwd='/tmp/project') rc._prepare_env() assert rc.idle_timeout is None assert rc.job_timeout is None assert rc.pexpect_timeout == 5 assert rc.host_cwd == '/tmp/project'
def test_wrap_args_with_ssh_agent_silent(): rc = BaseConfig(private_data_dir='/tmp') res = rc.wrap_args_with_ssh_agent(['ansible-playbook', 'main.yaml'], '/tmp/sshkey', silence_ssh_add=True) assert res == [ 'ssh-agent', 'sh', '-c', "trap 'rm -f /tmp/sshkey' EXIT && ssh-add /tmp/sshkey 2>/dev/null && rm -f /tmp/sshkey && ansible-playbook main.yaml" ]
def test_check_not_safe_to_mount_file(not_safe, mocker): """Ensure unsafe directories for a given file are not mounted""" file_path = os.path.join(not_safe, "file.txt") with pytest.raises(ConfigurationError): bc = BaseConfig() mocker.patch("os.path.exists", return_value=True) bc._update_volume_mount_paths(args_list=[], src_mount_path=file_path, dst_mount_path=None)
def test_prepare_env_defaults(): with patch('os.path.exists') as path_exists: path_exists.return_value = True rc = BaseConfig(private_data_dir='/tmp') rc._prepare_env() assert rc.idle_timeout is None assert rc.job_timeout is None assert rc.pexpect_timeout == 5 assert rc.cwd == '/tmp/project'
def test_prepare_env_sshkey(mocker): rc = BaseConfig() value = '01234567890' sshkey_side_effect = partial(load_file_side_effect, 'env/ssh_key', value) mocker.patch.object(rc.loader, 'load_file', side_effect=sshkey_side_effect) rc._prepare_env() assert rc.ssh_key_data == value
def test_prepare_env_sshkey(): rc = BaseConfig(private_data_dir='/tmp') value = '01234567890' sshkey_side_effect = partial(load_file_side_effect, 'env/ssh_key', value) with patch.object(rc.loader, 'load_file', side_effect=sshkey_side_effect): rc._prepare_env() assert rc.ssh_key_data == value
def test_prepare_environment_pexpect_defaults(): rc = BaseConfig() rc._prepare_env() assert len(rc.expect_passwords) == 2 assert TIMEOUT in rc.expect_passwords assert rc.expect_passwords[TIMEOUT] is None assert EOF in rc.expect_passwords assert rc.expect_passwords[EOF] is None
def test_prepare_env_settings(): rc = BaseConfig(private_data_dir='/tmp') value = {'test': 'string'} settings_side_effect = partial(load_file_side_effect, 'env/settings', value) with patch.object(rc.loader, 'load_file', side_effect=settings_side_effect): rc._prepare_env() assert rc.settings == value
def test_base_config_init_defaults(): rc = BaseConfig(private_data_dir='/tmp') assert rc.private_data_dir == '/tmp' assert rc.ident is not None assert rc.process_isolation is False assert rc.fact_cache_type == 'jsonfile' assert rc.json_mode is False assert rc.quiet is False assert rc.quiet is False assert rc.rotate_artifacts == 0 assert rc.artifact_dir == os.path.join('/tmp/artifacts/%s' % rc.ident) assert isinstance(rc.loader, ArtifactLoader)
def test_prepare_env_settings(mocker): rc = BaseConfig() value = {'test': 'string'} settings_side_effect = partial(load_file_side_effect, 'env/settings', value) mocker.patch.object(rc.loader, 'load_file', side_effect=settings_side_effect) rc._prepare_env() assert rc.settings == value
def test_base_config_init_defaults(tmp_path): rc = BaseConfig(private_data_dir=tmp_path.as_posix()) assert rc.private_data_dir == tmp_path.as_posix() assert rc.ident is not None assert rc.process_isolation is False assert rc.fact_cache_type == 'jsonfile' assert rc.json_mode is False assert rc.quiet is False assert rc.quiet is False assert rc.rotate_artifacts == 0 assert rc.artifact_dir == tmp_path.joinpath('artifacts').joinpath( rc.ident).as_posix() assert isinstance(rc.loader, ArtifactLoader)
def test_prepare_with_ssh_key(open_fifo_write_mock): rc = BaseConfig(private_data_dir='/tmp') rc.artifact_dir = '/tmp/artifact' rc.env = {} rc.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS rc.ssh_key_data = '01234567890' rc.command = 'ansible-playbook' rc.cmdline_args = [] with patch.dict('os.environ', {'AWX_LIB_DIRECTORY': '/tmp/artifact'}): rc._prepare_env() assert rc.ssh_key_path == '/tmp/artifact/ssh_key_data' assert open_fifo_write_mock.called
def test_base_config_with_artifact_dir(tmp_path, patch_private_data_dir): rc = BaseConfig( artifact_dir=tmp_path.joinpath('this-is-some-dir').as_posix()) assert rc.artifact_dir == tmp_path.joinpath('this-is-some-dir').joinpath( rc.ident).as_posix() # Check that the private data dir is placed in our default location with our default prefix # and has some extra uniqueness on the end. base_private_data_dir = tmp_path.joinpath('.ansible-runner-').as_posix() assert rc.private_data_dir.startswith(base_private_data_dir) assert len(rc.private_data_dir) > len(base_private_data_dir) rc._prepare_env() assert not tmp_path.joinpath('artifacts').exists() assert tmp_path.joinpath('this-is-some-dir').exists()
def test_prepare_env_passwords(): rc = BaseConfig(private_data_dir='/tmp') value = {'^SSH [pP]assword.*$': 'secret'} password_side_effect = partial(load_file_side_effect, 'env/passwords', value) with patch.object(rc.loader, 'load_file', side_effect=password_side_effect): rc._prepare_env() rc.expect_passwords.pop(TIMEOUT) rc.expect_passwords.pop(EOF) assert len(rc.expect_passwords) == 1 assert isinstance(list(rc.expect_passwords.keys())[0], Pattern) assert 'secret' in rc.expect_passwords.values()
def test_prepare_environment_vars_only_strings(): rc = BaseConfig(private_data_dir="/tmp", envvars=dict(D='D')) value = dict(A=1, B=True, C="foo") envvar_side_effect = partial(load_file_side_effect, 'env/envvars', value) with patch.object(rc.loader, 'load_file', side_effect=envvar_side_effect): rc._prepare_env() assert 'A' in rc.env assert isinstance(rc.env['A'], six.string_types) assert 'B' in rc.env assert isinstance(rc.env['B'], six.string_types) assert 'C' in rc.env assert isinstance(rc.env['C'], six.string_types) assert 'D' in rc.env assert rc.env['D'] == 'D'
def test_prepare_with_ssh_key(mocker, tmp_path): open_fifo_write_mock = mocker.patch( 'ansible_runner.config._base.open_fifo_write') custom_artifacts = tmp_path.joinpath('custom_arts') rc = BaseConfig(private_data_dir=tmp_path.as_posix(), artifact_dir=custom_artifacts.as_posix()) rc.artifact_dir = custom_artifacts.as_posix() rc.env = {} rc.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS rc.ssh_key_data = '01234567890' rc.command = 'ansible-playbook' rc.cmdline_args = [] rc._prepare_env() assert rc.ssh_key_path == custom_artifacts.joinpath( 'ssh_key_data').as_posix() assert open_fifo_write_mock.called
def test_containerization_settings(tmpdir, container_runtime): with patch('ansible_runner.config._base.BaseConfig.containerized', new_callable=PropertyMock) as mock_containerized: rc = BaseConfig(private_data_dir=tmpdir) rc.ident = 'foo' rc.cmdline_args = ['main.yaml', '-i', '/tmp/inventory'] rc.command = ['ansible-playbook'] + rc.cmdline_args rc.process_isolation = True rc.runner_mode = 'pexpect' rc.process_isolation_executable = container_runtime rc.container_image = 'my_container' rc.container_volume_mounts = [ '/host1:/container1', 'host2:/container2' ] mock_containerized.return_value = True rc.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS rc._prepare_env() rc._handle_command_wrap(rc.execution_mode, rc.cmdline_args) extra_container_args = [] if container_runtime == 'podman': extra_container_args = ['--quiet'] else: extra_container_args = ['--user={os.getuid()}'] expected_command_start = [container_runtime, 'run', '--rm', '--interactive', '--tty', '--workdir', '/runner/project'] + \ ['-v', '{}/.ssh/:/home/runner/.ssh/'.format(os.environ['HOME'])] if container_runtime == 'podman': expected_command_start += [ '--group-add=root', '--userns=keep-id', '--ipc=host' ] expected_command_start += ['-v', '{}/artifacts:/runner/artifacts:Z'.format(rc.private_data_dir)] + \ ['-v', '{}:/runner:Z'.format(rc.private_data_dir)] + \ ['--env-file', '{}/env.list'.format(rc.artifact_dir)] + \ extra_container_args + \ ['--name', 'ansible_runner_foo'] + \ ['my_container', 'ansible-playbook', 'main.yaml', '-i', '/tmp/inventory'] for index, element in enumerate(expected_command_start): if '--user='******'--user=' in rc.command[index] else: assert rc.command[index] == element
def test_duplicate_detection_dst(path, mocker): mocker.patch("os.path.exists", return_value=True) mocker.patch("os.path.isdir", return_value=True) """Ensure no duplicate volumne mount entries are created""" base_config = BaseConfig() def generate(args_list): for entry in dir_variations: for label in labels: base_config._update_volume_mount_paths( args_list=first_pass, src_mount_path=path.path, dst_mount_path=entry.path, labels=label, ) first_pass = [] generate(first_pass) second_pass = first_pass[:] generate(second_pass) assert first_pass == second_pass
def test_prepare_env_ansible_vars(): rc = BaseConfig(private_data_dir='/tmp') rc.ssh_key_data = None rc.artifact_dir = '/tmp/artifact' rc.env = {} rc.execution_mode = BaseExecutionMode.ANSIBLE_COMMANDS rc._prepare_env() 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'] == '/tmp/artifact' 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_env() 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"
def test_src_dst_all_dirs(src, dst, labels, mocker): mocker.patch("os.path.exists", return_value=True) mocker.patch("os.path.isdir", return_value=True) """Ensure src and dest end with trailing slash""" src_str = os.path.join(resolve_path(src.path), "") dst_str = os.path.join(resolve_path(dst.path), "") expected = generate_volmount_args(src_str=src_str, dst_str=dst_str, labels=labels) result = [] BaseConfig()._update_volume_mount_paths(args_list=result, src_mount_path=src.path, dst_mount_path=dst.path, labels=labels) explanation = ( f"provided: {src.path}:{dst.path}", f"got: {result}", f"expected {expected}", ) assert result == expected, explanation assert all(part.endswith('/') for part in result[1].split(':')[0:1]), explanation
def test_combine_python_and_file_settings(project_fixtures): rc = BaseConfig(private_data_dir=str(project_fixtures / 'job_env'), settings={'job_timeout': 40}) rc._prepare_env() assert rc.settings == {'job_timeout': 40, 'process_isolation': True}