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_connect_with_rsa_ssh_key_missing_password(self): state = State( make_inventory(hosts=(('somehost', { 'ssh_key': 'testkey' }), )), Config()) with patch( 'pyinfra.api.connectors.ssh.path.isfile', lambda *args, **kwargs: True, ), patch('pyinfra.api.connectors.ssh.RSAKey.from_private_key_file', ) as fake_key_open: def fake_key_open_fail(*args, **kwargs): raise PasswordRequiredException fake_key_open.side_effect = fake_key_open_fail fake_key = MagicMock() fake_key_open.return_value = fake_key state.deploy_dir = '/' with self.assertRaises(PyinfraError) as e: connect_all(state) assert e.exception.args[0] == ( 'Private key file (testkey) is encrypted, set ssh_key_password ' 'to use this key')
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(inventory, Config()) host = inventory.get_host('somehost') host.connect() command = 'echo Šablony' fake_stdout.channel.recv_exit_status.return_value = 0 out = host.run_shell_command(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( 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_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): 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_get_file_error(self, fake_get_file): fake_ssh_docker_shell.custom_command = [ 'docker cp containerid:not-a-file remote_tempfile', False, [], ['docker error'] ] inventory = make_inventory( hosts=('@dockerssh/somehost:not-an-image', )) state = State(inventory, Config()) state.get_temp_filename = lambda _: 'remote_tempfile' host = inventory.get_host('@dockerssh/somehost:not-an-image') host.connect() fake_get_file.return_value = True with self.assertRaises(IOError) as ex: host.get_file('not-a-file', 'not-another-file', print_output=True) assert str(ex.exception) == 'docker error' # SSH error fake_ssh_docker_shell.custom_command = [ 'docker cp containerid:not-a-file remote_tempfile', True, [], [] ] fake_get_file.return_value = False with self.assertRaises(IOError) as ex: host.get_file('not-a-file', 'not-another-file', print_output=True) assert str(ex.exception) == 'failed to copy file over ssh'
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_with_ssh_key(self): state = State(make_inventory(hosts=( ('somehost', {'ssh_key': 'testkey'}), )), Config()) with patch('pyinfra.api.connect.path.isfile', lambda *args, **kwargs: True), \ patch('pyinfra.api.connect.RSAKey.from_private_key_file') as fake_key_open: fake_key = FakeRSAKey() fake_key_open.return_value = fake_key state.deploy_dir = '/' connect_all(state) # Check the key was created properly fake_key_open.assert_called_with(filename='testkey') # 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, port=22, timeout=10, username='******', )
def test_op_state_hosts_limit(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 first host with state.limit('test_group'): add_op( state, server.user, 'somehost_user', ) # Now, also limited but set hosts to the non-limited hosts, which # should mean this operation applies to no hosts. add_op( state, server.user, 'somehost_user', hosts=inventory['anotherhost'], ) # Ensure there are three ops self.assertEqual(len(state.op_order), 3) # Ensure somehost has two ops and anotherhost only has the one self.assertEqual(len(state.ops['somehost']), 2) self.assertEqual(len(state.ops['anotherhost']), 1)
def build_arguments_doc(): pyinfra_config = Config() this_dir = path.dirname(path.realpath(__file__)) docs_dir = path.abspath(path.join(this_dir, "..", "docs")) lines = [] for category, kwarg_configs in OPERATION_KWARGS.items(): if category is None: continue lines.append("{0}".format(category)) lines.append("".join("~" for _ in range(len(category)))) lines.append("") for key, config in kwarg_configs.items(): description = config if isinstance(config, dict): description = config.get("description") default = config.get("default") if callable(default): default = default(pyinfra_config) if default is not None: key = "{0}={1}".format(key, default) lines.append("+ ``{0}``: {1}".format(key, description)) module_filename = path.join(docs_dir, "_deploy_globals.rst") print("--> Writing {0}".format(module_filename)) out = "\n".join(lines) with open(module_filename, "w") as outfile: outfile.write(out)
def test_get_file_su_user(self, fake_sftp_client, fake_ssh_client): inventory = make_inventory(hosts=('somehost',)) State(inventory, Config()) host = inventory.get_host('somehost') host.connect() 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( '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 -c 'sh -c '\"'\"'cp not-a-file " '/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r ' "not-a-file'\"'\"''" ), get_pty=False), call(( "su centos -c '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_nested_op_api(self): inventory = make_inventory() state = State(inventory, Config()) connect_all(state) somehost = inventory.get_host("somehost") ctx_state.set(state) ctx_host.set(somehost) pyinfra.is_cli = True try: outer_result = server.shell(commands="echo outer") assert outer_result.combined_output_lines is None def callback(): inner_result = server.shell(commands="echo inner") assert inner_result.combined_output_lines is not None python.call(function=callback) assert len(state.get_op_order()) == 2 run_ops(state) assert len(state.get_op_order()) == 3 assert state.results[somehost]["success_ops"] == 3 assert outer_result.combined_output_lines is not None disconnect_all(state) finally: pyinfra.is_cli = False
def test_rsync_op(self): inventory = make_inventory(hosts=("somehost", )) state = State(inventory, Config()) connect_all(state) with patch("pyinfra.connectors.ssh.check_can_rsync"): add_op(state, files.rsync, "src", "dest", sudo=True, sudo_user="******") assert len(state.get_op_order()) == 1 with patch("pyinfra.connectors.ssh.run_local_process" ) as fake_run_local_process: fake_run_local_process.return_value = 0, [] run_ops(state) fake_run_local_process.assert_called_with( ("rsync -ax --delete --rsh " "'ssh -o BatchMode=yes '" " --rsync-path 'sudo -u root rsync' src vagrant@somehost:dest"), print_output=False, print_prefix=inventory.get_host("somehost").print_prefix, )
def test_run_shell_command(self): inventory = make_inventory(hosts=('@chroot/not-a-chroot',)) State(inventory, Config()) host = inventory.get_host('@chroot/not-a-chroot') host.connect() command = 'echo hoi' self.fake_popen_mock().returncode = 0 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 = '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_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_pseudo_op(self): inventory = make_inventory() state = State(inventory, Config()) connect_all(state) pseudo_state.set(state) pseudo_host.set(inventory['somehost']) # Exceute the op "bare" server.shell('echo "hi"') # Ensure this is ignored state.active = False server.shell('echo "hi 2"') # We should now have one op self.assertEqual(len(state.op_order), 1) # Ensure only somehost has actual op self.assertEqual(len(state.ops['somehost']), 1) self.assertEqual(len(state.ops['anotherhost']), 0) # Check we can't call it inside another op state.active = True state.in_op = True with self.assertRaises(PyinfraError): server.shell('echo "hi 3"') pseudo_state.reset() pseudo_host.reset()
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 build_global_kwargs_doc(): pyinfra_config = Config() this_dir = path.dirname(path.realpath(__file__)) docs_dir = path.abspath(path.join(this_dir, '..', 'docs')) lines = [] for category, kwarg_configs in OPERATION_KWARGS.items(): if category is None: continue lines.append('{0}:'.format(category)) for key, config in kwarg_configs.items(): description = config if isinstance(config, dict): description = config.get('description') default = config.get('default') if callable(default): default = default(pyinfra_config) if default is not None: key = '{0}={1}'.format(key, default) lines.append(' + ``{0}``: {1}'.format(key, description)) module_filename = path.join(docs_dir, '_deploy_globals.rst') print('--> Writing {0}'.format(module_filename)) out = '\n'.join(lines) with open(module_filename, 'w') as outfile: outfile.write(out)
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_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_connect_with_rsa_ssh_key_password(self): state = State( make_inventory(hosts=(('somehost', { 'ssh_key': 'testkey', 'ssh_key_password': '******' }), )), Config()) with patch( 'pyinfra.api.connectors.ssh.path.isfile', lambda *args, **kwargs: True, ), patch('pyinfra.api.connectors.ssh.RSAKey.from_private_key_file', ) as fake_key_open: def fake_key_open_fail(*args, **kwargs): if 'password' not in kwargs: raise PasswordRequiredException() fake_key_open.side_effect = fake_key_open_fail fake_key = FakeRSAKey() fake_key_open.return_value = fake_key state.deploy_dir = '/' connect_all(state) # Check the key was created properly fake_key_open.assert_called_with(filename='testkey', password='******')
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_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_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_connect_with_rsa_ssh_key_password_from_prompt(self): state = State( make_inventory(hosts=(('somehost', { 'ssh_key': 'testkey' }), )), Config()) with patch( 'pyinfra.api.connectors.ssh.path.isfile', lambda *args, **kwargs: True, ), patch( 'pyinfra.api.connectors.ssh.getpass', lambda *args, **kwargs: 'testpass', ), patch('pyinfra.api.connectors.ssh.RSAKey.from_private_key_file', ) as fake_key_open: fake_key = MagicMock() def fake_key_open_fail(*args, **kwargs): if 'password' not in kwargs: raise PasswordRequiredException() return fake_key fake_key_open.side_effect = fake_key_open_fail state.deploy_dir = '/' pyinfra.is_cli = True connect_all(state) pyinfra.is_cli = False # Check the key was created properly fake_key_open.assert_called_with(filename='testkey', password='******') # Check the certificate file was then loaded fake_key.load_certificate.assert_called_with('testkey.pub')
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_connect_with_rsa_ssh_key_wrong_password(self): state = State( make_inventory(hosts=(('somehost', { 'ssh_key': 'testkey', 'ssh_key_password': '******' }), )), Config()) with patch( 'pyinfra.api.connectors.ssh.path.isfile', lambda *args, **kwargs: True, ), patch('pyinfra.api.connectors.ssh.RSAKey.from_private_key_file', ) as fake_key_open: def fake_key_open_fail(*args, **kwargs): if 'password' not in kwargs: raise PasswordRequiredException raise SSHException fake_key_open.side_effect = fake_key_open_fail fake_key = MagicMock() fake_key_open.return_value = fake_key state.deploy_dir = '/' with self.assertRaises(PyinfraError) as e: connect_all(state) assert e.exception.args[ 0] == 'Incorrect password for private key: testkey'
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_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(inventory, Config()) host = inventory.get_host('somehost') host.connect() command = StringCommand('echo', MaskString('top-secret-stuff')) fake_stdout.channel.recv_exit_status.return_value = 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 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), err=True, )
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, )