def test_prepare_env_ad_hoc_command():
    rc = RunnerConfig(private_data_dir="/")

    value = {'AD_HOC_COMMAND_ID': 'teststring'}
    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 rc.cwd == '/'
def test_prepare_environment_pexpect_defaults():
    rc = RunnerConfig(private_data_dir="/")
    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_wrap_args_with_ssh_agent_silent():
    rc = RunnerConfig('/')
    res = rc.wrap_args_with_ssh_agent(['ansible-playbook', 'main.yaml'],
                                      '/tmp/sshkey',
                                      silence_ssh_add=True)
    assert res == [
        'ssh-agent', 'sh', '-c',
        'ssh-add /tmp/sshkey 2>/dev/null && rm -f /tmp/sshkey && ansible-playbook main.yaml'
    ]
def test_prepare_env_defaults():
    with patch('os.path.exists') as path_exists:
        path_exists.return_value = True
        rc = RunnerConfig('/')
        rc.prepare_env()
        assert rc.idle_timeout is None
        assert rc.job_timeout is None
        assert rc.pexpect_timeout == 5
        assert rc.cwd == '/project'
def test_prepare_env_sshkey():
    rc = RunnerConfig('/')

    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_command_with_args():
    rc = RunnerConfig('/')

    value = 'test string'
    args_side_effect = partial(load_file_side_effect, 'args', value)

    with patch.object(rc.loader, 'load_file', side_effect=args_side_effect):
        rc.prepare_command()
        assert rc.command == value
Example #7
0
def test_generate_ansible_command_with_api_extravars():
    rc = RunnerConfig(private_data_dir='/',
                      playbook='main.yaml',
                      extravars={"foo": "bar"})
    rc.prepare_inventory()

    cmd = rc.generate_ansible_command()
    assert cmd == [
        'ansible-playbook', '-i', '/inventory', '-e', 'foo="bar"', 'main.yaml'
    ]
Example #8
0
def test_prepare_inventory():
    rc = RunnerConfig(private_data_dir='/')
    rc.prepare_inventory()
    assert rc.inventory == '/inventory'
    rc.inventory = '/tmp/inventory'
    rc.prepare_inventory()
    assert rc.inventory == '/tmp/inventory'
    rc.inventory = 'localhost,anotherhost,'
    rc.prepare_inventory()
    assert rc.inventory == 'localhost,anotherhost,'
Example #9
0
def test_generate_ansible_command_with_cmdline_args(cmdline):
    rc = RunnerConfig(private_data_dir='/', playbook='main.yaml')
    rc.prepare_inventory()
    rc.extra_vars = {}

    cmdline_side_effect = partial(load_file_side_effect, 'env/cmdline', cmdline)

    with patch.object(rc.loader, 'load_file', side_effect=cmdline_side_effect):
        cmd = rc.generate_ansible_command()
        assert cmd == ['ansible-playbook'] + shlex.split(cmdline) + ['-i', '/inventory', 'main.yaml']
def test_prepare_environment_vars_only_strings():
    rc = RunnerConfig(private_data_dir="/")
    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'], string_types)
        assert 'B' in rc.env
        assert isinstance(rc.env['B'], string_types)
        assert 'C' in rc.env
        assert isinstance(rc.env['C'], string_types)
Example #11
0
def rc(request, tmpdir):
    rc = RunnerConfig(str(tmpdir))
    rc.suppress_ansible_output = True
    rc.expect_passwords = {pexpect.TIMEOUT: None, pexpect.EOF: None}
    rc.cwd = str(tmpdir)
    rc.env = {}
    rc.job_timeout = .1
    rc.idle_timeout = 0
    rc.pexpect_timeout = .1
    return rc
def test_prepare_env_passwords():
    rc = RunnerConfig(private_data_dir='/')
    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], re._pattern_type)
        assert 'secret' in rc.expect_passwords.values()
