diff -Nru python-vagrant-0.5.14/CHANGELOG.md python-vagrant-0.5.15/CHANGELOG.md --- python-vagrant-0.5.14/CHANGELOG.md 2016-06-17 11:31:01.000000000 +0000 +++ python-vagrant-0.5.15/CHANGELOG.md 2017-05-08 14:35:53.000000000 +0000 @@ -4,6 +4,17 @@ This document lists the changes (and individuals who contributed to those changes) for each release of python-vagrant. +## 0.5.15 + +- Pull Request #54: Create ssh() method to run shell commands in a VM + Authors: Parker Thompson (https://github.com/mothran) and Todd DeLuca + (https://github.com/todddeluca) +- Pull Request #56: Return generator for `up` and `reload` output lines to + avoid having entire output in memory. + Authors: mmabey (https://github.com/mmabey) and Todd DeLuca + (https://github.com/todddeluca) + + ## 0.5.14 - Pull Request #51: Add support for the vagrant package command. diff -Nru python-vagrant-0.5.14/debian/changelog python-vagrant-0.5.15/debian/changelog --- python-vagrant-0.5.14/debian/changelog 2016-06-21 13:04:20.000000000 +0000 +++ python-vagrant-0.5.15/debian/changelog 2018-01-08 21:57:56.000000000 +0000 @@ -1,3 +1,15 @@ +python-vagrant (0.5.15-1~zesty) zesty; urgency=medium + + * backport to zesty + + -- Hans-Christoph Steiner Mon, 08 Jan 2018 22:57:56 +0100 + +python-vagrant (0.5.15-1) unstable; urgency=medium + + * New upstream version + + -- Hans-Christoph Steiner Mon, 08 Jan 2018 22:07:40 +0100 + python-vagrant (0.5.14-1) unstable; urgency=medium * latest upstream release diff -Nru python-vagrant-0.5.14/debian/control python-vagrant-0.5.15/debian/control --- python-vagrant-0.5.14/debian/control 2016-06-17 11:55:22.000000000 +0000 +++ python-vagrant-0.5.15/debian/control 2018-01-08 21:11:53.000000000 +0000 @@ -9,7 +9,7 @@ python-setuptools, python3-all, python3-setuptools, -Standards-Version: 3.9.8 +Standards-Version: 4.1.3 Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/python-vagrant.git Vcs-Browser: https://anonscm.debian.org/cgit/python-modules/packages/python-vagrant.git Homepage: https://github.com/todddeluca/python-vagrant diff -Nru python-vagrant-0.5.14/debian/copyright python-vagrant-0.5.15/debian/copyright --- python-vagrant-0.5.14/debian/copyright 2016-06-17 08:11:48.000000000 +0000 +++ python-vagrant-0.5.15/debian/copyright 2017-08-30 10:03:00.000000000 +0000 @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: python-vagrant Upstream-Contact: Todd DeLuca Source: https://github.com/todddeluca/python-vagrant diff -Nru python-vagrant-0.5.14/debian/watch python-vagrant-0.5.15/debian/watch --- python-vagrant-0.5.14/debian/watch 2016-06-16 19:53:41.000000000 +0000 +++ python-vagrant-0.5.15/debian/watch 2018-01-08 21:52:35.000000000 +0000 @@ -1,3 +1,3 @@ version=3 opts=uversionmangle=s/\.RC/~RC/ \ -http://pypi.debian.net/python-vagrant/python-vagrant-(.*)\.tar\.gz +https://pypi.debian.net/python-vagrant/python-vagrant-(.*)\.tar\.gz diff -Nru python-vagrant-0.5.14/PKG-INFO python-vagrant-0.5.15/PKG-INFO --- python-vagrant-0.5.14/PKG-INFO 2016-06-17 11:33:48.000000000 +0000 +++ python-vagrant-0.5.15/PKG-INFO 2017-05-08 14:36:35.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: python-vagrant -Version: 0.5.14 +Version: 0.5.15 Summary: Python bindings for interacting with Vagrant virtual machines. Home-page: https://github.com/todddeluca/python-vagrant Author: Todd Francis DeLuca diff -Nru python-vagrant-0.5.14/python_vagrant.egg-info/PKG-INFO python-vagrant-0.5.15/python_vagrant.egg-info/PKG-INFO --- python-vagrant-0.5.14/python_vagrant.egg-info/PKG-INFO 2016-06-17 11:33:44.000000000 +0000 +++ python-vagrant-0.5.15/python_vagrant.egg-info/PKG-INFO 2017-05-08 14:36:34.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: python-vagrant -Version: 0.5.14 +Version: 0.5.15 Summary: Python bindings for interacting with Vagrant virtual machines. Home-page: https://github.com/todddeluca/python-vagrant Author: Todd Francis DeLuca diff -Nru python-vagrant-0.5.14/tests/test_vagrant.py python-vagrant-0.5.15/tests/test_vagrant.py --- python-vagrant-0.5.14/tests/test_vagrant.py 2016-05-18 11:51:30.000000000 +0000 +++ python-vagrant-0.5.15/tests/test_vagrant.py 2017-05-08 14:35:53.000000000 +0000 @@ -23,7 +23,7 @@ import sys import tempfile import time -from nose.tools import eq_, ok_, with_setup +from nose.tools import eq_, ok_, with_setup, assert_raises import vagrant from vagrant import compat @@ -520,6 +520,58 @@ eq_(keyfile, parsed_config["IdentityFile"].lstrip('"').rstrip('"')) +@with_setup(make_setup_vm(), teardown_vm) +def test_ssh_command(): + ''' + Test executing a command via ssh on a vm. + ''' + v = vagrant.Vagrant(TD) + v.up() + output = v.ssh(command='echo hello') + assert output.strip() == 'hello' + + +@with_setup(make_setup_vm(MULTIVM_VAGRANTFILE), teardown_vm) +def test_ssh_command_multivm(): + ''' + Test executing a command via ssh on a specific vm + ''' + v = vagrant.Vagrant(TD) + v.up() + output = v.ssh(vm_name=VM_1, command='echo hello') + assert output.strip() == 'hello' + output = v.ssh(vm_name=VM_2, command='echo I like your hat') + assert output.strip() == 'I like your hat' + + +@with_setup(make_setup_vm(), teardown_vm) +def test_streaming_output(): + """ + Test streaming output of up or reload. + """ + test_string = 'Waiting for machine to boot.' + v = vagrant.Vagrant(TD) + + with assert_raises(subprocess.CalledProcessError): + v.up(vm_name='incorrect-name') + + streaming_up = False + for line in v.up(stream_output=True): + print('output line:', line) + if test_string in line: + streaming_up = True + + assert streaming_up + + streaming_reload = False + for line in v.reload(stream_output=True): + print('output line:', line) + if test_string in line: + streaming_reload = True + + assert streaming_reload + + def test_make_file_cm(): filename = os.path.join(TD, 'test.log') if os.path.exists(filename): diff -Nru python-vagrant-0.5.14/vagrant/__init__.py python-vagrant-0.5.15/vagrant/__init__.py --- python-vagrant-0.5.14/vagrant/__init__.py 2016-06-17 11:29:25.000000000 +0000 +++ python-vagrant-0.5.15/vagrant/__init__.py 2017-05-08 14:35:53.000000000 +0000 @@ -26,7 +26,7 @@ # python package version # should match r"^__version__ = '(?P[^']+)'$" for setup.py -__version__ = '0.5.14' +__version__ = '0.5.15' log = logging.getLogger(__name__) @@ -227,8 +227,8 @@ env=None, out_cm=None, err_cm=None): ''' root: a directory containing a file named Vagrantfile. Defaults to - os.getcwd(). This is the directory and Vagrantfile that the Vagrant - instance will operate on. + os.getcwd(). This is the directory and Vagrantfile that the Vagrant + instance will operate on. env: a dict of environment variables (string keys and values) passed to the vagrant command subprocess or None. Defaults to None. If env is None, `subprocess.Popen` uses the current process environment. @@ -300,17 +300,24 @@ self._call_vagrant_command(['init', box_name, box_url]) def up(self, no_provision=False, provider=None, vm_name=None, - provision=None, provision_with=None): + provision=None, provision_with=None, stream_output=False): ''' - Launch the Vagrant box. + Invoke `vagrant up` to start a box or boxes, possibly streaming the + command output. vm_name=None: name of VM. provision_with: optional list of provisioners to enable. provider: Back the machine with a specific provider no_provision: if True, disable provisioning. Same as 'provision=False'. provision: optional boolean. Enable or disable provisioning. Default behavior is to use the underlying vagrant default. + stream_output: if True, return a generator that yields each line of the + output of running the command. Consume the generator or the + subprocess might hang. if False, None is returned and the command + is run to completion without streaming the output. Defaults to + False. Note: If provision and no_provision are not None, no_provision will be ignored. + returns: None or a generator yielding lines of output. ''' provider_arg = '--provider=%s' % provider if provider else None prov_with_arg = None if provision_with is None else '--provision-with' @@ -323,15 +330,14 @@ no_provision_arg = '--no-provision' if no_provision else None provision_arg = None if provision is None else '--provision' if provision else '--no-provision' - self._call_vagrant_command(['up', vm_name, no_provision_arg, - provision_arg, provider_arg, - prov_with_arg, providers_arg]) - try: - self.conf(vm_name=vm_name) # cache configuration - except subprocess.CalledProcessError: - # in multi-VM environments, up() can be used to start all VMs, - # however vm_name is required for conf() or ssh_config(). - pass + args = ['up', vm_name, no_provision_arg, provision_arg, provider_arg, prov_with_arg, providers_arg] + if stream_output: + generator = self._stream_vagrant_command(args) + else: + self._call_vagrant_command(args) + + self._cached_conf[vm_name] = None # remove cached configuration + return generator if stream_output else None def provision(self, vm_name=None, provision_with=None): ''' @@ -345,25 +351,40 @@ self._call_vagrant_command(['provision', vm_name, prov_with_arg, providers_arg]) - def reload(self, vm_name=None, provision=None, provision_with=None): + def reload(self, vm_name=None, provision=None, provision_with=None, + stream_output=False): ''' Quoting from Vagrant docs: > The equivalent of running a halt followed by an up. - - > This command is usually required for changes made in the Vagrantfile to take effect. After making any modifications to the Vagrantfile, a reload should be called. - - > The configured provisioners will not run again, by default. You can force the provisioners to re-run by specifying the --provision flag. + > This command is usually required for changes made in the Vagrantfile + to take effect. After making any modifications to the Vagrantfile, a + reload should be called. + > The configured provisioners will not run again, by default. You can + force the provisioners to re-run by specifying the --provision flag. provision: optional boolean. Enable or disable provisioning. Default behavior is to use the underlying vagrant default. provision_with: optional list of provisioners to enable. e.g. ['shell', 'chef_solo'] + stream_output: if True, return a generator that yields each line of the + output of running the command. Consume the generator or the + subprocess might hang. if False, None is returned and the command + is run to completion without streaming the output. Defaults to + False. + returns: None or a generator yielding lines of output. ''' prov_with_arg = None if provision_with is None else '--provision-with' providers_arg = None if provision_with is None else ','.join(provision_with) provision_arg = None if provision is None else '--provision' if provision else '--no-provision' - self._call_vagrant_command(['reload', vm_name, provision_arg, - prov_with_arg, providers_arg]) + + args = ['reload', vm_name, provision_arg, prov_with_arg, providers_arg] + if stream_output: + generator = self._stream_vagrant_command(args) + else: + self._call_vagrant_command(args) + + self._cached_conf[vm_name] = None # remove cached configuration + return generator if stream_output else None def suspend(self, vm_name=None): ''' @@ -728,14 +749,24 @@ ''' self._call_vagrant_command(['snapshot', 'delete', name]) + def ssh(self, vm_name=None, command=None, extra_ssh_args=None): + ''' + Execute a command via ssh on the vm specified. + command: The command to execute via ssh. + extra_ssh_args: Corresponds to '--' option in the vagrant ssh command + Returns the output of running the command. + ''' + cmd = ['ssh', vm_name, '--command', command] + if extra_ssh_args is not None: + cmd += ['--', extra_ssh_args] + + return self._run_vagrant_command(cmd) + def _parse_box_list(self, output): ''' Remove Vagrant usage for unit testing ''' # Parse box list output - # Cue snarky comment about how nice it would be if vagrant used JSON - # or even had a description of the machine readable output for each - # command boxes = [] # initialize box values @@ -944,6 +975,35 @@ return compat.decode(subprocess.check_output(command, cwd=self.root, env=self.env, stderr=err_fh)) + def _stream_vagrant_command(self, args): + """ + Execute a vagrant command, returning a generator of the output lines. + Caller should consume the entire generator to avoid the hanging the + subprocess. + + :param args: Arguments for the Vagrant command. + :return: generator that yields each line of the command stdout. + :rtype: generator iterator + """ + py3 = sys.version_info > (3, 0) + + # Make subprocess command + command = self._make_vagrant_command(args) + with self.err_cm() as err_fh: + sp_args = dict(args=command, cwd=self.root, env=self.env, + stdout=subprocess.PIPE, stderr=err_fh, bufsize=1) + + # Iterate over output lines. + # See http://stackoverflow.com/questions/2715847/python-read-streaming-input-from-subprocess-communicate#17698359 + p = subprocess.Popen(**sp_args) + with p.stdout: + for line in iter(p.stdout.readline, b''): + yield compat.decode(line) # if PY3 decode bytestrings + p.wait() + # Raise CalledProcessError for consistency with _call_vagrant_command + if p.returncode != 0: + raise subprocess.CalledProcessError(p.returncode, command) + class SandboxVagrant(Vagrant): '''