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
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' ]
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,'
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)
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()
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)
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
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' ]
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"']
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'
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']
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()
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' ]
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'
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
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)
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'
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))
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', ]
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}")
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', ]
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)