Example #13
0
def test_runner_config_init_with_ident():
    rc = RunnerConfig('/', ident='test')
    assert rc.private_data_dir == '/'
    assert rc.ident == 'test'
    assert rc.playbook is None
    assert rc.inventory is None
    assert rc.limit is None
    assert rc.module is None
    assert rc.module_args is None
    assert rc.artifact_dir == os.path.join('/artifacts/test')
    assert isinstance(rc.loader, ArtifactLoader)
Example #14
0
def test_prepare_env_settings():
    rc = RunnerConfig('/')

    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
Example #15
0
def test_runner_config_init_defaults():
    rc = RunnerConfig('/')
    assert rc.private_data_dir == '/'
    assert rc.ident is not None
    assert rc.playbook is None
    assert rc.inventory is None
    assert rc.limit is None
    assert rc.module is None
    assert rc.module_args is None
    assert rc.artifact_dir == os.path.join('/artifacts/%s' % rc.ident)
    assert isinstance(rc.loader, ArtifactLoader)
def test_generate_ansible_command_with_api_extravars():
    rc = RunnerConfig(private_data_dir='/',
                      playbook='main.yaml',
                      extravars={"foo": "bar"})
    with patch('os.path.exists') as path_exists:
        path_exists.return_value = True
        rc.prepare_inventory()

    cmd = rc.generate_ansible_command()
    assert cmd == [
        'ansible-playbook', '-i', '/inventory', '-e', 'foo="bar"', 'main.yaml'
    ]
Example #17
0
def test_prepare_command_defaults():
    rc = RunnerConfig('/')

    cmd_side_effect = partial(load_file_side_effect, 'args')

    def generate_side_effect():
        return StringIO('test "string with spaces"')

    with patch.object(rc.loader, 'load_file', side_effect=cmd_side_effect):
        with patch.object(rc, 'generate_ansible_command', side_effect=generate_side_effect):
            rc.prepare_command()
            rc.command == ['test', '"string with spaces"']
Example #18
0
def test_profiling_plugin_settings_with_custom_intervals(mock_mkdir):
    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'
Example #19
0
def test_generate_ansible_command_with_cmdline_args(cmdline, tokens):
    rc = RunnerConfig(private_data_dir='/', playbook='main.yaml')
    with patch('os.path.exists') as path_exists:
        path_exists.return_value = True
        rc.prepare_inventory()
    rc.extra_vars = {}

    cmdline_side_effect = partial(load_file_side_effect, 'env/cmdline',
                                  cmdline)
    with patch.object(rc.loader, 'load_file', side_effect=cmdline_side_effect):
        cmd = rc.generate_ansible_command()
        assert cmd == ['ansible-playbook'
                       ] + tokens + ['-i', '/inventory', 'main.yaml']
Example #20
0
def test_prepare_env_passwords():
    rc = RunnerConfig(private_data_dir='/')

    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()
Example #21
0
def test_process_isolation_settings():
    rc = RunnerConfig('/')
    rc.artifact_dir = '/tmp/artifacts'
    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'

    with patch('os.path.exists') as path_exists:
        path_exists.return_value = True
        rc.prepare()

    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'
    ]
Example #22
0
def test_prepare_with_defaults():
    rc = RunnerConfig('/')

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

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

    with raises(ConfigurationError) as exc:
        rc.prepare()
        assert str(exc) == 'Runner playbook is not defined'
Example #23
0
def test_prepare_inventory(path_exists):
    rc = RunnerConfig(private_data_dir='/')
    rc.prepare_inventory()
    assert rc.inventory == '/inventory'
    rc.inventory = '/tmp/inventory'
    rc.prepare_inventory()
    assert rc.inventory == '/tmp/inventory'
    rc.inventory = 'localhost,anotherhost,'
    rc.prepare_inventory()
    assert rc.inventory == 'localhost,anotherhost,'
    path_exists.return_value = False
    rc.inventory = None
    rc.prepare_inventory()
    assert rc.inventory is None
