@@ -300,7 +300,7 @@ 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 ):
303+ provision = None , provision_with = None , output_filter = None ):
304304 '''
305305 Launch the Vagrant box.
306306 vm_name=None: name of VM.
@@ -323,16 +323,22 @@ def up(self, no_provision=False, provider=None, vm_name=None,
323323 no_provision_arg = '--no-provision' if no_provision else None
324324 provision_arg = None if provision is None else '--provision' if provision else '--no-provision'
325325
326- self ._call_vagrant_command (['up' , vm_name , no_provision_arg ,
327- provision_arg , provider_arg ,
328- prov_with_arg , providers_arg ])
326+ 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 )
330+ else :
331+ self ._call_vagrant_command (args )
332+
329333 try :
330334 self .conf (vm_name = vm_name ) # cache configuration
331335 except subprocess .CalledProcessError :
332336 # in multi-VM environments, up() can be used to start all VMs,
333337 # however vm_name is required for conf() or ssh_config().
334338 pass
335339
340+ return filter_results
341+
336342 def provision (self , vm_name = None , provision_with = None ):
337343 '''
338344 Runs the provisioners defined in the Vagrantfile.
@@ -345,7 +351,8 @@ def provision(self, vm_name=None, provision_with=None):
345351 self ._call_vagrant_command (['provision' , vm_name , prov_with_arg ,
346352 providers_arg ])
347353
348- def reload (self , vm_name = None , provision = None , provision_with = None ):
354+ def reload (self , vm_name = None , provision = None , provision_with = None ,
355+ output_filter = None ):
349356 '''
350357 Quoting from Vagrant docs:
351358 > The equivalent of running a halt followed by an up.
@@ -362,8 +369,15 @@ def reload(self, vm_name=None, provision=None, provision_with=None):
362369 prov_with_arg = None if provision_with is None else '--provision-with'
363370 providers_arg = None if provision_with is None else ',' .join (provision_with )
364371 provision_arg = None if provision is None else '--provision' if provision else '--no-provision'
365- self ._call_vagrant_command (['reload' , vm_name , provision_arg ,
366- prov_with_arg , providers_arg ])
372+
373+ 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 )
377+ else :
378+ self ._call_vagrant_command (args )
379+
380+ return filter_results
367381
368382 def suspend (self , vm_name = None ):
369383 '''
@@ -954,6 +968,104 @@ def _run_vagrant_command(self, args):
954968 return compat .decode (subprocess .check_output (command , cwd = self .root ,
955969 env = self .env , stderr = err_fh ))
956970
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.
982+
983+ :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
990+ """
991+ assert isinstance (output_filter , dict )
992+ py3 = sys .version_info > (3 , 0 )
993+
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+
1018+ # Make subprocess command
1019+ 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 :
1023+ sp_args = dict (args = command , cwd = self .root , env = self .env ,
1024+ stdout = subprocess .PIPE , stderr = err_fh , bufsize = 1 )
1025+
1026+ # Parse command output for the specified filters. Method used depends on version of Python.
1027+ # See http://stackoverflow.com/questions/2715847/python-read-streaming-input-from-subprocess-communicate#17698359
1028+ if not py3 : # Python 2.x
1029+ p = subprocess .Popen (** sp_args )
1030+ with p .stdout :
1031+ 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 )
1047+ p .wait ()
1048+ else : # Python 3.0+
1049+ with subprocess .Popen (** sp_args ) as p :
1050+ 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
1068+
9571069
9581070class SandboxVagrant (Vagrant ):
9591071 '''
0 commit comments