diff --git a/.github/workflows/validate-queries.yml b/.github/workflows/validate-queries.yml new file mode 100644 index 0000000000..86b8cee410 --- /dev/null +++ b/.github/workflows/validate-queries.yml @@ -0,0 +1,29 @@ +name: Validate SumoLogic Queries +on: + pull_request: + paths: + - '**/*.md' # Trigger only when Markdown files change + +jobs: + validate-queries: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Required for git diff detection + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install dependencies + run: pip install requests python-dotenv + + - name: Validate queries + working-directory: ./scripts + env: + SUMO_LOGIC_ACCESS_ID: ${{ secrets.SUMO_LOGIC_ACCESS_ID }} + SUMO_LOGIC_ACCESS_KEY: ${{ secrets.SUMO_LOGIC_ACCESS_KEY }} + run: | + python validate_queries.py diff --git a/docs/search/search-query-language/search-operators/isvalidip.md b/docs/search/search-query-language/search-operators/isvalidip.md index 733a43ad0c..10a54325c2 100644 --- a/docs/search/search-query-language/search-operators/isvalidip.md +++ b/docs/search/search-query-language/search-operators/isvalidip.md @@ -53,6 +53,9 @@ The following returns `true`: ```sql | isValidIP("10.255.255.255") as isIP ``` +```sql +| isValidIP("30.255.255.255") as isIP +``` The following returns `true`: diff --git a/docs/search/search-query-language/search-operators/where.md b/docs/search/search-query-language/search-operators/where.md index 6c88a30754..5db775a093 100644 --- a/docs/search/search-query-language/search-operators/where.md +++ b/docs/search/search-query-language/search-operators/where.md @@ -12,6 +12,13 @@ For example, usingย `where`ย with the boolean operator [`isValidIP`](/docs/searc ```sql | where isValidIP("192.168.0.10") ``` +* Checking my PR: + ```sql + _collector="ABC1" | where type="web" + ``` + ```sql + _collector="ABC6" | where type="web" + ``` * Filters as false and will not return results: ```sql | where !isValidIP("192.168.0.10") diff --git a/scripts/sumologic_client.py b/scripts/sumologic_client.py new file mode 100644 index 0000000000..eb41a72077 --- /dev/null +++ b/scripts/sumologic_client.py @@ -0,0 +1,47 @@ +import os +import requests +from datetime import datetime, timedelta + +class SumoLogicClient: + def __init__(self): + self.base_url = "https://long-api.sumologic.net/api/v1" + self.session = requests.Session() + self.session.headers.update({'Content-Type': 'application/json'}) + self.session.auth = ( + os.getenv("SUMO_LOGIC_ACCESS_ID"), + os.getenv("SUMO_LOGIC_ACCESS_KEY") + ) + + def test_query(self, query): + """Execute a query in Sumo Logic and check for results""" + job_id = self._create_search_job(query) + status = self._wait_for_job(job_id) + return self._check_results(job_id) if status == "DONE GATHERING RESULTS" else False + + def _create_search_job(self, query): + end_time = datetime.utcnow() + start_time = end_time - timedelta(hours=24) + payload = { + "query": query, + "from": start_time.isoformat() + "Z", + "to": end_time.isoformat() + "Z", + "timeZone": "UTC" + } + response = self.session.post(f"{self.base_url}/search/jobs", json=payload) + response.raise_for_status() + return response.json()["id"] + + def _wait_for_job(self, job_id, max_attempts=10): + for _ in range(max_attempts): + response = self.session.get(f"{self.base_url}/search/jobs/{job_id}") + response.raise_for_status() + status = response.json()["state"] + if status in ["DONE GATHERING RESULTS", "CANCELLED"]: + return status + time.sleep(3) + return "TIMEOUT" + + def _check_results(self, job_id): + response = self.session.get(f"{self.base_url}/search/jobs/{job_id}/messages") + response.raise_for_status() + return len(response.json()["messages"]) > 0 diff --git a/scripts/validate_queries.py b/scripts/validate_queries.py new file mode 100644 index 0000000000..9b6b0169a7 --- /dev/null +++ b/scripts/validate_queries.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +import re +import sys +import os +import json +import requests +from pathlib import Path +from datetime import datetime, timedelta + +def get_repo_root(): + """Get absolute path to repository root""" + github_workspace = os.getenv('GITHUB_WORKSPACE') + if github_workspace and Path(github_workspace).exists(): + return Path(github_workspace) + return Path(__file__).parent.parent # Move up from scripts/ directory + +def debug_environment(): + """Debug workspace structure""" + repo_root = get_repo_root() + print("::group::โš™๏ธ Environment Debug") + print(f"๐Ÿ“‚ Repo root: {repo_root}") + print(f"๐Ÿ“‚ Working dir: {os.getcwd()}") + print("\n๐Ÿ“ Directory Structure:") + os.system(f"find {repo_root} -maxdepth 3 -type d | sort") + print("\n๐Ÿ“ Markdown Files:") + os.system(f"find {repo_root} -name '*.md' | head -n 20") + print("::endgroup::") + return repo_root + +def get_changed_files(repo_root): + """Find Markdown files to validate""" + # Try GitHub PR context first + if "GITHUB_EVENT_PATH" in os.environ: + try: + with open(os.environ["GITHUB_EVENT_PATH"]) as f: + pr_files = [ + str(repo_root / f['filename']) + for f in json.load(f).get('pull_request', {}).get('files', []) + if f['filename'].endswith('.md') + ] + if pr_files: + print(f"๐Ÿ“ฆ Found {len(pr_files)} changed Markdown files") + return pr_files + except Exception as e: + print(f"::warning::Couldn't read PR data: {e}") + + # Fallback: Scan docs directory + docs_dir = repo_root / "docs" + if docs_dir.exists(): + md_files = list(docs_dir.rglob("*.md")) + print(f"๐Ÿ”„ Scanning {len(md_files)} docs files") + return [str(f) for f in md_files] + + print("::error::No Markdown files found in docs/ directory") + return [] + +def main(): + repo_root = debug_environment() + changed_files = get_changed_files(repo_root) + + if not changed_files: + print("::warning::No Markdown files to validate") + sys.exit(0) + + print(f"Validating {len(changed_files)} files...") + # Rest of your validation logic here + +if __name__ == "__main__": + main() \ No newline at end of file