コード例 #1
0
    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
コード例 #2
0
    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.hosts('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.get_host('anotherhost'),
            )

        # Ensure there are three ops
        assert len(state.get_op_order()) == 3

        # 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')]) == 1
コード例 #3
0
    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,
        )
コード例 #4
0
    def test_rsync_op(self):
        inventory = make_inventory(hosts=('somehost', ))
        state = State(inventory, Config())
        connect_all(state)

        with patch('pyinfra.api.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.api.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 -o StrictHostKeyChecking=no '"
             " --rsync-path 'sudo -u root rsync' src vagrant@somehost:dest"),
            print_output=False,
            print_prefix=inventory.get_host('somehost').print_prefix,
        )
コード例 #5
0
    def test_nested_deploy(self):
        inventory = make_inventory()
        somehost = inventory.get_host("somehost")

        state = State(inventory, Config())

        # Enable printing on this test to catch any exceptions in the formatting
        state.print_output = True
        state.print_input = True
        state.print_fact_info = True
        state.print_noop_info = True

        connect_all(state)

        @deploy
        def test_nested_deploy():
            server.shell(commands=["echo nested command"])

        @deploy
        def test_deploy():
            server.shell(commands=["echo first command"])
            test_nested_deploy()
            server.shell(commands=["echo second command"])

        add_deploy(state, test_deploy)

        op_order = state.get_op_order()

        # Ensure we have an op
        assert len(op_order) == 3

        first_op_hash = op_order[0]
        assert state.op_meta[first_op_hash]["names"] == {
            "test_deploy | Server/Shell"
        }
        assert state.ops[somehost][first_op_hash]["commands"] == [
            StringCommand("echo first command"),
        ]

        second_op_hash = op_order[1]
        assert state.op_meta[second_op_hash]["names"] == {
            "test_deploy | test_nested_deploy | Server/Shell",
        }
        assert state.ops[somehost][second_op_hash]["commands"] == [
            StringCommand("echo nested command"),
        ]

        third_op_hash = op_order[2]
        assert state.op_meta[third_op_hash]["names"] == {
            "test_deploy | Server/Shell"
        }
        assert state.ops[somehost][third_op_hash]["commands"] == [
            StringCommand("echo second command"),
        ]
コード例 #6
0
    def test_cli_op_line_numbers(self):
        inventory = make_inventory()
        state = State(inventory, Config())
        connect_all(state)

        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(
            (0, first_pseudo_call_line)) == first_pseudo_hash
        assert 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
        assert len(state.ops[inventory.get_host('somehost')]) == 2
        assert len(state.ops[inventory.get_host('anotherhost')]) == 2
コード例 #7
0
    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)
コード例 #8
0
    def test_api_op_line_numbers(self):
        inventory = make_inventory()
        state = State(inventory, Config())
        connect_all(state)

        another_host = inventory.get_host('anotherhost')

        def add_another_op():
            return add_op(state, server.shell, 'echo second-op')[another_host].hash

        first_op_hash = add_op(state, server.shell, 'echo first-op')[another_host].hash
        second_op_hash = add_another_op()  # note `add_op` will be called on an earlier line

        op_order = state.get_op_order()
        assert len(op_order) == 2

        assert op_order[0] == first_op_hash
        assert op_order[1] == second_op_hash
コード例 #9
0
    def _do_test_deploy(self):
        correct_op_name_and_host_names = [
            ('First main operation', True),  # true for all hosts
            ('Second main operation', ('somehost', )),
            ('tests/test_deploy/a_task.py | First task operation',
             ('anotherhost', )),
            ('tests/test_deploy/a_task.py | Second task operation',
             ('anotherhost', )),
            ('tests/test_deploy/a_task.py | First task operation', True),
            ('tests/test_deploy/a_task.py | Second task operation', True),
            ('Loop-0 main operation', True),
            ('Loop-1 main operation', True),
            ('Third main operation', True),
        ]

        hosts = ['somehost', 'anotherhost', 'someotherhost']
        shuffle(hosts)

        inventory = make_inventory(hosts=hosts)
        state = State(inventory, Config())
        state.deploy_dir = path.join('tests', 'test_deploy')

        connect_all(state)
        pseudo_state.set(state)

        pyinfra.is_cli = True
        load_deploy_file(state, path.join(state.deploy_dir, 'deploy.py'))
        pyinfra.is_cli = False

        op_order = state.get_op_order()

        for i, (correct_op_name, correct_host_names) in enumerate(
                correct_op_name_and_host_names, ):
            op_hash = op_order[i]
            op_meta = state.op_meta[op_hash]

            self.assertEqual(list(op_meta['names'])[0], correct_op_name)

            for host in inventory:
                op_hashes = state.meta[host]['op_hashes']
                if correct_host_names is True or host.name in correct_host_names:
                    self.assertIn(op_hash, op_hashes)
                else:
                    self.assertNotIn(op_hash, op_hashes)
