def test_get_fact_cached(self): inventory = make_inventory(hosts=("anotherhost", )) state = State(inventory, Config()) fact_hash = "a-fact-hash" cached_fact = {"this is a cached fact"} anotherhost = inventory.get_host("anotherhost") anotherhost.facts[fact_hash] = cached_fact connect_all(state) with patch("pyinfra.connectors.ssh.run_shell_command" ) as fake_run_command: fake_run_command.return_value = MagicMock(), [("stdout", "some-output")] fact_data = get_facts( state, Command, args=("yes", ), kwargs={"_sudo": True}, fact_hash=fact_hash, ) assert fact_data == {anotherhost: cached_fact} fake_run_command.assert_not_called()
def test_get_fact(self): inventory = make_inventory(hosts=("anotherhost", )) state = State(inventory, Config()) anotherhost = inventory.get_host("anotherhost") connect_all(state) with patch("pyinfra.connectors.ssh.run_shell_command" ) as fake_run_command: fake_run_command.return_value = MagicMock(), [("stdout", "some-output")] fact_data = get_facts(state, Command, ("yes", )) assert fact_data == {anotherhost: "some-output"} fake_run_command.assert_called_with( state, anotherhost, "yes", print_input=False, print_output=False, return_combined_output=True, **_get_executor_defaults(state, anotherhost), )
def test_get_fact_error_ignore(self): inventory = make_inventory(hosts=("anotherhost", )) state = State(inventory, Config()) anotherhost = inventory.get_host("anotherhost") connect_all(state) anotherhost.current_op_global_kwargs = { "ignore_errors": True, } with patch("pyinfra.connectors.ssh.run_shell_command" ) as fake_run_command: fake_run_command.return_value = False, MagicMock() fact_data = get_facts(state, Command, ("fail command", )) assert fact_data == {anotherhost: None} fake_run_command.assert_called_with( state, anotherhost, "fail command", print_input=False, print_output=False, return_combined_output=True, **_get_executor_defaults(state, anotherhost), )
def test_run_shell_command(self): inventory = make_inventory(hosts=('@local',)) state = State(inventory, Config()) host = inventory.get_host('@local') command = 'echo hi' self.fake_popen_mock().returncode = 0 out = host.run_shell_command(state, command, stdin='hello', print_output=True) assert len(out) == 3 status, stdout, stderr = out assert status is True self.fake_popen_mock().stdin.write.assert_called_with(b'hello\n') combined_out = host.run_shell_command( state, command, stdin='hello', print_output=True, return_combined_output=True, ) assert len(combined_out) == 2 shell_command = make_unix_command(command) self.fake_popen_mock.assert_called_with( shell_command, shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE, )
def test_run_shell_command(self): inventory = make_inventory(hosts=("@docker/not-an-image", )) State(inventory, Config()) command = "echo hi" self.fake_popen_mock().returncode = 0 host = inventory.get_host("@docker/not-an-image") host.connect() out = host.run_shell_command( command, stdin="hello", get_pty=True, print_output=True, ) assert len(out) == 3 assert out[0] is True command = make_unix_command(command).get_raw_value() command = shlex.quote(command) docker_command = "docker exec -it containerid sh -c {0}".format( command) shell_command = make_unix_command(docker_command).get_raw_value() self.fake_popen_mock.assert_called_with( shell_command, shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE, )
def test_get_fact_error(self): inventory = make_inventory(hosts=("anotherhost", )) state = State(inventory, Config()) anotherhost = inventory.get_host("anotherhost") connect_all(state) with patch("pyinfra.connectors.ssh.run_shell_command" ) as fake_run_command: fake_run_command.return_value = False, MagicMock() with self.assertRaises(PyinfraError) as context: get_facts(state, Command, ("fail command", )) assert context.exception.args[0] == "No hosts remaining!" fake_run_command.assert_called_with( state, anotherhost, "fail command", print_input=False, print_output=False, return_combined_output=True, **_get_executor_defaults(state, anotherhost), )
def test_run_shell_command(self): fake_winrm_session = MagicMock() fake_winrm = MagicMock() fake_stdin = MagicMock() fake_stdout = MagicMock() fake_winrm.run_cmd.return_value = fake_stdin, fake_stdout, MagicMock() fake_winrm_session.return_value = fake_winrm inventory = make_inventory(hosts=('@winrm/somehost', )) State(inventory, Config()) host = inventory.get_host('@winrm/somehost') host.connect() command = 'echo hi' out = host.run_shell_command(command, stdin='hello', print_output=True) assert len(out) == 3 status, stdout, stderr = out # TODO: assert status is True combined_out = host.run_shell_command( command, print_output=True, return_combined_output=True, ) assert len(combined_out) == 2
def test_run_shell_command_masked(self, fake_click): inventory = make_inventory(hosts=('@local', )) State(inventory, Config()) host = inventory.get_host('@local') command = StringCommand('echo', MaskString('top-secret-stuff')) self.fake_popen_mock().returncode = 0 out = host.run_shell_command(command, print_output=True, print_input=True) assert len(out) == 3 status, stdout, stderr = out assert status is True self.fake_popen_mock.assert_called_with( "sh -c 'echo top-secret-stuff'", shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE, ) fake_click.echo.assert_called_with( "{0}>>> sh -c 'echo ***'".format(host.print_prefix), err=True, )
def test_connect_disconnect_host(self): inventory = make_inventory(hosts=("@docker/not-an-image", )) state = State(inventory, Config()) host = inventory.get_host("@docker/not-an-image") host.connect(reason=True) assert len(state.active_hosts) == 0 host.disconnect()
def __init__(self, make_names_data=make_names_data_local, general_facts={}, fail_percent=100, connect_timeout=5, ): hosts = [] groups = defaultdict(lambda: ([], {})) names_data = make_names_data() if callable( make_names_data) else make_names_data for name, data, group_names in names_data: hosts.append((name, data)) for group_name in group_names: if name not in groups[group_name][0]: groups[group_name][0].append(name) for host in hosts: for fact_name, fact in general_facts.items(): host[1][fact_name] = fact # First we setup some inventory we want to target # the first argument is a tuple of (list all all hosts, global/ALL data) self.inventory = Inventory((hosts, {}), **groups) # Now we create a new config (w/optional args) self.config = Config( FAIL_PERCENT=fail_percent, CONNECT_TIMEOUT=connect_timeout, ) # Setup the pyinfra state for this deploy self.state = State(self.inventory, self.config)
def test_full_op_fail(self): inventory = make_inventory() state = State(inventory, Config()) connect_all(state) add_op(state, server.shell, 'echo "hi"') with patch('pyinfra.api.connectors.ssh.run_shell_command' ) as fake_run_command: fake_channel = FakeChannel(1) fake_run_command.return_value = ( False, FakeBuffer('', fake_channel), ) with self.assertRaises(PyinfraError) as e: run_ops(state) assert e.exception.args[0] == 'No hosts remaining!' somehost = inventory.get_host('somehost') # Ensure the op was not flagged as success assert state.results[somehost]['success_ops'] == 0 # And was flagged asn an error assert state.results[somehost]['error_ops'] == 1
def test_ignore_errors_op_fail(self): inventory = make_inventory() state = State(inventory, Config()) connect_all(state) add_op(state, server.shell, 'echo "hi"', ignore_errors=True) with patch('pyinfra.api.connectors.ssh.run_shell_command' ) as fake_run_command: fake_channel = FakeChannel(1) fake_run_command.return_value = ( False, FakeBuffer('', fake_channel), ) # This should run OK run_ops(state) somehost = inventory.get_host('somehost') # Ensure the op was added to results assert state.results[somehost]['ops'] == 1 assert state.results[somehost]['error_ops'] == 1 # But not as a success assert state.results[somehost]['success_ops'] == 0
def test_connect_all_error(self): inventory = make_inventory( hosts=('@dockerssh/somehost:a-broken-image', )) state = State(inventory, Config()) with self.assertRaises(PyinfraError): connect_all(state)
def test_get_fact_error(self): inventory = make_inventory(hosts=('anotherhost', )) state = State(inventory, Config()) anotherhost = inventory.get_host('anotherhost') connect_all(state) with patch('pyinfra.api.connectors.ssh.run_shell_command' ) as fake_run_command: fake_run_command.return_value = False, MagicMock(), MagicMock() with self.assertRaises(PyinfraError) as context: get_facts(state, 'command', ('fail command', )) assert context.exception.args[0] == 'No hosts remaining!' fake_run_command.assert_called_with( state, anotherhost, 'fail command', print_input=False, print_output=False, shell_executable=None, su_user=None, sudo=False, sudo_user=None, timeout=None, use_sudo_password=False, )
def test_get_fact(self): inventory = make_inventory(hosts=('anotherhost', )) state = State(inventory, Config()) anotherhost = inventory.get_host('anotherhost') connect_all(state) with patch('pyinfra.api.connectors.ssh.run_shell_command' ) as fake_run_command: fake_run_command.return_value = MagicMock(), MagicMock( ), MagicMock() fact_data = get_facts(state, 'command', ('yes', )) assert fact_data == {anotherhost: ''} fake_run_command.assert_called_with( state, anotherhost, 'yes', print_input=False, print_output=False, shell_executable=None, su_user=None, sudo=False, sudo_user=None, timeout=None, use_sudo_password=False, )
def test_run_shell_command(self, fake_ssh_client): fake_ssh = MagicMock() fake_stdin = MagicMock() fake_stdout = MagicMock() fake_ssh.exec_command.return_value = fake_stdin, fake_stdout, MagicMock() fake_ssh_client.return_value = fake_ssh inventory = make_inventory(hosts=('somehost',)) state = State(inventory, Config()) host = inventory.get_host('somehost') host.connect(state) command = 'echo Šablony' fake_stdout.channel.recv_exit_status.return_value = 0 out = host.run_shell_command(state, command, stdin='hello', print_output=True) assert len(out) == 3 status, stdout, stderr = out assert status is True fake_stdin.write.assert_called_with(b'hello\n') combined_out = host.run_shell_command( state, command, stdin='hello', print_output=True, return_combined_output=True, ) assert len(combined_out) == 2 fake_ssh.exec_command.assert_called_with("sh -c 'echo Šablony'", get_pty=False)
def test_run_shell_command_masked(self, fake_ssh_client, fake_click): fake_ssh = MagicMock() fake_stdout = MagicMock() fake_ssh.exec_command.return_value = MagicMock(), fake_stdout, MagicMock() fake_ssh_client.return_value = fake_ssh inventory = make_inventory(hosts=('somehost',)) state = State(inventory, Config()) host = inventory.get_host('somehost') host.connect(state) command = StringCommand('echo', MaskString('top-secret-stuff')) fake_stdout.channel.recv_exit_status.return_value = 0 out = host.run_shell_command(state, command, print_output=True, print_input=True) assert len(out) == 3 status, stdout, stderr = out assert status is True fake_ssh.exec_command.assert_called_with( "sh -c 'echo top-secret-stuff'", get_pty=False, ) fake_click.echo.assert_called_with( "{0}>>> sh -c 'echo ***'".format(host.print_prefix), )
def test_put_file_su_user_fail(self, fake_sftp_client, fake_ssh_client): inventory = make_inventory(hosts=('anotherhost',)) state = State(inventory, Config()) host = inventory.get_host('anotherhost') host.connect(state) stdout_mock = MagicMock() stdout_mock.channel.recv_exit_status.return_value = 1 fake_ssh_client().exec_command.return_value = MagicMock(), stdout_mock, MagicMock() fake_open = mock_open(read_data='test!') with patch('pyinfra.api.util.open', fake_open, create=True): status = host.put_file( state, 'not-a-file', 'not-another-file', print_output=True, su_user='******', ) assert status is False fake_ssh_client().exec_command.assert_called_with(( "su centos -s `which sh` -c 'mv " '/tmp/pyinfra-43db9984686317089fefcf2e38de527e4cb44487 ' "not-another-file && chown centos not-another-file'" ), get_pty=False) fake_sftp_client.from_transport().putfo.assert_called_with( fake_open(), '/tmp/pyinfra-43db9984686317089fefcf2e38de527e4cb44487', )
def test_run_shell_command(self): inventory = make_inventory(hosts=('@docker/not-an-image', )) state = State(inventory, Config()) command = 'echo hi' self.fake_popen_mock().returncode = 0 host = inventory.get_host('@docker/not-an-image') host.connect(state) out = host.run_shell_command( state, command, stdin='hello', get_pty=True, print_output=True, ) assert len(out) == 3 assert out[0] is True command, _ = make_unix_command(command) command = shlex_quote(command) docker_command = 'docker exec -it containerid sh -c {0}'.format( command) shell_command, _ = make_unix_command(docker_command) self.fake_popen_mock.assert_called_with( shell_command, shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE, )
def test_run_shell_command(self): inventory = make_inventory(hosts=('@chroot/not-a-chroot',)) state = State(inventory, Config()) host = inventory.get_host('@chroot/not-a-chroot') host.connect(state) command = 'echo hoi' self.fake_popen_mock().returncode = 0 out = host.run_shell_command( state, command, stdin='hello', get_pty=True, print_output=True, ) assert len(out) == 3 assert out[0] is True command = make_unix_command(command).get_raw_value() command = shlex_quote(command) docker_command = 'chroot /not-a-chroot sh -c {0}'.format(command) shell_command = make_unix_command(docker_command).get_raw_value() self.fake_popen_mock.assert_called_with( shell_command, shell=True, stdout=PIPE, stderr=PIPE, stdin=PIPE, )
def test_connect_disconnect_host(self): inventory = make_inventory(hosts=('@docker/not-an-image', )) state = State(inventory, Config()) host = inventory.get_host('@docker/not-an-image') host.connect(state, for_fact=True) assert len(state.active_hosts) == 0 host.disconnect(state)
def test_get_file_su_user(self, fake_sftp_client, fake_ssh_client): inventory = make_inventory(hosts=('somehost',)) state = State(inventory, Config()) host = inventory.get_host('somehost') host.connect(state) stdout_mock = MagicMock() stdout_mock.channel.recv_exit_status.return_value = 0 fake_ssh_client().exec_command.return_value = MagicMock(), stdout_mock, MagicMock() fake_open = mock_open(read_data='test!') with patch('pyinfra.api.util.open', fake_open, create=True): status = host.get_file( state, 'not-a-file', 'not-another-file', print_output=True, su_user='******', ) assert status is True fake_ssh_client().exec_command.assert_has_calls([ call(( "su centos -s `which sh` -c 'cp not-a-file " "/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r not-a-file'" ), get_pty=False), call(( "su centos -s `which sh` -c 'rm -f " "/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508'" ), get_pty=False), ]) fake_sftp_client.from_transport().getfo.assert_called_with( '/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508', fake_open(), )
def test_get_file_sudo_copy_fail(self, fake_ssh_client): inventory = make_inventory(hosts=('somehost',)) state = State(inventory, Config()) host = inventory.get_host('somehost') host.connect(state) stdout_mock = MagicMock() stdout_mock.channel.recv_exit_status.return_value = 1 fake_ssh_client().exec_command.return_value = MagicMock(), stdout_mock, MagicMock() status = host.get_file( state, 'not-a-file', 'not-another-file', print_output=True, sudo=True, sudo_user='******', ) assert status is False fake_ssh_client().exec_command.assert_has_calls([ call(( "sudo -H -n -u ubuntu sh -c 'cp not-a-file " "/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r not-a-file'" ), get_pty=False), ])
def test_cli_op_line_numbers(self): inventory = make_inventory() state = State(inventory, Config()) connect_all(state) state.current_deploy_filename = __file__ pyinfra.is_cli = True pseudo_state.set(state) # Add op to both hosts for name in ('anotherhost', 'somehost'): pseudo_host.set(inventory.get_host(name)) server.shell('echo hi') # note this is called twice but on *the same line* # Add op to just the second host - using the pseudo modules such that # it replicates a deploy file. pseudo_host.set(inventory.get_host('anotherhost')) first_pseudo_hash = server.user('anotherhost_user').hash first_pseudo_call_line = getframeinfo(currentframe()).lineno - 1 # Add op to just the first host - using the pseudo modules such that # it replicates a deploy file. pseudo_host.set(inventory.get_host('somehost')) second_pseudo_hash = server.user('somehost_user').hash second_pseudo_call_line = getframeinfo(currentframe()).lineno - 1 pseudo_state.reset() pseudo_host.reset() pyinfra.is_cli = False # Ensure there are two ops op_order = state.get_op_order() assert len(op_order) == 3 # And that the two ops above were called in the expected order assert op_order[1] == first_pseudo_hash assert op_order[2] == second_pseudo_hash # And that they have the expected line numbers assert state.op_line_numbers_to_hash.get((first_pseudo_call_line,)) == first_pseudo_hash assert state.op_line_numbers_to_hash.get((second_pseudo_call_line,)) == second_pseudo_hash # Ensure somehost has two ops and anotherhost only has the one assert len(state.ops[inventory.get_host('somehost')]) == 2 assert len(state.ops[inventory.get_host('anotherhost')]) == 2
def test_user_provided_container_id(self): inventory = make_inventory(hosts=( ('@docker/not-an-image', {'docker_container_id': 'abc'}), )) state = State(inventory, Config()) host = inventory.get_host('@docker/not-an-image') host.connect(state) assert host.data.docker_container_id == 'abc'
def test_op_line_numbers(self): inventory = make_inventory() state = State(inventory, Config()) connect_all(state) # Add op to both hosts add_op(state, server.shell, 'echo "hi"') # Add op to just the second host - using the pseudo modules such that # it replicates a deploy file. pseudo_state.set(state) pseudo_host.set(inventory['anotherhost']) first_pseudo_hash = server.user('anotherhost_user').hash first_pseudo_call_line = getframeinfo(currentframe()).lineno - 1 # Add op to just the first host - using the pseudo modules such that # it replicates a deploy file. pseudo_state.set(state) pseudo_host.set(inventory['somehost']) second_pseudo_hash = server.user('somehost_user').hash second_pseudo_call_line = getframeinfo(currentframe()).lineno - 1 pseudo_state.reset() pseudo_host.reset() # Ensure there are two ops op_order = state.get_op_order() self.assertEqual(len(op_order), 3) # And that the two ops above were called in the expected order self.assertEqual(op_order[1], first_pseudo_hash) self.assertEqual(op_order[2], second_pseudo_hash) # And that they have the expected line numbers self.assertEqual( state.op_line_numbers_to_hash.get((0, first_pseudo_call_line)), first_pseudo_hash, ) self.assertEqual( state.op_line_numbers_to_hash.get((0, second_pseudo_call_line)), second_pseudo_hash, ) # Ensure somehost has two ops and anotherhost only has the one self.assertEqual(len(state.ops[inventory.get_host('somehost')]), 2) self.assertEqual(len(state.ops[inventory.get_host('anotherhost')]), 2)
def test_user_provided_container_id(self): inventory = make_inventory( hosts=(("@dockerssh/somehost:not-an-image", {"docker_container_id": "abc"}),), ) State(inventory, Config()) host = inventory.get_host("@dockerssh/somehost:not-an-image") host.connect() assert host.data.docker_container_id == "abc"
def test_connect_with_rsa_ssh_key(self): state = State( make_inventory(hosts=(("somehost", { "ssh_key": "testkey" }), )), Config()) with patch("pyinfra.connectors.ssh.path.isfile", lambda *args, **kwargs: True), patch( "pyinfra.connectors.ssh.RSAKey.from_private_key_file", ) as fake_key_open: fake_key = MagicMock() fake_key_open.return_value = fake_key connect_all(state) # Check the key was created properly fake_key_open.assert_called_with(filename="testkey") # Check the certificate file was then loaded fake_key.load_certificate.assert_called_with("testkey.pub") # And check the Paramiko SSH call was correct self.fake_connect_mock.assert_called_with( "somehost", allow_agent=False, look_for_keys=False, pkey=fake_key, timeout=10, username="******", _pyinfra_ssh_forward_agent=None, _pyinfra_ssh_config_file=None, _pyinfra_ssh_known_hosts_file=None, _pyinfra_ssh_strict_host_key_checking=None, _pyinfra_ssh_paramiko_connect_kwargs=None, ) # Check that loading the same key again is cached in the state second_state = State( make_inventory(hosts=(("somehost", { "ssh_key": "testkey" }), )), Config(), ) second_state.private_keys = state.private_keys connect_all(second_state)
def test_op_cannot_change_execution_kwargs(self): inventory = make_inventory() state = State(inventory, Config()) class NoSetDefaultDict(defaultdict): def setdefault(self, key, _): return self[key] state.op_meta = NoSetDefaultDict(lambda: {'serial': True}) connect_all(state) with self.assertRaises(OperationValueError) as context: add_op(state, files.file, '/var/log/pyinfra.log', serial=False) assert context.exception.args[0] == 'Cannot have different values for `serial`.'
def test_get_from_config(self): config = Config(SUDO="config-value") inventory = Inventory((("somehost", ), {})) state = State(config=config, inventory=inventory) kwargs, keys = pop_global_arguments( {}, state=state, host=inventory.get_host("somehost")) assert kwargs["sudo"] == "config-value"