This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new bdc61474938 Don't create audit log for API calls with dry_run=True.
(#55116)
bdc61474938 is described below
commit bdc61474938554945892fd9ce1954a87aa3aad95
Author: Karthikeyan Singaravelan <[email protected]>
AuthorDate: Tue Sep 2 20:40:42 2025 +0530
Don't create audit log for API calls with dry_run=True. (#55116)
* Don't create audit log for API calls with dry_run=True.
* Skip audit_log only for clear task instance and dag run dry runs.
---
.../src/airflow/api_fastapi/logging/decorators.py | 4 +++
.../core_api/routes/public/test_dag_run.py | 9 +++++-
.../core_api/routes/public/test_task_instances.py | 35 ++++++++++++++++++++--
3 files changed, 45 insertions(+), 3 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/logging/decorators.py
b/airflow-core/src/airflow/api_fastapi/logging/decorators.py
index 8ee14acf320..d5a073f3044 100644
--- a/airflow-core/src/airflow/api_fastapi/logging/decorators.py
+++ b/airflow-core/src/airflow/api_fastapi/logging/decorators.py
@@ -81,6 +81,7 @@ def action_logging(event: str | None = None):
):
"""Log user actions."""
event_name = event or request.scope["endpoint"].__name__
+ skip_dry_run_events = {"clear_dag_run", "post_clear_task_instances"}
if not user:
user_name = "anonymous"
@@ -98,6 +99,9 @@ def action_logging(event: str | None = None):
request_body = {}
masked_body_json = {}
+ if event_name in skip_dry_run_events and request_body.get("dry_run",
True):
+ return
+
fields_skip_logging = {
"csrf_token",
"_csrf_token",
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py
index b990c027239..b04fed4d72c 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_dag_run.py
@@ -28,7 +28,7 @@ from sqlalchemy import select
from airflow._shared.timezones import timezone
from airflow.api_fastapi.core_api.datamodels.dag_versions import
DagVersionResponse
from airflow.listeners.listener import get_listener_manager
-from airflow.models import DagModel, DagRun
+from airflow.models import DagModel, DagRun, Log
from airflow.models.asset import AssetEvent, AssetModel
from airflow.providers.standard.operators.empty import EmptyOperator
from airflow.sdk.definitions.asset import Asset
@@ -1340,6 +1340,13 @@ class TestClearDagRun:
dag_run = session.scalar(select(DagRun).filter_by(dag_id=DAG1_ID,
run_id=DAG1_RUN1_ID))
assert dag_run.state == DAG1_RUN1_STATE
+ logs = (
+ session.query(Log)
+ .filter(Log.dag_id == DAG1_ID, Log.run_id == dag_run_id, Log.event
== "clear_dag_run")
+ .count()
+ )
+ assert logs == 0
+
def test_clear_dag_run_not_found(self, test_client):
response = test_client.post(f"/dags/{DAG1_ID}/dagRuns/invalid/clear",
json={"dry_run": False})
assert response.status_code == 404
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
index f9306d20fe0..c19b7d20728 100644
---
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
+++
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_task_instances.py
@@ -33,7 +33,7 @@ from airflow.dag_processing.bundles.manager import
DagBundlesManager
from airflow.jobs.job import Job
from airflow.jobs.triggerer_job_runner import TriggererJobRunner
from airflow.listeners.listener import get_listener_manager
-from airflow.models import DagRun, TaskInstance
+from airflow.models import DagRun, Log, TaskInstance
from airflow.models.dag_version import DagVersion
from airflow.models.dagbag import DagBag, sync_bag_to_db
from airflow.models.renderedtifields import RenderedTaskInstanceFields as RTIF
@@ -2391,7 +2391,9 @@ class
TestPostClearTaskInstances(TestTaskInstanceEndpoint):
)
assert response.status_code == 200
assert response.json()["total_entries"] == expected_ti
- check_last_log(session, dag_id=request_dag,
event="post_clear_task_instances", logical_date=None)
+
+ if not payload.get("dry_run", True):
+ check_last_log(session, dag_id=request_dag,
event="post_clear_task_instances", logical_date=None)
@pytest.mark.parametrize("flag", ["include_future", "include_past"])
def test_dag_run_with_future_or_past_flag_returns_400(self, test_client,
session, flag):
@@ -2978,6 +2980,35 @@ class
TestPostClearTaskInstances(TestTaskInstanceEndpoint):
assert response.status_code == 404
assert "The Dag with ID: `non-existent-dag` was not found" in
response.text
+ @pytest.mark.parametrize(
+ "dry_run, audit_log_count",
+ [
+ (True, 0),
+ (False, 1),
+ ],
+ )
+ def test_dry_run_audit_log(self, test_client, session, dry_run,
audit_log_count):
+ dag_id = "example_python_operator"
+ dag_run_id = "TEST_DAG_RUN_ID"
+ event = "post_clear_task_instances"
+
+ payload = {"dry_run": dry_run, "dag_run_id": dag_run_id}
+ self.create_task_instances(session, dag_id)
+
+ response = test_client.post(
+ f"/dags/{dag_id}/clearTaskInstances",
+ json=payload,
+ )
+
+ logs = (
+ session.query(Log)
+ .filter(Log.dag_id == dag_id, Log.run_id == dag_run_id, Log.event
== event)
+ .count()
+ )
+
+ assert response.status_code == 200
+ assert logs == audit_log_count
+
class TestGetTaskInstanceTries(TestTaskInstanceEndpoint):
def test_should_respond_200(self, test_client, session):