コード例 #10
0
    def test_file_download_op(self):
        inventory = make_inventory()

        state = State(inventory, Config())
        connect_all(state)

        with patch("pyinfra.operations.files.os.path.isfile",
                   lambda *args, **kwargs: True):
            add_op(
                state,
                files.get,
                name="First op name",
                src="/home/vagrant/file.txt",
                dest="files/file.txt",
            )

        op_order = state.get_op_order()

        assert len(op_order) == 1

        first_op_hash = op_order[0]
        assert state.op_meta[first_op_hash]["names"] == {"First op name"}

        somehost = inventory.get_host("somehost")
        anotherhost = inventory.get_host("anotherhost")

        # Ensure first op has the right (upload) command
        assert state.ops[somehost][first_op_hash]["commands"] == [
            FileDownloadCommand("/home/vagrant/file.txt", "files/file.txt"),
        ]

        with patch("pyinfra.api.util.open",
                   mock_open(read_data="test!"),
                   create=True):
            run_ops(state)

        assert state.results[somehost]["success_ops"] == 1
        assert state.results[somehost]["ops"] == 1
        assert state.results[anotherhost]["success_ops"] == 1
        assert state.results[anotherhost]["ops"] == 1
        assert state.results[somehost]["error_ops"] == 0
        assert state.results[anotherhost]["error_ops"] == 0
コード例 #11
0
    def test_file_download_op(self):
        inventory = make_inventory()

        state = State(inventory, Config())
        connect_all(state)

        with patch('pyinfra.operations.files.os_path.isfile',
                   lambda *args, **kwargs: True):
            add_op(
                state,
                files.get,
                name='First op name',
                src='/home/vagrant/file.txt',
                dest='files/file.txt',
            )

        op_order = state.get_op_order()

        assert len(op_order) == 1

        first_op_hash = op_order[0]
        assert state.op_meta[first_op_hash]['names'] == {'First op name'}

        somehost = inventory.get_host('somehost')
        anotherhost = inventory.get_host('anotherhost')

        # Ensure first op has the right (upload) command
        assert state.ops[somehost][first_op_hash]['commands'] == [
            FileDownloadCommand('/home/vagrant/file.txt', 'files/file.txt'),
        ]

        with patch('pyinfra.api.util.open',
                   mock_open(read_data='test!'),
                   create=True):
            run_ops(state)

        assert state.results[somehost]['success_ops'] == 1
        assert state.results[somehost]['ops'] == 1
        assert state.results[anotherhost]['success_ops'] == 1
        assert state.results[anotherhost]['ops'] == 1
        assert state.results[somehost]['error_ops'] == 0
        assert state.results[anotherhost]['error_ops'] == 0
コード例 #12
0
    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
        ctx_state.set(state)

        # Add op to both hosts
        for name in ("anotherhost", "somehost"):
            ctx_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 context modules such that
        # it replicates a deploy file.
        ctx_host.set(inventory.get_host("anotherhost"))
        first_context_hash = server.user("anotherhost_user").hash

        # Add op to just the first host - using the context modules such that
        # it replicates a deploy file.
        ctx_host.set(inventory.get_host("somehost"))
        second_context_hash = server.user("somehost_user").hash

        ctx_state.reset()
        ctx_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_context_hash
        assert op_order[2] == second_context_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
