|
| 1 | +import fnmatch |
1 | 2 | import json |
2 | 3 | import logging |
3 | 4 | import os |
4 | 5 | import time |
5 | | -from typing import List, Dict, Any |
| 6 | +from typing import List, Dict, Any, Set |
6 | 7 |
|
7 | 8 | import google.auth.transport.requests |
8 | 9 | import jwt |
@@ -135,7 +136,7 @@ def create_comment(issue, body): |
135 | 136 | issue.create_comment(body=body) |
136 | 137 |
|
137 | 138 |
|
138 | | -def read_list_from_gcs(bucket_name: str, blob_name: str) -> set[str]: |
| 139 | +def read_items_from_gcs(bucket_name: str, blob_name: str) -> set[str]: |
139 | 140 | storage_client = storage.Client() |
140 | 141 | bucket = storage_client.bucket(bucket_name) |
141 | 142 | blob = bucket.blob(blob_name) |
@@ -190,18 +191,19 @@ def on_dag_finished(self, dag_run: DagRun, msg: str): |
190 | 191 | try: |
191 | 192 | # A DAG would trigger the following logic if the DAG ID is in allow list and not in block list. |
192 | 193 | enable_plugin = None |
193 | | - if DagRunListener.is_in_allow_list(dag_run.dag_id): |
| 194 | + if DagRunListener.is_in_allow_list(dag_run): |
194 | 195 | enable_plugin = True |
195 | 196 | logging.info( |
196 | 197 | f"[{self.log_prefix}] DAG {dag_run.dag_id} is in allow_list.txt" |
197 | 198 | ) |
198 | | - if DagRunListener.is_in_block_list(dag_run.dag_id): |
| 199 | + |
| 200 | + if DagRunListener.is_in_block_list(dag_run): |
199 | 201 | enable_plugin = False |
200 | 202 | logging.info( |
201 | 203 | f"[{self.log_prefix}] DAG {dag_run.dag_id} is in block_list.txt" |
202 | 204 | ) |
203 | 205 |
|
204 | | - default_enabled = DagRunListener.enable_plugin_by_default(dag_run.dag_id) |
| 206 | + default_enabled = DagRunListener.enable_plugin_by_default() |
205 | 207 |
|
206 | 208 | if enable_plugin is False or ( |
207 | 209 | enable_plugin is None and not default_enabled |
@@ -331,27 +333,71 @@ def get_airflow_url(project: str, region: str, env: str) -> str: |
331 | 333 | return configs["config"]["airflowUri"] |
332 | 334 |
|
333 | 335 | @staticmethod |
334 | | - def is_in_allow_list(dag_id: str) -> bool: |
335 | | - allow_list = read_list_from_gcs( |
| 336 | + def is_in_allow_list(dag_run: DagRun) -> bool: |
| 337 | + allow_items = read_items_from_gcs( |
336 | 338 | os.environ.get("GCS_BUCKET"), ALLOW_LIST_PATH |
337 | 339 | ) |
338 | | - return True if dag_id in allow_list else False |
| 340 | + return DagRunListener.is_in_list(allow_items, dag_run) |
339 | 341 |
|
340 | 342 | @staticmethod |
341 | | - def is_in_block_list(dag_id: str) -> bool: |
342 | | - block_list = read_list_from_gcs( |
| 343 | + def is_in_block_list(dag_run: DagRun) -> bool: |
| 344 | + block_items = read_items_from_gcs( |
343 | 345 | os.environ.get("GCS_BUCKET"), BLOCK_LIST_PATH |
344 | 346 | ) |
345 | | - return True if dag_id in block_list else False |
| 347 | + return DagRunListener.is_in_list(block_items, dag_run) |
346 | 348 |
|
347 | 349 | @staticmethod |
348 | | - def enable_plugin_by_default(dag_id: str) -> bool: |
| 350 | + def is_in_list(items: Set[str], dag_run: DagRun): |
| 351 | + return ( |
| 352 | + DagRunListener.contains_id(items, dag_run.dag_id) |
| 353 | + or DagRunListener.has_any_tag(items, dag_run.dag.tags) |
| 354 | + or DagRunListener.matches_pattern(items, dag_run.dag_id) |
| 355 | + ) |
| 356 | + |
| 357 | + @staticmethod |
| 358 | + def enable_plugin_by_default() -> bool: |
349 | 359 | config = read_json_from_gcs(os.environ.get("GCS_BUCKET"), CONFIG_PATH) |
350 | 360 | logging.info( |
351 | 361 | f"[DagRunListener] Enable plugin by default: {config['enable_plugin_by_default']}" |
352 | 362 | ) |
353 | 363 | return config["enable_plugin_by_default"] |
354 | 364 |
|
| 365 | + @staticmethod |
| 366 | + def contains_id(items: Set[str], target_id: str): |
| 367 | + id_set = set() |
| 368 | + for item in items: |
| 369 | + parts = item.split(":", 1) |
| 370 | + if len(parts) == 2: |
| 371 | + key = parts[0].lower() |
| 372 | + value = parts[1].strip() |
| 373 | + if key == "id": |
| 374 | + id_set.add(value) |
| 375 | + return target_id in id_set |
| 376 | + |
| 377 | + @staticmethod |
| 378 | + def has_any_tag(items: Set[str], target_tags: List[str]): |
| 379 | + tag_set = set() |
| 380 | + for item in items: |
| 381 | + parts = item.split(":", 1) |
| 382 | + if len(parts) == 2: |
| 383 | + key = parts[0].lower() |
| 384 | + value = parts[1].strip() |
| 385 | + if key == "tag" and value: |
| 386 | + tag_set.add(value) |
| 387 | + return not tag_set.isdisjoint(target_tags) |
| 388 | + |
| 389 | + @staticmethod |
| 390 | + def matches_pattern(items: Set[str], target_id: str): |
| 391 | + pattern_set = set() |
| 392 | + for item in items: |
| 393 | + parts = item.split(":", 1) |
| 394 | + if len(parts) == 2: |
| 395 | + key = parts[0].lower() |
| 396 | + value = parts[1].strip() |
| 397 | + if key == "pattern" and value: |
| 398 | + pattern_set.add(value) |
| 399 | + return any(fnmatch.fnmatch(target_id, pattern) for pattern in pattern_set) |
| 400 | + |
355 | 401 |
|
356 | 402 | class ListenerPlugins(AirflowPlugin): |
357 | 403 | name = "listener_plugins" |
|
0 commit comments