1+ # VerifyASimParserTemplate.py
2+ #
3+ # Validates the YAML template of modified ASIM parsers (both ASim and vim variants).
4+ # For each modified parser, the following checks are performed against the parser
5+ # YAML and its corresponding union parser YAML:
6+ #
7+ # 1. EventProduct - ParserQuery must set EventProduct (exempt for _Native parsers)
8+ # 2. EventVendor - ParserQuery must set EventVendor (exempt for _Native parsers)
9+ # 3. ParserName in union parser - ParserName must appear in the union parser's ParserQuery
10+ # 4. EquivalentBuiltInParser in union parser - must appear in the union parser's Parsers list
11+ # 5. Parser.Title - must be present
12+ # 6. Parser.Version - must be present and in X.X.X format
13+ # 7. Parser.LastUpdated - must be present and in "Mon DD, YYYY" format (e.g. Jun 29, 2024)
14+ # 8. Normalization.Schema - must match a known ASIM schema name from SCHEMA_INFO
15+ # 9. Normalization.Version - must match the expected version for the schema in SCHEMA_INFO
16+ # 10. References - must include schema-specific and ASIM doc reference links with correct titles/URLs
17+ # 11. ParserName format - must match <FileType><Schema>... (e.g. ASimDnsMyProduct)
18+ # 12. EquivalentBuiltInParser format - must match _<FileType>_<Schema>_... (e.g. _ASim_Dns_MyProduct)
19+ # 13. Sample data file - "Sample Data/ASIM/{Vendor}_{Product}_{Schema}_IngestedLogs.csv" must exist (ASim only)
20+ #
21+ # Parsers listed in ExclusionListForASimTests.csv are allowed to fail without blocking the workflow.
22+
123import sys
224import os
325
830if script_dir in sys .path :
931 sys .path .remove (script_dir )
1032
11- import requests
1233import yaml
1334import re
1435import subprocess
1940
2041# Constants
2142SENTINEL_REPO_RAW_URL = f'https://raw.githubusercontent.com/Azure/Azure-Sentinel'
22- SAMPLE_DATA_PATH = 'Sample%20Data /ASIM/'
43+ SAMPLE_DATA_PATH = 'Sample Data /ASIM/'
2344parser_exclusion_file_path = '.script/tests/asimParsersTest/ExclusionListForASimTests.csv'
2445# Sentinel Repo URL
2546SentinelRepoUrl = f"https://github.com/Azure/Azure-Sentinel.git"
5475def run ():
5576 """Main function to execute the script logic."""
5677 current_directory = os .path .dirname (os .path .abspath (__file__ ))
78+ repo_root = os .path .normpath (os .path .join (current_directory , '..' , '..' , '..' ))
5779 modified_files = get_modified_files (current_directory )
58- commit_number = get_current_commit_number ()
59- sample_data_url = f'{ SENTINEL_REPO_RAW_URL } /{ commit_number } /{ SAMPLE_DATA_PATH } '
80+ sample_data_path = os .path .join (repo_root , SAMPLE_DATA_PATH )
6081 parser_yaml_files = filter_yaml_files (modified_files )
82+ has_failures = False
6183 print (f"{ GREEN } Following files were found to be modified:{ RESET } " )
6284 for file in parser_yaml_files :
6385 print (f"{ YELLOW } { file } { RESET } " )
@@ -78,41 +100,62 @@ def run():
78100 else :
79101 # Skip the vim parser file as the corresponding ASim parser file is present and vim files will be tested with ASim files in upcoming steps.
80102 continue
81- asim_parser_url = f' { SENTINEL_REPO_RAW_URL } / { commit_number } / { parser } '
82- print (f'{ YELLOW } Constructed parser raw url: { asim_parser_url } { RESET } ' ) # uncomment for debugging
83- asim_union_parser_url = f' { SENTINEL_REPO_RAW_URL } / { commit_number } / Parsers/ ASim{ schema_name } / Parsers/ ASim{ schema_name } .yaml'
84- print (f'{ YELLOW } Constructed union parser raw url: { asim_union_parser_url } { RESET } ' ) # uncomment for debugging
85- asim_parser = read_github_yaml ( asim_parser_url )
86- asim_union_parser = read_github_yaml ( asim_union_parser_url )
103+ asim_parser_path = os . path . join ( repo_root , parser )
104+ print (f'{ YELLOW } Parser file path: { asim_parser_path } { RESET } ' )
105+ asim_union_parser_path = os . path . join ( repo_root , ' Parsers' , f' ASim{ schema_name } ' , ' Parsers' , f' ASim{ schema_name } .yaml')
106+ print (f'{ YELLOW } Union parser file path: { asim_union_parser_path } { RESET } ' )
107+ asim_parser = read_local_yaml ( asim_parser_path )
108+ asim_union_parser = read_local_yaml ( asim_union_parser_path )
87109 # Both ASim and union parser files should be present to proceed with the tests
88- if not (check_parser_found (asim_parser , asim_parser_url ) and check_parser_found (asim_union_parser , asim_union_parser_url )):
110+ if not (check_parser_found (asim_parser , asim_parser_path ) and check_parser_found (asim_union_parser , asim_union_parser_path )):
89111 continue
90112 print_test_header (asim_parser .get ('EquivalentBuiltInParser' ))
91- results = extract_and_check_properties (asim_parser , asim_union_parser , "ASim" , asim_parser_url , sample_data_url )
113+ results = extract_and_check_properties (asim_parser , asim_union_parser , "ASim" , sample_data_path )
92114 print_results_table (results )
93115
94- check_test_failures (results , asim_parser )
116+ if check_test_failures (results , asim_parser ):
117+ has_failures = True
95118
96- vim_parser , vim_union_parser = get_vim_parsers (asim_parser_url , asim_union_parser_url , asim_parser )
119+ vim_parser , vim_union_parser = get_vim_parsers (asim_parser_path , asim_union_parser_path )
97120 # Both vim and union parser files should be present to proceed with the tests
98- if not (check_parser_found (vim_parser , asim_parser_url ) and check_parser_found (vim_union_parser , asim_union_parser_url )):
121+ if not (check_parser_found (vim_parser , asim_parser_path ) and check_parser_found (vim_union_parser , asim_union_parser_path )):
99122 continue
100123 print_test_header (vim_parser .get ('EquivalentBuiltInParser' ))
101- results = extract_and_check_properties (vim_parser , vim_union_parser , "vim" , asim_parser_url , sample_data_url )
124+ results = extract_and_check_properties (vim_parser , vim_union_parser , "vim" , sample_data_path )
102125 print_results_table (results )
103126
104- check_test_failures (results , vim_parser )
127+ if check_test_failures (results , vim_parser ):
128+ has_failures = True
129+
130+ if has_failures :
131+ exit (1 )
105132
106- def extract_and_check_properties (Parser_file , Union_Parser__file , FileType , ParserUrl , ASIMSampleDataURL ):
133+ def extract_and_check_properties (Parser_file , Union_Parser__file , FileType , ASIMSampleDataURL ):
107134 """
108- Extracts properties from the given YAML files and checks if they exist in another YAML file.
135+ Validates the template properties of an ASIM parser YAML file.
136+
137+ Checks performed:
138+ - EventProduct and EventVendor are mapped in ParserQuery (or parser is _Native)
139+ - ParserName is referenced in the union parser's ParserQuery
140+ - EquivalentBuiltInParser is listed in the union parser's Parsers array
141+ - Parser.Title is present
142+ - Parser.Version is present and in X.X.X format
143+ - Parser.LastUpdated is present and in 'Mon DD, YYYY' format
144+ - Normalization.Schema matches a known ASIM schema name
145+ - Normalization.Version matches the expected schema version
146+ - References include correct schema-specific and ASIM doc links
147+ - ParserName follows the naming convention <FileType><Schema>...
148+ - EquivalentBuiltInParser follows _<FileType>_<Schema>_... format
149+ - Sample data CSV file exists locally (ASim parsers only)
109150
110151 Args:
111- yaml_file (dict): The YAML file to extract properties from.
112- another_yaml_file (dict): The YAML file to check for the existence of properties.
152+ Parser_file (dict): Parsed YAML content of the parser file.
153+ Union_Parser__file (dict): Parsed YAML content of the union parser file.
154+ FileType (str): Parser type - 'ASim' or 'vim'.
155+ ASIMSampleDataURL (str): Local path to the sample data directory.
113156
114157 Returns:
115- list: A list of tuples containing the property name, the property type, and a boolean indicating if the property exists in another_yaml_file .
158+ list: A list of (value, description, result) tuples where result is 'Pass' or 'Fail' .
116159 """
117160 results = []
118161 parser_name = Parser_file .get ('ParserName' )
@@ -265,10 +308,9 @@ def extract_and_check_properties(Parser_file, Union_Parser__file, FileType, Pars
265308 if FileType == "ASim" :
266309 # construct filename
267310 SampleDataFile = f'{ event_vendor } _{ event_product } _{ schema } _IngestedLogs.csv'
268- SampleDataUrl = ASIMSampleDataURL + SampleDataFile
269- # check if file exists
270- response = requests .get (SampleDataUrl )
271- if response .status_code == 200 :
311+ SampleDataFilePath = os .path .join (ASIMSampleDataURL , SampleDataFile )
312+ # check if file exists locally
313+ if os .path .exists (SampleDataFilePath ):
272314 results .append ((SampleDataFile , 'Sample data file exists' , 'Pass' ))
273315 else :
274316 results .append ((f'{ RED } Expected sample file not found{ RESET } ' , f'{ RED } Sample data file does not exist or may not be named correctly. Please include sample data file "{ event_vendor } _{ event_product } _{ schema } _IngestedLogs.csv"{ RESET } ' , f'{ RED } Fail{ RESET } ' ))
@@ -308,12 +350,16 @@ def extract_schema_name(parser):
308350 match = re .search (r'ASim(\w+)/' , parser )
309351 return match .group (1 ) if match else None
310352
311- def read_github_yaml ( url ):
353+ def read_local_yaml ( filepath ):
312354 try :
313- response = requests .get (url )
355+ with open (filepath , 'r' , encoding = 'utf-8' ) as f :
356+ return yaml .safe_load (f .read ())
357+ except FileNotFoundError :
358+ print (f"::error::YAML file not found at { filepath } " )
359+ return None
314360 except Exception as e :
315- print (f"::error::An error occurred while trying to get content of YAML file located at { url } : { e } " )
316- return yaml . safe_load ( response . text ) if response . status_code == 200 else None
361+ print (f"::error::An error occurred while reading YAML file at { filepath } : { e } " )
362+ return None
317363
318364def print_test_header (parser_name ):
319365 print ("***********************************" )
@@ -330,33 +376,27 @@ def check_test_failures(results, parser):
330376 exclusion_list = read_exclusion_list_from_csv ()
331377 if parser .get ('EquivalentBuiltInParser' ) in exclusion_list :
332378 print (f"::warning::The parser { parser .get ('EquivalentBuiltInParser' )} is listed in the exclusions file, so this workflow run will not fail because of it. To allow this parser to trigger a workflow failure, please remove its name from the exclusions list file located at: { parser_exclusion_file_path } " )
379+ return False
333380 else :
334- exit ( 1 )
381+ return True
335382 else :
336383 print (f"{ GREEN } All tests successfully passed for this parser.{ RESET } " )
384+ return False
337385
338- def check_parser_found (asim_parser ,parser_url ):
386+ def check_parser_found (asim_parser , parser_path ):
339387 if asim_parser is None :
340- print (f"::error::Parser file not found. Please check the URL and try again : { parser_url } " )
388+ print (f"::error::Parser file not found at : { parser_path } " )
341389 exit (1 ) # Uncomment this line to fail the workflow if parser file not found.
342- else :
343- return True
390+
391+ return True
344392
345- def get_vim_parsers (asim_parser_url , asim_union_parser_url , asim_parser ):
346- # Split the URL into parts
347- parts = asim_parser_url .split ('/' )
393+ def get_vim_parsers (asim_parser_path , asim_union_parser_path ):
348394 # Replace 'ASim' with 'vim' in the filename only
349- parts [- 1 ] = parts [- 1 ].replace ('ASim' , 'vim' , 1 )
350- # Join the parts back into a full URL for vim_parser_url
351- vim_parser_url = '/' .join (parts )
352- # Repeat the process for asim_union_parser_url
353- parts_union = asim_union_parser_url .split ('/' )
354- # Replace 'ASim' with 'im' in the filename only
355- parts_union [- 1 ] = parts_union [- 1 ].replace ('ASim' , 'im' , 1 )
356- # Join the parts back into a full URL for vim_union_parser_url
357- vim_union_parser_url = '/' .join (parts_union )
358- vim_parser = read_github_yaml (vim_parser_url )
359- vim_union_parser = read_github_yaml (vim_union_parser_url )
395+ vim_parser_path = os .path .join (os .path .dirname (asim_parser_path ), os .path .basename (asim_parser_path ).replace ('ASim' , 'vim' , 1 ))
396+ # Replace 'ASim' with 'im' in the union parser filename only
397+ vim_union_parser_path = os .path .join (os .path .dirname (asim_union_parser_path ), os .path .basename (asim_union_parser_path ).replace ('ASim' , 'im' , 1 ))
398+ vim_parser = read_local_yaml (vim_parser_path )
399+ vim_union_parser = read_local_yaml (vim_union_parser_path )
360400 return vim_parser , vim_union_parser
361401
362402# Function to read Exclusion list for ASim Parser test from a CSV file
0 commit comments