コード例 #13
0
    def test_function_call_op(self):
        inventory = make_inventory()
        state = State(inventory, Config())
        connect_all(state)

        is_called = []

        def mocked_function(*args, **kwargs):
            is_called.append(True)
            return None

        # Add op to both hosts
        add_op(state, python.call, mocked_function)

        # Ensure there is one op
        assert len(state.get_op_order()) == 1

        run_ops(state)

        assert is_called
コード例 #14
0
    def test_op_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
        add_op(
            state, server.user,
            'somehost_user',
            hosts=inventory['somehost'],
        )

        # Ensure there are two ops
        assert len(state.get_op_order()) == 2

        # 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')]) == 1
コード例 #15
0
    def test_run_once_serial_op(self):
        inventory = make_inventory()
        state = State(inventory, Config())
        connect_all(state)

        # Add a run once op
        add_op(state, server.shell, 'echo "hi"', run_once=True, serial=True)

        # Ensure it's added to op_order
        assert len(state.get_op_order()) == 1

        somehost = inventory.get_host('somehost')
        anotherhost = inventory.get_host('anotherhost')

        # Ensure between the two hosts we only run the one op
        assert len(state.ops[somehost]) + len(state.ops[anotherhost]) == 1

        # Check run works
        run_ops(state)

        assert (state.results[somehost]['success_ops'] +
                state.results[anotherhost]['success_ops']) == 1
コード例 #16
0
    def test_op(self):
        inventory = make_inventory()
        somehost = inventory.get_host("somehost")
        anotherhost = inventory.get_host("anotherhost")

        state = State(inventory, Config())
        state.add_callback_handler(BaseStateCallback())

        # Enable printing on this test to catch any exceptions in the formatting
        state.print_output = True
        state.print_input = True
        state.print_fact_info = True
        state.print_noop_info = True

        connect_all(state)

        add_op(
            state,
            files.file,
            "/var/log/pyinfra.log",
            user="******",
            group="pyinfra",
            mode="644",
            create_remote_dir=False,
            sudo=True,
            sudo_user="******",
            su_user="******",
            ignore_errors=True,
            env={
                "TEST": "what",
            },
        )

        op_order = state.get_op_order()

        # Ensure we have an op
        assert len(op_order) == 1

        first_op_hash = op_order[0]

        # Ensure the op name
        assert state.op_meta[first_op_hash]["names"] == {"Files/File"}

        # Ensure the commands
        assert state.ops[somehost][first_op_hash]["commands"] == [
            StringCommand("touch /var/log/pyinfra.log"),
            StringCommand("chmod 644 /var/log/pyinfra.log"),
            StringCommand("chown pyinfra:pyinfra /var/log/pyinfra.log"),
        ]

        # Ensure the global kwargs (same for both hosts)
        somehost_global_kwargs = state.ops[somehost][first_op_hash][
            "global_kwargs"]
        assert somehost_global_kwargs["sudo"] is True
        assert somehost_global_kwargs["sudo_user"] == "test_sudo"
        assert somehost_global_kwargs["su_user"] == "test_su"
        assert somehost_global_kwargs["ignore_errors"] is True

        anotherhost_global_kwargs = state.ops[anotherhost][first_op_hash][
            "global_kwargs"]
        assert anotherhost_global_kwargs["sudo"] is True
        assert anotherhost_global_kwargs["sudo_user"] == "test_sudo"
        assert anotherhost_global_kwargs["su_user"] == "test_su"
        assert anotherhost_global_kwargs["ignore_errors"] is True

        # Ensure run ops works
        run_ops(state)

        # Ensure ops completed OK
        assert state.results[somehost]["success_ops"] == 1
        assert state.results[somehost]["ops"] == 1
        assert state.results[anotherhost]["success_ops"] == 1
        assert state.results[anotherhost]["ops"] == 1

        # And w/o errors
        assert state.results[somehost]["error_ops"] == 0
        assert state.results[anotherhost]["error_ops"] == 0

        # And with the different modes
        run_ops(state, serial=True)
        run_ops(state, no_wait=True)

        disconnect_all(state)