Example #24
0
def test_prepare_environment_vars_only_strings():
    rc = RunnerConfig(private_data_dir="/")

    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'], string_types)
        assert 'B' in rc.env
        assert isinstance(rc.env['B'], string_types)
        assert 'C' in rc.env
        assert isinstance(rc.env['C'], string_types)
Example #25
0
def test_prepare_with_defaults():
    rc = RunnerConfig('/')

    rc.prepare_inventory = Mock()
    rc.prepare_env = Mock()
    rc.prepare_command = 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'
Example #26
0
def test_container_volume_mounting_with_Z(tmpdir):
    rc = RunnerConfig(str(tmpdir))
    rc.container_volume_mounts = ['project_path:project_path:Z']
    rc.container_name = 'foo'
    rc.env = {}
    new_args = rc.wrap_args_for_containerization(
        ['ansible-playbook', 'foo.yml'])
    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))
Example #27
0
def test_process_isolation_defaults():
    rc = RunnerConfig('/')
    rc.artifact_dir = '/tmp/artifacts'
    rc.playbook = 'main.yaml'
    rc.command = 'ansible-playbook'
    rc.process_isolation = True
    rc.prepare()

    assert rc.command == [
        'bwrap',
        '--unshare-pid',
        '--dev-bind', '/', '/',
        '--proc', '/proc',
        '--bind', '/', '/',
        '--chdir', '/project',
        'ansible-playbook', '-i', '/inventory', 'main.yaml',
    ]
Example #28
0
def ansible_run(
    playbook: Path,
    tags: str | None = None,
    extra_vars: dict[str, str] | None = None,
) -> None:
    """Run an ansible playbook.

    Parameters
    ----------
    playbook : pathlib.Path
        The path to the ansible Playbook
    tags : str, optional
        The tags to use
    extra_vars : dict [str, str], optional
        The extra_vars to use.

    Returns
    -------
    None

    """
    with tempfile.TemporaryDirectory() as temp_dir:

        logger.debug(
            f'Created temporary directory "{temp_dir}" for the '
            "ansible-runner. The temporary directory will be removed after "
            "the ansible-runner succeeded or failed.")

        runner_config: RunnerConfig = RunnerConfig(
            private_data_dir=temp_dir,
            playbook=playbook,
            tags=tags,
            extravars=extra_vars,
        )
        runner_config.prepare()

        runner: Runner = Runner(config=runner_config)
        runner.run()

        # debug output
        logger.debug("Runner status")
        logger.debug(f"{runner.status}: {runner.rc}")
        for host_event in runner.events:
            logger.debug(host_event["event"])
        logger.debug(f"Final status: {runner.stats}")
Example #29
0
def test_process_isolation_and_directory_isolation(mock_makedirs, mock_copytree, mock_mkdtemp, mock_chmod):
    rc = RunnerConfig('/')
    rc.artifact_dir = '/tmp/artifacts'
    rc.directory_isolation_path = '/tmp/dirisolation'
    rc.playbook = 'main.yaml'
    rc.command = 'ansible-playbook'
    rc.process_isolation = True
    rc.prepare()

    assert rc.command == [
        'bwrap',
        '--unshare-pid',
        '--dev-bind', '/', '/',
        '--proc', '/proc',
        '--bind', '/', '/',
        '--chdir', '/tmp/dirisolation/foo',
        'ansible-playbook', '-i', '/inventory', 'main.yaml',
    ]
Example #30
0
def init_runner(**kwargs):
    '''
    Initialize the Runner() instance

    This function will properly initialize both run() and run_async()
    functions in the same way and return a value instance of Runner.
    '''
    dump_artifacts(kwargs)

    debug = kwargs.pop('debug', None)

    logfile = None
    if debug:
        logfile = os.path.join(kwargs['private_data_dir'], 'debug.log')

    configure_logging(filename=logfile, debug=debug)

    rc = RunnerConfig(**kwargs)
    rc.prepare()

    return Runner(rc)