@@ -1374,19 +1374,15 @@ def __init__(self, location,
13741374 self .resource_attributes = resource_attributes or OrderedDict ()
13751375 self .resource_class = None
13761376 self .has_single_resource = False
1377+ self .location = location
13771378
13781379 scan_data = self ._get_scan_data (location )
13791380 self ._populate (scan_data )
13801381
1381- def _get_scan_data (self , location ):
1382+ def _get_scan_data_helper (self , location ):
13821383 """
1383- Return scan data loaded from `location` that is either:
1384- - a path string
1385- - a JSON string
1386- - a Python mapping
1384+ Return scan data loaded from `location`, which is a path string
13871385 """
1388- if isinstance (location , dict ):
1389- return location
13901386 try :
13911387 return json .loads (location , object_pairs_hook = OrderedDict )
13921388 except :
@@ -1398,6 +1394,33 @@ def _get_scan_data(self, location):
13981394 f , object_pairs_hook = OrderedDict , encoding = 'utf-8' )
13991395 return scan_data
14001396
1397+ def _get_scan_data (self , location ):
1398+ """
1399+ Return scan data loaded from `location` that is either:
1400+ - a path string
1401+ - a JSON string
1402+ - a Python mapping
1403+
1404+ or `location` is a List or a Tuple that contains multiple paths to scans that are to be joined together.
1405+ """
1406+ if isinstance (location , dict ):
1407+ return location
1408+ if isinstance (location , (list , tuple ,)):
1409+ combined_scan_data = OrderedDict (headers = [], files = [])
1410+ for loc in location :
1411+ scan_data = self ._get_scan_data_helper (loc )
1412+ headers = scan_data .get ('headers' )
1413+ if headers :
1414+ combined_scan_data ['headers' ].extend (headers )
1415+ files = scan_data .get ('files' )
1416+ if files :
1417+ combined_scan_data ['files' ].extend (files )
1418+ else :
1419+ raise Exception ('Input file does not have Resources to import: {}' .format (loc ))
1420+ combined_scan_data ['headers' ] = sorted (combined_scan_data ['headers' ], key = lambda x : x ['start_timestamp' ])
1421+ return combined_scan_data
1422+ return self ._get_scan_data_helper (location )
1423+
14011424 def _create_empty_resource_data (self ):
14021425 """
14031426 Return a dictionary of Resource fields and their default values.
@@ -1527,9 +1550,15 @@ def _populate(self, scan_data):
15271550 ##########################################################
15281551 # Create root resource without setting root data just yet. If we run into the root data
15291552 # while we iterate through `resources_data`, we fill in the data then.
1530- sample_resource_path = sample_resource_data ['path' ]
1531- sample_resource_path = sample_resource_path .strip ('/' )
1532- root_path = sample_resource_path .split ('/' )[0 ]
1553+
1554+ # Create a virtual root if we are merging multiple scans together
1555+ multiple_input = isinstance (self .location , (list , tuple ,)) and len (self .location ) > 1
1556+ if multiple_input :
1557+ root_path = 'virtual_root'
1558+ else :
1559+ sample_resource_path = sample_resource_data ['path' ]
1560+ sample_resource_path = sample_resource_path .strip ('/' )
1561+ root_path = sample_resource_path .split ('/' )[0 ]
15331562 root_name = root_path
15341563 root_is_file = False
15351564 root_data = self ._create_empty_resource_data ()
@@ -1541,6 +1570,9 @@ def _populate(self, scan_data):
15411570
15421571 for resource_data in resources_data :
15431572 path = resource_data .get ('path' )
1573+ # Append virtual_root path to imported Resource path if we are merging multiple scans
1574+ if multiple_input :
1575+ path = os .path .join (root_path , path )
15441576 name = resource_data .get ('name' , None )
15451577 if not name :
15461578 name = file_name (path )
0 commit comments