コード例 #17
0
    def test_op(self):
        inventory = make_inventory()
        somehost = inventory.get_host('somehost')
        anotherhost = inventory.get_host('anotherhost')

        state = State(inventory, Config())

        # Enable printing on this test to catch any exceptions in the formatting
        state.print_output = True
        state.print_fact_info = True
        state.print_fact_output = True

        connect_all(state)

        add_op(
            state, files.file,
            '/var/log/pyinfra.log',
            user='******',
            group='pyinfra',
            mode='644',
            sudo=True,
            sudo_user='******',
            su_user='******',
            ignore_errors=True,
            env={
                'TEST': 'what',
            },
        )

        op_order = state.get_op_order()

        # Ensure we have an op
        assert len(op_order) == 1

        first_op_hash = op_order[0]

        # Ensure the op name
        assert state.op_meta[first_op_hash]['names'] == {'Files/File'}

        # Ensure the commands
        assert state.ops[somehost][first_op_hash]['commands'] == [
            'touch /var/log/pyinfra.log',
            'chmod 644 /var/log/pyinfra.log',
            'chown pyinfra:pyinfra /var/log/pyinfra.log',
        ]

        # Ensure the meta
        meta = state.op_meta[first_op_hash]
        assert meta['sudo'] is True
        assert meta['sudo_user'] == 'test_sudo'
        assert meta['su_user'] == 'test_su'
        assert meta['ignore_errors'] is True

        # Ensure run ops works
        run_ops(state)

        # Ensure ops completed OK
        assert state.results[somehost]['success_ops'] == 1
        assert state.results[somehost]['ops'] == 1
        assert state.results[anotherhost]['success_ops'] == 1
        assert state.results[anotherhost]['ops'] == 1

        # And w/o errors
        assert state.results[somehost]['error_ops'] == 0
        assert state.results[anotherhost]['error_ops'] == 0

        # And with the different modes
        run_ops(state, serial=True)
        run_ops(state, no_wait=True)
コード例 #18
0
    def test_deploy(self):
        inventory = make_inventory()
        somehost = inventory.get_host("somehost")
        anotherhost = inventory.get_host("anotherhost")

        state = State(inventory, Config())

        # Enable printing on this test to catch any exceptions in the formatting
        state.print_output = True
        state.print_input = True
        state.print_fact_info = True
        state.print_noop_info = True

        connect_all(state)

        @deploy
        def test_deploy(state=None, host=None):
            server.shell(commands=["echo first command"])
            server.shell(commands=["echo second command"])

        add_deploy(state, test_deploy)

        op_order = state.get_op_order()

        # Ensure we have an op
        assert len(op_order) == 2

        first_op_hash = op_order[0]
        assert state.op_meta[first_op_hash]["names"] == {
            "test_deploy | Server/Shell"
        }
        assert state.ops[somehost][first_op_hash]["commands"] == [
            StringCommand("echo first command"),
        ]
        assert state.ops[anotherhost][first_op_hash]["commands"] == [
            StringCommand("echo first command"),
        ]

        second_op_hash = op_order[1]
        assert state.op_meta[second_op_hash]["names"] == {
            "test_deploy | Server/Shell"
        }
        assert state.ops[somehost][second_op_hash]["commands"] == [
            StringCommand("echo second command"),
        ]
        assert state.ops[anotherhost][second_op_hash]["commands"] == [
            StringCommand("echo second command"),
        ]

        # Ensure run ops works
        run_ops(state)

        # Ensure ops completed OK
        assert state.results[somehost]["success_ops"] == 2
        assert state.results[somehost]["ops"] == 2
        assert state.results[anotherhost]["success_ops"] == 2
        assert state.results[anotherhost]["ops"] == 2

        # And w/o errors
        assert state.results[somehost]["error_ops"] == 0
        assert state.results[anotherhost]["error_ops"] == 0

        # And with the different modes
        run_ops(state, serial=True)
        run_ops(state, no_wait=True)

        disconnect_all(state)
