@@ -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,23 @@ 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+ if filter_results :
341+ return filter_results
342+
336343 def provision (self , vm_name = None , provision_with = None ):
337344 '''
338345 Runs the provisioners defined in the Vagrantfile.
@@ -944,6 +951,95 @@ def _run_vagrant_command(self, args):
944951 return compat .decode (subprocess .check_output (command , cwd = self .root ,
945952 env = self .env , stderr = err_fh ))
946953
954+ def _filter_vagrant_command (self , args , output_filter ):
955+ """Execute the Vagrant command, return matches to the output filters.
956+
957+ Output filter must have the following form:
958+ {
959+ 'filter_name': {'pat': r'regex pattern',
960+ 'group': <int group number to return>},
961+ ...
962+ }
963+
964+ The `group` key is actually optional, but defaults to 1 when omitted.
965+
966+ :param args: Arguments for the Vagrant command.
967+ :param output_filter: Dictionary of output filters.
968+ :type output_filter: dict
969+ :return: Dictionary of results with the following form:
970+ {'filter_name': 'matching result', ...}. If no match is found, the
971+ value will be None.
972+ :rtype: dict
973+ """
974+ assert isinstance (output_filter , dict )
975+ if sys .version_info > (3 , 0 ):
976+ py3 = True
977+ else :
978+ py3 = False
979+
980+ # Create dictionary that will store the results from the filters
981+ filter_results = dict .fromkeys (output_filter )
982+
983+ for f in output_filter .keys ():
984+ # Check the filter values, compile as regular expression objects if necessary
985+ if not isinstance (output_filter [f ]['pat' ], type (re .compile ('' ))):
986+ try :
987+ output_filter [f ]['pat' ] = re .compile (output_filter [f ]['pat' ])
988+ except TypeError :
989+ raise TypeError ('Output filters must have either a compiled regular expression or a regular '
990+ 'expression string stored in key ["pat"], got: {}' .
991+ format (type (output_filter [f ]['pat' ])))
992+
993+ # Check that the group is an int, set to 1 if omitted
994+ if output_filter [f ].get ('group' ) is None :
995+ output_filter [f ]['group' ] = 1
996+ elif not isinstance (output_filter [f ]['group' ], int ):
997+ raise TypeError ('For output filters, value for key `group` must be an int, got {}' .
998+ format (type (output_filter [f ]['group' ])))
999+
1000+ # Make subprocess command
1001+ command = self ._make_vagrant_command (args )
1002+
1003+ # Don't override the user-specified output and error context managers
1004+ with self .out_cm () as out_fh , self .err_cm () as err_fh :
1005+ sp_args = dict (args = command , cwd = self .root , env = self .env ,
1006+ stdout = subprocess .PIPE , stderr = err_fh , bufsize = 1 )
1007+
1008+ # Parse command output for the specified filters. Method used depends on version of Python.
1009+ # See http://stackoverflow.com/questions/2715847/python-read-streaming-input-from-subprocess-communicate#17698359
1010+ if not py3 : # Python < 3.0
1011+ p = subprocess .Popen (** sp_args )
1012+ with p .stdout :
1013+ for line in iter (p .stdout .readline , b'' ):
1014+ pop_key = None
1015+ for f in output_filter .keys ():
1016+ m = re .search (output_filter [f ]['pat' ], line )
1017+ if m :
1018+ filter_results [f ] = m .group (output_filter [f ]['group' ])
1019+ # No need to search for this pattern again in future lines
1020+ pop_key = f
1021+ break
1022+ if pop_key :
1023+ output_filter .pop (pop_key )
1024+ out_fh .write (line )
1025+ p .wait ()
1026+ else : # Python 3.0+
1027+ with subprocess .Popen (** sp_args ) as p :
1028+ for line in p .stdout :
1029+ pop_key = None
1030+ for f in output_filter .keys ():
1031+ m = re .search (output_filter [f ]['pat' ], line )
1032+ if m :
1033+ filter_results [f ] = m .group (output_filter [f ]['group' ])
1034+ # No need to search for this pattern again in future lines
1035+ pop_key = f
1036+ break
1037+ if pop_key :
1038+ output_filter .pop (pop_key )
1039+ out_fh .write (line )
1040+
1041+ return filter_results
1042+
9471043
9481044class SandboxVagrant (Vagrant ):
9491045 '''
0 commit comments