Example #1
0
def test_registry_auth_cleanup(tmp_path, runtime):
    pdd_path = tmp_path / 'private_data_dir'
    pdd_path.mkdir()
    private_data_dir = str(pdd_path)

    rc = RunnerConfig(private_data_dir=private_data_dir,
                      playbook='ping.yml',
                      process_isolation_executable=runtime,
                      process_isolation=True,
                      container_image='foo.invalid/alan/runner',
                      container_auth_data={
                          'host': 'https://somedomain.invalid',
                          'username': '******',
                          'password': '******'
                      },
                      ident='awx_123')
    rc.prepare()
    assert rc.registry_auth_path
    assert os.path.exists(rc.registry_auth_path)

    ct = cleanup_dirs(pattern=private_data_dir, grace_period=0)
    assert ct == 1

    assert not os.path.exists(private_data_dir)
    assert not os.path.exists(rc.registry_auth_path)
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_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_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_with_defaults(mocker):
    mocker.patch('os.makedirs', return_value=True)

    rc = RunnerConfig('/')

    rc.prepare_inventory = mocker.Mock()
    rc.prepare_env = mocker.Mock()
    rc.prepare_command = mocker.Mock()

    rc.ssh_key_data = None
    rc.artifact_dir = '/'
    rc.env = {}

    with pytest.raises(ConfigurationError) as exc:
        rc.prepare()

    assert str(exc.value) == 'No executable for runner to run'
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"