Skip to content

Commit f1aec18

Browse files
yummyblablaDerrick Lee
andauthored
[ASIM] Modify Template Validation test to prevent flakey test results (#14469)
* Modify script and add test yamls * [ASIM Parsers] Generate deployable ARM templates from KQL function YAML files. * Try this * test changes * misname * [ASIM Parsers] Generate deployable ARM templates from KQL function YAML files. * Revert test changes * Undo change in arm template --------- Co-authored-by: Derrick Lee <derricklee@microsoft.com> Co-authored-by: github-actions[bot] <>
1 parent 8f96b61 commit f1aec18

2 files changed

Lines changed: 115 additions & 82 deletions

File tree

.github/workflows/runAsimSchemaAndDataTesters.yaml

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,6 @@ jobs:
5050
outputs:
5151
approved: ${{ steps.check-approval.outputs.approved }}
5252
steps:
53-
- name: Checkout pull request branch
54-
if: github.event.pull_request != null
55-
uses: actions/checkout@v3
56-
with:
57-
ref: ${{ github.event.pull_request.head.sha }}
58-
repository: ${{ github.event.pull_request.head.repo.full_name }}
59-
fetch-depth: 0
60-
6153
- name: Check if PR needs approval
6254
id: check-approval
6355
run: |
@@ -166,35 +158,36 @@ jobs:
166158
167159
- name: Prevent fork modifications to test infrastructure
168160
if: github.event.pull_request.head.repo.fork == true
169-
shell: bash
170-
run: |
171-
set -euo pipefail
172-
173-
log_info() { echo "ℹ️ $1"; }
174-
log_error() { echo "❌ $1"; }
175-
log_success() { echo "✅ $1"; }
176-
177-
log_info "Checking for modifications to asimParsersTest folder in fork PR..."
178-
179-
# We are currently checked out at the fork's HEAD SHA (actions/checkout did that).
180-
# Add the base repo as a remote and fetch the base branch, so we can diff reliably.
181-
git remote remove upstream 2>/dev/null || true
182-
git remote add upstream "https://github.com/${{ github.repository }}.git"
161+
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410
162+
with:
163+
script: |
164+
const log = (level, message) => {
165+
const icons = { info: 'ℹ️', success: '✅', warning: '⚠️', error: '❌' };
166+
console.log(`${icons[level] || 'ℹ️'} ${message}`);
167+
};
183168
184-
log_info "Fetching base branch ${{ github.event.pull_request.base.ref }} from upstream..."
185-
git fetch --no-tags --prune upstream "${{ github.event.pull_request.base.ref }}"
169+
log('info', 'Checking for modifications to asimParsersTest folder in fork PR...');
186170
187-
# Diff base branch (FETCH_HEAD) -> current HEAD (fork head)
188-
modified_files="$(git diff --name-only "FETCH_HEAD...HEAD")"
171+
// Paginate through all changed files in the PR
172+
const files = await github.paginate(github.rest.pulls.listFiles, {
173+
owner: context.repo.owner,
174+
repo: context.repo.repo,
175+
pull_number: context.issue.number,
176+
per_page: 100,
177+
});
189178
190-
if echo "$modified_files" | grep -E "\.script/tests/asimParsersTest/" > /dev/null; then
191-
log_error "Fork PRs cannot modify the asimParsersTest test infrastructure folder"
192-
log_error "Modified test files detected:"
193-
echo "$modified_files" | grep "\.script/tests/asimParsersTest/" | sed 's/^/ - /'
194-
exit 1
195-
fi
179+
const forbiddenFiles = files
180+
.map(f => f.filename)
181+
.filter(name => name.startsWith('.script/tests/asimParsersTest/'));
196182
197-
log_success "No modifications to asimParsersTest folder detected"
183+
if (forbiddenFiles.length > 0) {
184+
log('error', 'Fork PRs cannot modify the asimParsersTest test infrastructure folder');
185+
log('error', 'Modified test files detected:');
186+
forbiddenFiles.forEach(f => console.log(` - ${f}`));
187+
core.setFailed('Fork PR modifies protected test infrastructure files');
188+
} else {
189+
log('success', 'No modifications to asimParsersTest folder detected');
190+
}
198191
199192
- name: Comment on fork PR for approval guidance
200193
if: |

.script/tests/asimParsersTest/VerifyASimParserTemplate.py

Lines changed: 89 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
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+
123
import sys
224
import os
325

@@ -8,7 +30,6 @@
830
if script_dir in sys.path:
931
sys.path.remove(script_dir)
1032

11-
import requests
1233
import yaml
1334
import re
1435
import subprocess
@@ -19,7 +40,7 @@
1940

2041
# Constants
2142
SENTINEL_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/'
2344
parser_exclusion_file_path = '.script/tests/asimParsersTest/ExclusionListForASimTests.csv'
2445
# Sentinel Repo URL
2546
SentinelRepoUrl = f"https://github.com/Azure/Azure-Sentinel.git"
@@ -54,10 +75,11 @@
5475
def 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

318364
def 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

Comments
 (0)