コード例 #19
0
    def test_file_upload_op(self):
        inventory = make_inventory()

        state = State(inventory, Config())
        connect_all(state)

        with patch('pyinfra.operations.files.path.isfile', lambda *args, **kwargs: True):
            # Test normal
            add_op(
                state, files.put,
                {'First op name'},
                'files/file.txt',
                '/home/vagrant/file.txt',
            )

            # And with sudo
            add_op(
                state, files.put,
                'files/file.txt',
                '/home/vagrant/file.txt',
                sudo=True,
                sudo_user='******',
            )

            # And with su
            add_op(
                state, files.put,
                'files/file.txt',
                '/home/vagrant/file.txt',
                sudo=True,
                su_user='******',
            )

        op_order = state.get_op_order()

        # Ensure we have all ops
        assert len(op_order) == 3

        first_op_hash = op_order[0]

        # Ensure first op is the right one
        assert state.op_meta[first_op_hash]['names'] == {'First op name'}

        somehost = inventory.get_host('somehost')
        anotherhost = inventory.get_host('anotherhost')

        # Ensure first op has the right (upload) command
        assert state.ops[somehost][first_op_hash]['commands'] == [
            ('upload', 'files/file.txt', '/home/vagrant/file.txt'),
        ]

        # Ensure second op has sudo/sudo_user
        assert state.op_meta[op_order[1]]['sudo'] is True
        assert state.op_meta[op_order[1]]['sudo_user'] == 'pyinfra'

        # Ensure third has su_user
        assert state.op_meta[op_order[2]]['su_user'] == 'pyinfra'

        # Check run ops works
        with patch('pyinfra.api.util.open', mock_open(read_data='test!'), create=True):
            run_ops(state)

        # Ensure ops completed OK
        assert state.results[somehost]['success_ops'] == 3
        assert state.results[somehost]['ops'] == 3
        assert state.results[anotherhost]['success_ops'] == 3
        assert state.results[anotherhost]['ops'] == 3

        # And w/o errors
        assert state.results[somehost]['error_ops'] == 0
        assert state.results[anotherhost]['error_ops'] == 0
コード例 #20
0
    def test_file_upload_op(self):
        inventory = make_inventory()

        state = State(inventory, Config())
        connect_all(state)

        # Test normal
        add_op(
            state,
            files.put,
            name="First op name",
            src="files/file.txt",
            dest="/home/vagrant/file.txt",
        )

        # And with sudo
        add_op(
            state,
            files.put,
            src="files/file.txt",
            dest="/home/vagrant/file.txt",
            sudo=True,
            sudo_user="******",
        )

        # And with su
        add_op(
            state,
            files.put,
            src="files/file.txt",
            dest="/home/vagrant/file.txt",
            sudo=True,
            su_user="******",
        )

        op_order = state.get_op_order()

        # Ensure we have all ops
        assert len(op_order) == 3

        first_op_hash = op_order[0]
        second_op_hash = op_order[1]

        # Ensure first op is the right one
        assert state.op_meta[first_op_hash]["names"] == {"First op name"}

        somehost = inventory.get_host("somehost")
        anotherhost = inventory.get_host("anotherhost")

        # Ensure first op has the right (upload) command
        assert state.ops[somehost][first_op_hash]["commands"] == [
            StringCommand("mkdir -p /home/vagrant"),
            FileUploadCommand("files/file.txt", "/home/vagrant/file.txt"),
        ]

        # Ensure second op has sudo/sudo_user
        assert state.ops[somehost][second_op_hash]["global_kwargs"][
            "sudo"] is True
        assert state.ops[somehost][second_op_hash]["global_kwargs"][
            "sudo_user"] == "pyinfra"

        # Ensure third has su_user
        assert state.ops[somehost][
            op_order[2]]["global_kwargs"]["su_user"] == "pyinfra"

        # Check run ops works
        run_ops(state)

        # Ensure ops completed OK
        assert state.results[somehost]["success_ops"] == 3
        assert state.results[somehost]["ops"] == 3
        assert state.results[anotherhost]["success_ops"] == 3
        assert state.results[anotherhost]["ops"] == 3

        # And w/o errors
        assert state.results[somehost]["error_ops"] == 0
        assert state.results[anotherhost]["error_ops"] == 0