@@ -300,17 +300,24 @@ def init(self, box_name=None, box_url=None):
300300 self ._call_vagrant_command (['init' , box_name , box_url ])
301301
302302 def up (self , no_provision = False , provider = None , vm_name = None ,
303- provision = None , provision_with = None , output_filter = None ):
303+ provision = None , provision_with = None , stream_output = False ):
304304 '''
305- Launch the Vagrant box.
305+ Invoke `vagrant up` to start a box or boxes, possibly streaming the
306+ command output.
306307 vm_name=None: name of VM.
307308 provision_with: optional list of provisioners to enable.
308309 provider: Back the machine with a specific provider
309310 no_provision: if True, disable provisioning. Same as 'provision=False'.
310311 provision: optional boolean. Enable or disable provisioning. Default
311312 behavior is to use the underlying vagrant default.
313+ stream_output: if True, return a generator that yields each line of the
314+ output of running the command. Consume the generator or the
315+ subprocess might hang. if False, None is returned and the command
316+ is run to completion without streaming the output. Defaults to
317+ False.
312318 Note: If provision and no_provision are not None, no_provision will be
313319 ignored.
320+ returns: None or a generator yielding lines of output.
314321 '''
315322 provider_arg = '--provider=%s' % provider if provider else None
316323 prov_with_arg = None if provision_with is None else '--provision-with'
@@ -324,20 +331,13 @@ def up(self, no_provision=False, provider=None, vm_name=None,
324331 provision_arg = None if provision is None else '--provision' if provision else '--no-provision'
325332
326333 args = ['up' , vm_name , no_provision_arg , provision_arg , provider_arg , prov_with_arg , providers_arg ]
327- filter_results = None
328- if isinstance (output_filter , dict ):
329- filter_results = self ._filter_vagrant_command (args , output_filter )
334+ if stream_output :
335+ generator = self ._stream_vagrant_command (args )
330336 else :
331337 self ._call_vagrant_command (args )
332338
333- try :
334- self .conf (vm_name = vm_name ) # cache configuration
335- except subprocess .CalledProcessError :
336- # in multi-VM environments, up() can be used to start all VMs,
337- # however vm_name is required for conf() or ssh_config().
338- pass
339-
340- return filter_results
339+ self ._cached_conf [vm_name ] = None # remove cached configuration
340+ return generator if stream_output else None
341341
342342 def provision (self , vm_name = None , provision_with = None ):
343343 '''
@@ -352,32 +352,39 @@ def provision(self, vm_name=None, provision_with=None):
352352 providers_arg ])
353353
354354 def reload (self , vm_name = None , provision = None , provision_with = None ,
355- output_filter = None ):
355+ stream_output = False ):
356356 '''
357357 Quoting from Vagrant docs:
358358 > The equivalent of running a halt followed by an up.
359-
360- > 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.
361-
362- > The configured provisioners will not run again, by default. You can force the provisioners to re-run by specifying the --provision flag.
359+ > This command is usually required for changes made in the Vagrantfile
360+ to take effect. After making any modifications to the Vagrantfile, a
361+ reload should be called.
362+ > The configured provisioners will not run again, by default. You can
363+ force the provisioners to re-run by specifying the --provision flag.
363364
364365 provision: optional boolean. Enable or disable provisioning. Default
365366 behavior is to use the underlying vagrant default.
366367 provision_with: optional list of provisioners to enable.
367368 e.g. ['shell', 'chef_solo']
369+ stream_output: if True, return a generator that yields each line of the
370+ output of running the command. Consume the generator or the
371+ subprocess might hang. if False, None is returned and the command
372+ is run to completion without streaming the output. Defaults to
373+ False.
374+ returns: None or a generator yielding lines of output.
368375 '''
369376 prov_with_arg = None if provision_with is None else '--provision-with'
370377 providers_arg = None if provision_with is None else ',' .join (provision_with )
371378 provision_arg = None if provision is None else '--provision' if provision else '--no-provision'
372379
373380 args = ['reload' , vm_name , provision_arg , prov_with_arg , providers_arg ]
374- filter_results = None
375- if isinstance (output_filter , dict ):
376- filter_results = self ._filter_vagrant_command (args , output_filter )
381+ if stream_output :
382+ generator = self ._stream_vagrant_command (args )
377383 else :
378384 self ._call_vagrant_command (args )
379385
380- return filter_results
386+ self ._cached_conf [vm_name ] = None # remove cached configuration
387+ return generator if stream_output else None
381388
382389 def suspend (self , vm_name = None ):
383390 '''
@@ -968,103 +975,37 @@ def _run_vagrant_command(self, args):
968975 return compat .decode (subprocess .check_output (command , cwd = self .root ,
969976 env = self .env , stderr = err_fh ))
970977
971- def _filter_vagrant_command (self , args , output_filter ):
972- """Execute the Vagrant command, return matches to the output filters.
973-
974- Output filter must have the following form:
975- {
976- 'filter_name': {'pat': r'regex pattern',
977- 'group': <int group number to return>},
978- ...
979- }
980-
981- The `group` key is actually optional, but defaults to 1 when omitted.
978+ def _stream_vagrant_command (self , args ):
979+ """
980+ Execute a vagrant command, returning a generator of the output lines.
981+ Caller should consume the entire generator to avoid the hanging the
982+ subprocess.
982983
983984 :param args: Arguments for the Vagrant command.
984- :param output_filter: Dictionary of output filters.
985- :type output_filter: dict
986- :return: Dictionary of results with the following form:
987- {'filter_name': 'matching result', ...}. If no match is found, the
988- value will be None.
989- :rtype: dict
985+ :return: generator that yields each line of the command stdout.
986+ :rtype: generator iterator
990987 """
991- assert isinstance (output_filter , dict )
992988 py3 = sys .version_info > (3 , 0 )
993989
994- # Create dictionary that will store the results from the filters
995- filter_results = dict .fromkeys (output_filter )
996-
997- for f in output_filter .keys ():
998- # Check the filter values, compile as regular expression objects if necessary
999- if not isinstance (output_filter [f ]['pat' ], type (re .compile ('' ))):
1000- if py3 and isinstance (output_filter [f ]['pat' ], str ):
1001- # In Python 3, the output from subprocess will be bytes, so the pattern has to be bytes as well
1002- # for it to match.
1003- output_filter [f ]['pat' ] = output_filter [f ]['pat' ].encode ()
1004- try :
1005- output_filter [f ]['pat' ] = re .compile (output_filter [f ]['pat' ])
1006- except TypeError :
1007- raise TypeError ('Output filters must have either a compiled regular expression or a regular '
1008- 'expression string stored in key ["pat"], got: {}' .
1009- format (type (output_filter [f ]['pat' ])))
1010-
1011- # Check that the group is an int, set to 1 if omitted
1012- if output_filter [f ].get ('group' ) is None :
1013- output_filter [f ]['group' ] = 1
1014- elif not isinstance (output_filter [f ]['group' ], int ):
1015- raise TypeError ('For output filters, value for key `group` must be an int, got {}' .
1016- format (type (output_filter [f ]['group' ])))
1017-
1018990 # Make subprocess command
1019991 command = self ._make_vagrant_command (args )
1020-
1021- # Don't override the user-specified output and error context managers
1022- with self .out_cm () as out_fh , self .err_cm () as err_fh :
992+ with self .err_cm () as err_fh :
1023993 sp_args = dict (args = command , cwd = self .root , env = self .env ,
1024994 stdout = subprocess .PIPE , stderr = err_fh , bufsize = 1 )
1025995
1026- # Parse command output for the specified filters. Method used depends on version of Python.
996+ # Method to iterate over output lines depends on version of Python.
1027997 # See http://stackoverflow.com/questions/2715847/python-read-streaming-input-from-subprocess-communicate#17698359
1028998 if not py3 : # Python 2.x
1029999 p = subprocess .Popen (** sp_args )
10301000 with p .stdout :
10311001 for line in iter (p .stdout .readline , b'' ):
1032- pop_key = None
1033- for f in output_filter .keys ():
1034- m = re .search (output_filter [f ]['pat' ], line )
1035- if m :
1036- try :
1037- filter_results [f ] = m .group (output_filter [f ]['group' ])
1038- except IndexError :
1039- # User must have not included parenthases in their pattern
1040- pass
1041- # No need to search for this pattern again in future lines
1042- pop_key = f
1043- break
1044- if pop_key :
1045- output_filter .pop (pop_key )
1046- out_fh .write (line )
1002+ yield line
10471003 p .wait ()
10481004 else : # Python 3.0+
10491005 with subprocess .Popen (** sp_args ) as p :
10501006 for line in p .stdout :
1051- pop_key = None
1052- for f in output_filter .keys ():
1053- m = re .search (output_filter [f ]['pat' ], line )
1054- if m :
1055- try :
1056- filter_results [f ] = m .group (output_filter [f ]['group' ])
1057- except IndexError :
1058- # User must have not included parenthases in their pattern
1059- pass
1060- # No need to search for this pattern again in future lines
1061- pop_key = f
1062- break
1063- if pop_key :
1064- output_filter .pop (pop_key )
1065- out_fh .write (line )
1066-
1067- return filter_results
1007+ yield line
1008+
10681009
10691010
10701011class SandboxVagrant (Vagrant ):
0 commit comments